ネットにある PostgreSQL + Docker の記事の Compose ファイルをそのまま使い、タグだけ 18 にすると、コンテナが数秒で止まります。
$ docker run -d --name pgold -e POSTGRES_PASSWORD=secret -v pgold:/var/lib/postgresql/data postgres:18
b480eb9c72b29fa99f1ed8df17972e2bfc2d6da4b86c058a36cf21fd63579bfa
$ docker ps -a --filter name=pgold --format "{{.Status}}"
Exited (1) 5 seconds ago
パスワードや Volume の設定ミスではありません。公式イメージがデータを置く場所を変えたためです。以下は postgres:18(PostgreSQL 18.6)、Docker Engine 29.9 での実行結果です。
ログに書かれていること
$ docker logs pgold 2>&1 | head -n 12
Error: in 18+, these Docker images are configured to store database data in a
format which is compatible with "pg_ctlcluster" (specifically, using
major-version-specific directory names). This better reflects how
PostgreSQL itself works, and how upgrades are to be performed.
See also https://github.com/docker-library/postgres/pull/1259
Counter to that, there appears to be PostgreSQL data in:
/var/lib/postgresql/data (unused mount/volume)
This is usually the result of upgrading the Docker image without
upgrading the underlying database using "pg_upgrade" (which requires both
17 までのイメージは PGDATA=/var/lib/postgresql/data だったので、みんなそこに Volume をマウントしていました。18 からはメジャーバージョンごとのディレクトリがデフォルトです。
| イメージ | デフォルトのデータディレクトリ | Volume のマウント先 |
|---|---|---|
postgres:17 以前 |
/var/lib/postgresql/data |
/var/lib/postgresql/data |
postgres:18 |
/var/lib/postgresql/18/docker |
/var/lib/postgresql |
古いマウント先のままだと、実際のデータディレクトリは Volume の外、つまりコンテナの書き込みレイヤーに作られ、コンテナを作り直した時点で消えてしまいます。エントリポイントは使われていないマウントを検出して起動を拒否します。面倒ですが、気づかないうちにデータを失うのを防いでくれる仕組みです。
対処法
親ディレクトリにマウントします。
$ docker run -d --name pg -e POSTGRES_PASSWORD=secret -p 127.0.0.1:5432:5432 -v pgdata:/var/lib/postgresql postgres:18
$ docker exec pg psql -U postgres -c "SHOW data_directory;"
data_directory
-------------------------------
/var/lib/postgresql/18/docker
(1 row)
Compose の場合:
services:
db:
image: postgres:18
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql
volumes:
pgdata:
ディレクトリ名にメジャーバージョンが入るので、将来のバージョンのデータを同じ Volume に並べて置けます。これがこの変更の狙いです。
既存の 16 / 17 のデータベースを移行する
既存のデータベースでタグだけ変えても、別の理由で動きません。データファイルはメジャーバージョン間で互換性がないからです。16 のデータディレクトリを 18 で開くと(PGDATA で古いパスを指定):
$ docker run --name pg18 -e POSTGRES_PASSWORD=secret -e PGDATA=/var/lib/postgresql/data -v pg16data:/var/lib/postgresql/data postgres:18 2>&1 | tail -n 3
2026-10-13 00:54:09.740 UTC [1] FATAL: database files are incompatible with server
2026-10-13 00:54:09.740 UTC [1] DETAIL: The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 18.6 (Debian 18.6-1.pgdg13+2).
いちばん簡単で安全なのは、ダンプして新しい Volume にリストアする方法です。古い Volume は結果を確認するまで残しておきます。
$ docker exec pg16 pg_dumpall -U postgres > upgrade.sql
$ docker run -d --name pg18 -e POSTGRES_PASSWORD=secret -v pg18data:/var/lib/postgresql postgres:18
$ docker exec -i pg18 psql -U postgres -q < upgrade.sql
ERROR: role "postgres" already exists
このエラーは想定どおりです。新しいコンテナがすでに postgres ロールを作っていて、ダンプがもう一度作ろうとしているだけで、それ以外はリストアされます。バージョンとデータを確認してから、アプリの接続先を新しいコンテナに切り替えます。
Windows では注意点が 2 つあります。PowerShell は < による入力をサポートしておらず、Windows PowerShell 5.1 の > は UTF-16 で書き出すのでダンプが壊れます。その場合は pg_dumpall -f でコンテナ内にファイルを作り、docker cp で移動します。
postgres:18 チェックリスト
- Volume は
/var/lib/postgresqlにマウントする(/var/lib/postgresql/dataではない) -
PGDATAを自分で指定する場合は、マウントした Volume の中にする - マイナーアップデート(18.5 → 18.6)は pull して作り直すだけ。メジャーアップグレードはダンプ&リストア(または
pg_upgrade)で、新しい Volume へ -
POSTGRES_PASSWORDなどの変数はデータディレクトリが空のときだけ有効。後から変えても既存のパスワードは変わらない
Compose 構成、早すぎる healthy を返さないヘルスチェック、バックアップ、16 → 18 移行の全出力は、日本語の無料 Docker チュートリアルにまとめています:Docker で PostgreSQL を動かす