Deploy: create the data directories instead of assuming Docker will

"Bind mount failed: '/volume1/docker/webmail/data/postgres' doesn't exists" on
Synology after the data directory was removed. Docker on ordinary Linux creates
a missing bind-mount source silently; DSM's daemon refuses and fails the
container. Every service mounted its own subdirectory, so every one of them
needed to already exist — including the `init` service whose whole job was to
create them, which could therefore never run.

- init now mounts DATA_ROOT itself, once, and creates postgres/blobs/backups/
  redis/meili/state inside it. Only DATA_ROOT has to pre-exist.
- postgres gained the init dependency it was missing, so its data directory is
  never demanded before init has made it.
- init only runs `chown -R` when ownership is actually wrong, rather than
  walking the whole blob store on every start.
- webmail.sh up creates DATA_ROOT and, if it cannot, says so with the mkdir to
  run rather than leaving the daemon to produce the error above.

Verified from an empty DATA_ROOT: init creates all six directories, postgres
starts after it, migrations apply, stack serves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Nux3LCu4zP9nih4BRhPyHw
This commit is contained in:
2026-09-14 20:41:09 +02:00
co-authored by Claude Opus 5
parent 8f96a1ecae
commit ff64a5dfe7
2 changed files with 24 additions and 12 deletions
+15 -8
View File
@@ -96,17 +96,22 @@ services:
- -c
- |
set -e
mkdir -p /mnt/blobs /mnt/backups /mnt/redis /mnt/meili
chown -R 10001:10001 /mnt/blobs # backend runs as uid 10001
chown -R 999:999 /mnt/backups # pg_dump runs as postgres (999)
chown -R 999:999 /mnt/redis # redis runs as uid 999
mkdir -p /mnt/postgres /mnt/blobs /mnt/backups /mnt/redis /mnt/meili /mnt/state
# Only recurse when ownership is actually wrong: `chown -R` over a large
# blob store on every start would be minutes of pointless disk churn.
[ "$$(stat -c %u /mnt/blobs)" = "10001" ] || chown -R 10001:10001 /mnt/blobs
[ "$$(stat -c %u /mnt/backups)" = "999" ] || chown -R 999:999 /mnt/backups
[ "$$(stat -c %u /mnt/redis)" = "999" ] || chown -R 999:999 /mnt/redis
chmod 700 /mnt/backups
echo "[init] data directories ready"
volumes:
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/blobs:/mnt/blobs
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/backups:/mnt/backups
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/redis:/mnt/redis
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/meili:/mnt/meili
# ONE mount, of DATA_ROOT itself. Synology's Docker daemon refuses to
# create a missing bind-mount source ("Bind mount failed: ... doesn't
# exists") instead of creating it the way Docker does elsewhere, so every
# subdirectory mounted below must exist before its container is created.
# Mounting the parent here means only DATA_ROOT has to pre-exist, and this
# service creates the rest — which is why everything now depends on it.
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}:/mnt
restart: "no"
logging: *logging
@@ -135,6 +140,8 @@ services:
# Full page writes stay ON: NAS filesystems (btrfs/ext4) are not 8k-atomic.
volumes:
- ${DATA_ROOT:-/NO-.env-FOUND-RUN-deploy_webmail.sh-init-FIRST}/postgres:/var/lib/postgresql/data
depends_on:
init: { condition: service_completed_successfully }
healthcheck:
# -h 127.0.0.1 forces a TCP check. Without it pg_isready uses the Unix
# socket, which the temporary server that the postgres image runs during