Synology Container Manager rejects the compose file with "Property include is
not allowed": DSM bundles an older Compose than the one that introduced
`include:` (v2.20). The root docker-compose.yml existed purely so the NAS GUIs
would find production rather than the dev service stack, so using a feature
the GUI cannot parse defeated its only purpose.
docker/docker-compose.prod.yml is now the root docker-compose.yml itself — one
plain, flat file, no indirection. Build contexts go from `..` to `.`, the
backup sidecar mounts ./docker/backup.sh, and webmail.sh points at the new
path. Nothing else changed: same services, same project name webmail-prod,
same guards.
The header now says why the file lives at the root and to keep it free of
newer compose features, so this does not get "tidied up" back into something
DSM refuses.
Verified with `docker compose up -d --wait` from the repo root as the GUI
invokes it: all services healthy, account created, backup sidecar resolving
its script at the new path, and webmail.sh status/doctor/down driving the same
stack.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw
Synology Container Manager and QNAP Container Station build a project from a
folder and look for `docker-compose.yml` in it. Pointing either at `docker/`
finds the DEV service stack and starts the wrong thing, so production now has
a root-level entry point.
- docker-compose.yml at the repo root: `include`s the prod stack (relative
paths resolve against the included file's directory, so the build context
and backup.sh still resolve) and pins `name: webmail-prod`. A project name
typed into the GUI overrides the file's, so the docs say to type the same
one.
- webmail.sh init now drops a `.env` symlink to docker/.env.prod in the repo
root: the GUI reads `.env` from the project folder, and a symlink keeps one
file to edit instead of two copies that drift.
- Required-variable guards on DATA_ROOT / MASTER_KEY / POSTGRES_PASSWORD /
APP_DOMAIN. A bare `docker compose up` with no env file used to interpolate
DATA_ROOT to "" and bind-mount /postgres at the root of the filesystem; it
now aborts with a message naming the variable.
- require_env no longer sources the env file. Sourcing executed it as shell:
ENABLED_PLUGINS=calendar contacts labels filters ran `contacts` as a command
and broke every subcommand, and anything else in the file that holds the
master key would have been executed too. Replaced with a compose-compatible
KEY=VALUE parser; the template quotes the value as well.
- doctor: no longer aborts on a not-yet-created DATA_ROOT (set -e + pipefail
killed it inside a `df` assignment — on exactly the pre-install run where
its NFS/SMB check is the one that matters), and probes the nearest existing
ancestor so that check works before the directory exists.
- docs §2.5 rewritten with what the GUI can and cannot do: no compose
profiles (so no Meilisearch), no health-gated update, no restore; plus the
Task Scheduler route for generating MASTER_KEY without enabling SSH, and
why init/migrate showing "Exited (0)" is correct.
Verified by running `docker compose up -d --wait` from the repo root with no
-f and no --env-file (what the GUI does): all services healthy, migrations
applied, app served, and deploy/webmail.sh manages the same project.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw