Deploy: fix the two things that broke the Container Manager build
DSM reported "invalid interpolation format for x-backend-env.MASTER_KEY" and
"required variable MASTER_KEY is missing a value". Both were mine.
1. DSM's bundled Compose cannot parse `${VAR:?message}`. Those guards are gone;
interpolation now sticks to `${VAR}` and `${VAR:-default}`, the forms every
Compose understands. Bind mounts keep a `${DATA_ROOT:-/DATA_ROOT-is-unset}`
sentinel so a missing config still cannot scatter directories across the
root of the NAS filesystem, and the header says to keep it this way.
2. Config was at docker/.env.prod with a `.env` symlink beside it. Container
Manager did not pick that up. The real file is now `.env` in the project
folder — what every compose front end reads without being told — and
docker/.env.prod becomes a symlink to it. `init` and `require_env` migrate
an existing install in place, keeping its MASTER_KEY.
Also, while testing the migration: `init` only generated secrets when creating
a file from scratch, so a migrated or hand-written .env with an empty
POSTGRES_PASSWORD started Postgres in trust-auth mode. It now tops up empty
secrets — and never touches a MASTER_KEY that already has a value, since
replacing it would make every stored credential undecryptable. doctor flags an
empty password too.
Verified: parses cleanly with no .env at all (the failure DSM hit), migration
preserves an existing key, and `docker compose up -d --wait` from the project
folder brings the stack up healthy and serving.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw
This commit is contained in:
+20
-15
@@ -21,6 +21,11 @@
|
||||
# Start: ./deploy/webmail.sh up
|
||||
# Manual: docker compose up -d (reads .env from this directory)
|
||||
#
|
||||
# Interpolation here must stay on the oldest, dumbest forms: ${VAR} and
|
||||
# ${VAR:-default}. DSM's bundled Compose rejects `${VAR:?message}` with
|
||||
# "invalid interpolation format", and rejects `include:` outright. Required
|
||||
# values are enforced by deploy/webmail.sh and `webmail.sh doctor` instead.
|
||||
#
|
||||
# Project name is deliberately NOT "webmail": docker/docker-compose.yml uses
|
||||
# that, and two compose files sharing one project name fight over each other's
|
||||
# containers — running `make up` would stop production.
|
||||
@@ -42,10 +47,10 @@ x-backend-image: &backend-image
|
||||
GIT_SHA: ${GIT_SHA:-unknown}
|
||||
|
||||
x-backend-env: &backend-env
|
||||
APP_DOMAIN: "${APP_DOMAIN:?set APP_DOMAIN in your .env}"
|
||||
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-webmail}:${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in your .env}@postgres:5432/${POSTGRES_DB:-webmail}
|
||||
APP_DOMAIN: "${APP_DOMAIN:-APP_DOMAIN-unset}"
|
||||
DATABASE_URL: postgresql+asyncpg://${POSTGRES_USER:-webmail}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB:-webmail}
|
||||
REDIS_URL: redis://redis:6379/0
|
||||
MASTER_KEY: "${MASTER_KEY:?set MASTER_KEY in your .env - generate one with openssl rand -base64 32}"
|
||||
MASTER_KEY: "${MASTER_KEY}"
|
||||
BLOB_ROOT: /data/blobs
|
||||
SEARCH_BACKEND: ${SEARCH_BACKEND:-pg}
|
||||
MEILI_URL: ${MEILI_URL:-http://meilisearch:7700}
|
||||
@@ -90,10 +95,10 @@ services:
|
||||
chmod 700 /mnt/backups
|
||||
echo "[init] data directories ready"
|
||||
volumes:
|
||||
- ${DATA_ROOT}/blobs:/mnt/blobs
|
||||
- ${DATA_ROOT}/backups:/mnt/backups
|
||||
- ${DATA_ROOT}/redis:/mnt/redis
|
||||
- ${DATA_ROOT}/meili:/mnt/meili
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/blobs:/mnt/blobs
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/backups:/mnt/backups
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/redis:/mnt/redis
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/meili:/mnt/meili
|
||||
restart: "no"
|
||||
logging: *logging
|
||||
|
||||
@@ -121,7 +126,7 @@ services:
|
||||
- effective_cache_size=${PG_EFFECTIVE_CACHE_SIZE:-768MB}
|
||||
# Full page writes stay ON: NAS filesystems (btrfs/ext4) are not 8k-atomic.
|
||||
volumes:
|
||||
- ${DATA_ROOT:?set DATA_ROOT in your .env (see docker/.env.prod.example)}/postgres:/var/lib/postgresql/data
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/postgres:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-webmail} -d ${POSTGRES_DB:-webmail}"]
|
||||
interval: 10s
|
||||
@@ -148,7 +153,7 @@ services:
|
||||
- --maxmemory-policy
|
||||
- noeviction
|
||||
volumes:
|
||||
- ${DATA_ROOT}/redis:/data
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
@@ -171,7 +176,7 @@ services:
|
||||
MEILI_NO_ANALYTICS: "true"
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- ${DATA_ROOT}/meili:/meili_data
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/meili:/meili_data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7700/health | grep -q available"]
|
||||
interval: 10s
|
||||
@@ -194,7 +199,7 @@ services:
|
||||
<<: *backend-env
|
||||
ENABLED_PLUGINS: ${ENABLED_PLUGINS:-calendar contacts labels filters}
|
||||
volumes:
|
||||
- ${DATA_ROOT}/blobs:/data/blobs
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/blobs:/data/blobs
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
init: { condition: service_completed_successfully }
|
||||
@@ -206,7 +211,7 @@ services:
|
||||
command: ["api"]
|
||||
environment: *backend-env
|
||||
volumes:
|
||||
- ${DATA_ROOT}/blobs:/data/blobs
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/blobs:/data/blobs
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "python -c \"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/api/healthz', timeout=5).status==200 else 1)\""]
|
||||
interval: 30s
|
||||
@@ -227,7 +232,7 @@ services:
|
||||
command: ["worker-sync"]
|
||||
environment: *backend-env
|
||||
volumes:
|
||||
- ${DATA_ROOT}/blobs:/data/blobs
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/blobs:/data/blobs
|
||||
# Persistent IMAP IDLE connections; the default 1024 is fine for a personal
|
||||
# install but this costs nothing and matches the sync-engine v2 sizing.
|
||||
ulimits:
|
||||
@@ -248,7 +253,7 @@ services:
|
||||
command: ["worker-jobs"]
|
||||
environment: *backend-env
|
||||
volumes:
|
||||
- ${DATA_ROOT}/blobs:/data/blobs
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/blobs:/data/blobs
|
||||
ulimits:
|
||||
nofile: { soft: 8192, hard: 8192 }
|
||||
depends_on:
|
||||
@@ -304,7 +309,7 @@ services:
|
||||
BACKUP_KEEP_DAYS: ${BACKUP_KEEP_DAYS:-14}
|
||||
TZ: ${TZ:-UTC}
|
||||
volumes:
|
||||
- ${DATA_ROOT}/backups:/backups
|
||||
- ${DATA_ROOT:-/DATA_ROOT-is-unset}/backups:/backups
|
||||
- ./docker/backup.sh:/usr/local/bin/backup.sh:ro
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
|
||||
Reference in New Issue
Block a user