From 2589d4baf4e2f31f03b79aa7a6e7d64b88101d63 Mon Sep 17 00:00:00 2001 From: Fabian Wagner Date: Thu, 28 May 2026 10:57:13 +0200 Subject: [PATCH] =?UTF-8?q?U=20principle=20=C2=A76:=20bind=20mounts=20only?= =?UTF-8?q?,=20never=20named=20volumes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promote 'don't use named volumes' from a mysql/redis-specific explanation to a hard rule that applies to anything needing persistence (ssh host keys, uploads, queue state, etc). Lead with the 'docker compose down -v wipes named volumes' rationale since that's the keystroke this rule is really protecting against. Add a counter-pattern example. --- PRINCIPLES/dockerization-and-deployment.md | 50 ++++++++++++++++++---- 1 file changed, 42 insertions(+), 8 deletions(-) diff --git a/PRINCIPLES/dockerization-and-deployment.md b/PRINCIPLES/dockerization-and-deployment.md index 5e1244e..cfa79d3 100644 --- a/PRINCIPLES/dockerization-and-deployment.md +++ b/PRINCIPLES/dockerization-and-deployment.md @@ -393,30 +393,64 @@ that's why each one starts with the app's slug. --- -## 6. Persistent data: `./docker-data/` in the repo, gitignored +## 6. Persistent data: `./docker-data/` bind mounts, never named volumes -Bind-mount mysql and redis storage to a `docker-data/` folder right next -to the source: +**Rule: any service that needs to keep state between container restarts +gets a bind mount under `./docker-data//`. Never a named docker +volume.** This applies to mysql, redis, ssh host keys, app uploads, +queue state, every "this dir needs to survive" case — same shape, same +location, no exceptions. ``` docker-data/ mysql/ # mysql:8.0 datadir redis/ # redis dump.rdb + … # whatever else needs persistence ``` -The folder is gitignored (`docker-data/` in `.gitignore`). It survives -`docker compose down`. The deploy script `mkdir -p`s it on first run so -fresh boxes Just Work. +The folder is gitignored (`docker-data/` in `.gitignore`). The deploy +script `mkdir -p`s it on first run so fresh boxes Just Work. -### Why not named docker volumes? +### Why bind mounts, not named volumes + +The headline reason: **`docker compose down -v` wipes named volumes**. +That command is in too many people's muscle memory ("nuke the stack and +start clean") for the production datastore to be one accidental keystroke +away from gone. Bind mounts under the repo are immune — `down -v` +doesn't touch them. + +The rest of the rationale: - Trivially backed up — `tar -caf data.tar.xz docker-data/` from the repo root is the entire prod state. - Survives container/image churn including accidental - `docker volume prune -af`. + `docker volume prune -af` and `docker system prune --volumes`. - Discoverable — anyone with the repo can see where the data lives. - The repo path identifies which app owns the data when you have ten apps on one host. +- Restoring on a new host is `git clone && rsync docker-data/` — no + per-volume `docker volume create && docker run --rm -v ... tar` dance. + +### Counter-pattern (do not do this) + +```yaml +# WRONG — named volume; `docker compose down -v` deletes the data. +services: + mysql: + volumes: + - mysql-data:/var/lib/mysql + +volumes: + mysql-data: +``` + +```yaml +# RIGHT — bind mount; survives `down -v`. +services: + mysql: + volumes: + - ./docker-data/mysql:/var/lib/mysql +``` ---