Postgresのボリュームパスが3年間間違っていたのに動いていた理由 — VOLUME宣言がこっそり付けた名前のないボリューム
データはパスではなくボリューム名についていく。downを使う人だけがDBを失う
docker-composeのPostgresのボリュームが db:/data/db になっていた。MongoDBのパスだ。3年前、最初にセットアップしたときに自分が入れたミスだった。それなのに自分はその間、DBが消えたことが一度もなかった。同僚は「コンテナを立て直すたびにDBが消える」と言っていた。
先に結論。パスが間違っていてもPostgresは普通に動く。イメージが VOLUME で宣言したパスに、Dockerが名前のないボリュームを付けるからだ。そのボリュームは down した瞬間に捨てられる。 手元のcomposeファイルのDBボリュームの行を一度見ておくといい。
何が付いていたのか
db:
image: postgres:16
volumes: ["db:/data/db"] # Postgresはここを使わない
公式postgres 16イメージのDockerfileにはこの二行がある。
ENV PGDATA /var/lib/postgresql/data
VOLUME /var/lib/postgresql/data
VOLUME は「このパスはコンテナの外に置く」という宣言だ。composeがそのパスに何も付けなければ、Dockerが名前のない(anonymous)ボリュームを作って付ける。実際の姿はこうだった。
名前付きボリューム db は誰も使わない /data/db に付いていて、3年間空っぽだった。本当のデータはハッシュ名の名前のないボリュームにあった。
なぜ自分だけ無事だったのか — 習慣が分けた
| コマンド | 直す前(名前のないボリューム) | 直した後(名前付きボリューム) |
|---|---|---|
stop・start・restart |
残る | 残る |
up -d でコンテナ再作成 |
残る | 残る |
down → up |
空のDB | 残る |
rm の後 up、up -V |
空のDB | 残る |
down -v、volume prune |
空のDB | 空のDB |
up でコンテナを作り直すとき、composeは前のコンテナの名前のないボリュームを引き継ぐ。公式ドキュメントの -V, --renew-anon-volumes の説明が「前のコンテナからデータを持ってくる代わりに、名前のないボリュームを作り直す」なので、既定は引き継ぎだ。自分は up と stop しか使わず、同僚は down で片付ける習慣があった。バグはひとつなのに、症状は人によって違った。
最初に混乱したところ
「パスが同じならデータも残るはずでは」と思っていた。違った。コンテナを作り直すと、ファイルシステムもイメージから新しく展開される。データが残るのは、そのパスに同じ名前のボリュームがもう一度付くときだけだ。データはパスではなくボリューム名についていく。
/var/lib/postgresql/data はMacのパスでもない。コンテナの中のパスで、実際のファイルはDocker DesktopのVM内 /var/lib/docker/volumes/<プロジェクト名>_db/_data にある。Homebrewで入れたPostgresとも混ざらない。
自分のMacには、どこにも付いていないボリューム(docker volume ls -qf dangling=true)が32個あった。そのうちいくつが過去の down で捨てられたDBなのかは、まだ数えていない。
別のDBの行をコピーすると同じことが起きる
公式イメージの VOLUME はばらばらだ(各Dockerfile・Docker Hubの説明、2026-10-19確認)。
| イメージ | VOLUME |
|---|---|
| postgres 17以下 | /var/lib/postgresql/data |
| postgres 18以上 | /var/lib/postgresql(PGDATAは /var/lib/postgresql/18/docker) |
| mysql | /var/lib/mysql |
| mongo | /data/db、/data/configdb |
| redis | /data |
別プロジェクトのcomposeからボリュームの行だけ持ってくると、今回と同じことになる。Postgres 18に上げるときも同じ行を見直す。Docker Hubの説明は、18からは /var/lib/postgresql にマウントするよう書いている。17時代の :/var/lib/postgresql/data をそのまま残すと、PGDATAがその外に置かれる。
確かめ方
docker image inspect postgres:16 --format '{{json .Config.Volumes}}'
docker inspect $(docker compose ps -q db) --format '{{json .Mounts}}'
二つ目の結果で、PGDATAのパスに付いたボリュームの Name が64文字のハッシュなら、この罠にはまっている。
ボリューム三種類、いつどれを
| 種類 | こんなとき |
|---|---|
名前付きボリューム(db:/var/lib/postgresql/data) |
ローカル開発DBの基本。down しても残る |
bind mount(./tmp/pg:/var/lib/postgresql/data) |
データをフォルダとして見たい、持ち運びたいとき |
| 名前のないボリューム | 捨ててもいいキャッシュ。DBには使わない |
ローカル開発環境が人によって違う壊れ方をするなら、たいていこういう隠れた既定値がひとつ原因になっている。一緒に見てほしければ https://www.negabaro.com/contact から連絡してほしい。
動作フロー
イメージのDockerfileのVOLUMEがPGDATAのパスを「外に置く場所」として宣言
composeがそこに何も付けなければ、Dockerが名前のないボリュームを付ける
upで作り直すと前の名前のないボリュームを引き継ぐのでデータが残る
down → upだと新しい名前のないボリュームが付き、空のDBになる