PostgreSQL Docker起動エラー切り分け手順
目次
Docker公式イメージでPostgreSQLを起動したのにコンテナが即落ちする、ホストの psql から繋がらない、docker compose down でデータが消えた。この手のトラブルは原因のパターンが限られていて、ほとんどは前回作られたボリュームが残っているか、接続先のホスト名とポートを取り違えているかのどちらかです。
ログ、ボリューム、ネットワークの順に確認する切り分け手順と、エラーメッセージ別の対処、再発しない構成を整理します。
結論:まず「残っているボリューム」と「接続先の指定」を確認する
公式 postgres イメージのエントリポイントは、PGDATA が空のときだけ initdb を実行します。次の環境変数とスクリプトが効くのは初回起動時だけです。
POSTGRES_PASSWORDPOSTGRES_USERPOSTGRES_DB/docker-entrypoint-initdb.d/配下の.sql/.sh
ボリュームが残った状態で docker-compose.yml のパスワードやDB名を書き換えても、既存クラスタの中身は変わりません。結果として password authentication failed for user "app" や database "app" does not exist、初期化SQLが流れないといった症状になります。
接続系のトラブルは、どこから繋ごうとしているかで見る場所が変わります。
| 接続元 | 指定すべきホスト | ポート |
|---|---|---|
ホストOSの psql / GUIツール |
localhost(または 127.0.0.1) |
ports: で公開した左側の番号 |
| 同じCompose内の別コンテナ(アプリ) | サービス名(例 db) |
コンテナ側の 5432 |
コンテナ内の psql |
省略(UNIXドメインソケット) | 5432 |
アプリコンテナから localhost:5432 を指定すると、アプリ自身のコンテナを見にいくので Connection refused になります。逆にホストから繋ぎたいのに ports: を書かず expose: だけ、というのも定番です。
この2点を確認してから、以下の手順に進んでください。
原因の切り分け手順(確認しやすい順)
docker logs の最後の数行で終了理由を特定する
推測で設定を書き換える前にログを見ます。終了済みのコンテナでもログは残っています。
# 起動しているか、Exitしているかを確認
docker compose ps -a
# 終了理由はログの末尾に出る
docker compose logs --tail=50 db
# compose を使っていない場合
docker logs --tail=50 my-postgres
docker inspect --format '{{.State.ExitCode}} {{.State.OOMKilled}}' my-postgres
見るポイントは3つです。1つ目は正常起動のログ。
LOG: database system is ready to accept connections
この行が出ていればPostgreSQL自体は起動しています。繋がらないならネットワーク側なので、後述の接続確認に進んでください。
2つ目は終了コードです。ExitCode: 1 はPostgreSQLかエントリポイントがエラー終了した状態で、理由はログに書かれています。137(SIGKILL)で OOMKilled: true ならメモリ不足なので、コンテナのメモリ上限や shared_buffers、work_mem を見直します。
3つ目は初期化フェーズのログか起動フェーズのログかの区別。initdb や The files belonging to this database system will be owned by user "postgres" が出ていれば初回初期化が走っています。これが出ないまま起動しないなら、既存データの読み込みで失敗しています。
ボリュームが初期化済みかどうかを確認する
次にデータディレクトリの実体を確認します。
docker volume ls
docker volume inspect pgdata
中身の確認は、使い捨てコンテナでマウントするのが手軽です。
# データディレクトリが初期化済みか、どのメジャーバージョンで作られたか
docker run --rm -v pgdata:/v alpine sh -c 'ls -A /v | head; cat /v/PG_VERSION 2>/dev/null'
PG_VERSION が 16 なのにComposeで image: postgres:18 を指定していれば起動しません(後述)。ls -A が空なら未初期化で、環境変数と初期化SQLが有効になります。
開発環境でデータを捨ててやり直してよいなら、ボリュームごと作り直すのが最短です。
docker compose down -v # -v を付けないとボリュームは残る
docker compose up -d
-v の有無がこの手のトラブルの分かれ目です。「down したらデータが消えた」なら -v を付けていた(またはボリューム定義をせず匿名ボリュームだった)、「環境変数を変えたのに反映されない」なら -v を付けていなかった、という対応関係になります。消していいデータか判断がつかないときは、先にバックアップを取ってください(後述のFAQ参照)。
コンテナ内 psql →ホストからの接続の順に試す
database system is ready to accept connections が出ているのに繋がらない場合は、内側から外側へ順に試して、どこで切れているかを特定します。
# 1) コンテナ内から UNIXソケット経由
docker compose exec db psql -U postgres -c 'select version();'
# 2) コンテナ内から TCP 経由(listen_addresses と pg_hba の確認になる)
docker compose exec db psql -h 127.0.0.1 -U postgres -c 'select 1;'
# 3) 別コンテナからサービス名で(名前解決とネットワークの確認)
docker compose exec app sh -c 'getent hosts db; nc -z -v db 5432'
# 4) ホストから公開ポート経由
psql -h localhost -p 5432 -U postgres -d postgres
- 1 が失敗 → PostgreSQL側の問題。ログを再確認
- 1 成功・2 失敗 → カスタムマウントした
listen_addressesやpg_hba.confの設定ミス - 2 成功・3 失敗 → Composeのネットワークが別、またはサービス名のtypo
- 3 成功・4 失敗 →
ports:の指定漏れ、ホスト側ポートの衝突、ファイアウォール
ホスト側ポートが使用中だと、起動時点で Error starting userland proxy: listen tcp4 0.0.0.0:5432: bind: address already in use が出ます。ホストにPostgreSQLを直接インストールした環境では頻発するので、ports: ["55432:5432"] のようにずらすのが安全です。
エラーメッセージ別の対処
Database is uninitialized and superuser password is not specified
Error: Database is uninitialized and superuser password is not specified.
You must specify POSTGRES_PASSWORD to a non-empty value for the
superuser.
初回初期化なのにスーパーユーザーのパスワードが渡っていません。公式イメージはパスワード未指定での初期化を拒否します。対処は次のいずれかです。
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD}
.env にパスワードを置くか、Docker secretsで POSTGRES_PASSWORD_FILE を指定します。パスワードなしで動かしたい検証環境では POSTGRES_HOST_AUTH_METHOD=trust も使えますが、これは pg_hba.conf の全エントリを trust にする指定で、ネットワークから誰でも接続できる状態になります。公開ポートを閉じた使い捨て環境に限定してください。
関連して出やすいエラーも押さえておきます。
initdb: error: directory "/var/lib/postgresql/data" exists but is not empty は、バインドマウント先に lost+found などが存在すると発生します。データディレクトリをサブディレクトリにずらして回避します。
environment:
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- ./pgdata:/var/lib/postgresql/data
FATAL: data directory "/var/lib/postgresql/data" has invalid permissions は、パーミッションが 0700(または 0750)になっていないときに出ます。バインドマウントではホスト側の所有者と権限がそのまま見えるため、chown/chmod が通らない環境、特にWindows側のファイルシステムでは解決しづらい。開発環境では名前付きボリュームに切り替えるのが確実です。
password authentication failed for user "..." は、既存ボリュームに古いパスワードのロールが残っているのが典型です。データを維持したまま直すなら、コンテナ内から変更します。
docker compose exec db psql -U postgres -c "ALTER USER app PASSWORD 'newpass';"
認証方式の不一致も疑ってください。最近のイメージは既定で scram-sha-256 を使うため、md5 しか扱えない古いクライアントライブラリでは認証できません。クライアント側の更新が本筋です。
database files are incompatible with server / incorrect checksum in control file
FATAL: database files are incompatible with server
DETAIL: The data directory was initialized by PostgreSQL version 16,
which is not compatible with this version 18.
イメージのタグだけ上げたときの定番です。PostgreSQLのデータファイル形式はメジャーバージョン間で互換性がないため、タグの書き換えだけではアップグレードできません。対処は次の3択です。
- タグを元のメジャーバージョンに戻して起動し、データを救出する(まずこれ)
pg_dumpallでダンプを取り、新バージョンのクラスタへリストアする- 旧新両方のバイナリを持つイメージで
pg_upgradeを実行する
開発環境で中身を捨ててよいなら docker compose down -v で作り直すのが最速です。手順の詳細や移行後に遅くなるケースは PostgreSQL 18 移行でつまずく原因と対処法 にまとめています。
PostgreSQL 18系の公式イメージでは、データディレクトリの既定レイアウトが変更されています。既存のボリューム定義のままタグを上げると、新しい空のディレクトリが初期化され、データが消えたように見える事故が起きます。タグを上げる前に、Docker Hubの公式イメージのドキュメントで PGDATA と VOLUME の宣言を確認してください。
FATAL: incorrect checksum in control file
こちらは global/pg_control が破損した状態です。システム識別子、最新チェックポイントのWAL位置、ブロックサイズなどクラスタの基本情報を持つファイルで、これが読めないと起動処理に入れません。原因としては、起動中のデータディレクトリを cp やファイル同期ツールでコピーした、ホストのディスクが満杯になった、書き込み途中で強制終了した、ネットワークファイルシステムやDocker Desktopの共有フォルダ上にデータを置いている、あたりが考えられます。
復旧はバックアップからのリストアが原則です。pg_resetwal で起動できる場合もありますが、未適用のWALを破棄する操作なので整合性は保証されません。実行前に必ずデータディレクトリ全体のコピーを取り、復旧後は amcheck や業務データの突き合わせで全テーブルを検証してください。
予防としては、データディレクトリをバインドマウントで共有ファイルシステム上に置かないこと。fsync の挙動が保証されない場所にWALとデータファイルを置けば、クラッシュリカバリが成立しません。
この記事のテーマをさらに学べる本
内部構造からバックアップ・リストア・モニタリングまで、PostgreSQL運用の知識を体系的に整理できます。
楽天ブックスで詳しく見る起動はするが遅い・不安定なときに見る設定(shm_size、ボリュームドライバ、チェックポイント)
まず shm_size です。Dockerコンテナの /dev/shm は既定で64MBしかありません。PostgreSQLはパラレルクエリやハッシュ結合で動的共有メモリ(DSM)を使うため、集計系のクエリで次のエラーが出ます。
ERROR: could not resize shared memory segment "/PostgreSQL.123456789" to 1048576 bytes:
No space left on device
不足しているのは共有メモリで、ディスク容量は関係ありません。
services:
db:
image: postgres:17
shm_size: 1gb
docker run なら --shm-size=1g です。dynamic_shared_memory_type=mmap に変更して回避する方法もありますが、性能上は /dev/shm を増やすほうが素直です。
ボリュームドライバも確認します。macOSとWindowsのDocker Desktopでは、ホストディレクトリのバインドマウントがファイル共有レイヤを経由するため、小さなランダムI/Oの多いPostgreSQLでは体感できるほど遅くなります。データディレクトリは名前付きボリューム(Linux VM内のネイティブなファイルシステム)に置き、ソースコードや初期化SQLだけをバインドマウントしてください。
チェックポイントとWALは既定値が小さく、checkpoints are occurring too frequently がログに出ることがあります。
command:
- postgres
- -c
- max_wal_size=2GB
- -c
- checkpoint_timeout=15min
- -c
- log_min_duration_statement=500ms
command で渡せばイメージを作り直さずに試せます。CIなどデータを失ってよい環境に限れば -c fsync=off -c full_page_writes=off で速くなりますが、クラッシュ時にクラスタが破損する設定なので、永続データを置く環境では使わないでください。
遅さの原因がVACUUMや実行計画側にあることも多いので、設定を触る前に待機イベントと実行計画を確認する手順は PostgreSQLが遅い・詰まる原因|VACUUMと実行計画、ロック を参照してください。
よくある質問
docker compose の初期化SQLが実行されないのはなぜ?
/docker-entrypoint-initdb.d/ のスクリプトは、データディレクトリが空のとき、つまり初回初期化時にしか実行されません。2回目以降の up では無視されます。docker volume ls でボリュームが残っていないか確認し、開発環境なら docker compose down -v でやり直してください。
ほかにつまずきやすい点を4つ挙げます。
- 実行されるのは
.sql/.sql.gz/.shのみ。拡張子が違うと無視される - 複数ファイルはファイル名の辞書順。
01_schema.sql,02_data.sqlのように連番を付ける - 初期化中のスクリプトがエラーになるとエントリポイントが終了し、コンテナが起動失敗する。ログに
psql:/docker-entrypoint-initdb.d/01_schema.sql:12: ERROR: ...のように行番号付きで出る - 初期化中は一時サーバーがUNIXソケットのみで待ち受ける。SQL内で
-h localhostを前提にした処理は失敗する
データを残したままメジャーバージョンを上げられる?
できますが、イメージのタグを書き換えるだけでは不可能です。方法は2つあります。論理バックアップ経由は確実で、ダウンタイムはデータ量に比例します。
# 旧バージョンのコンテナが起動している状態で
docker compose exec db pg_dumpall -U postgres > dumpall.sql
# 新しいタグ・新しいボリュームで起動し直してからリストア
docker compose exec -T db psql -U postgres < dumpall.sql
pg_upgrade 経由は大きいDBを短時間で済ませたい場合に使い、旧版と新版の両方のバイナリを含むイメージが必要です。--link はハードリンクでデータファイルを引き継ぐため高速ですが、失敗すると旧クラスタも使えなくなるので、事前のバックアップが前提になります。どちらの方式でも、アップグレード後は全データベースで ANALYZE を実行して統計情報を作り直してください(pg_upgrade は統計情報を引き継がないバージョンがあります)。
本番環境でPostgreSQLをコンテナで動かしてもいい?
技術的には可能で、Kubernetes上でオペレータを使って運用する事例もあります。ただし単体の docker compose 構成では、次の責務が自分側に残ります。
- データの永続化とストレージの耐久性(
fsyncが保証されるボリュームか) - バックアップとPITR(WALアーカイブの保存先と復旧テスト)
- 自動フェイルオーバーと接続先の切り替え
- マイナーバージョン更新の適用手順
これらを自前で設計・検証できる体制がないなら、Amazon RDS / Aurora のようなマネージドサービスか、CloudNativePGなどのオペレータを使うほうが運用リスクは低くなります。判断基準は、可用性とバックアップの要件を満たす仕組みを用意できるかどうか。開発・CI・検証用途なら、環境を揃えられる点でコンテナが有利です。
コンテナ内のPostgreSQLをバックアップするにはどうすればいい?
論理バックアップは docker compose exec で取れます。リダイレクトはホスト側で行うのが安全です。
# 単一データベース(カスタム形式。並列リストアが可能)
docker compose exec -T db pg_dump -U postgres -Fc app > app_$(date +%Y%m%d).dump
# クラスタ全体(ロールや権限も含む)
docker compose exec -T db pg_dumpall -U postgres > cluster_$(date +%Y%m%d).sql
ボリュームをそのままアーカイブする物理バックアップは、コンテナを停止してから行ってください。起動中のデータディレクトリを単純コピーすると、前述の incorrect checksum in control file のような不整合を招きます。
docker compose stop db
docker run --rm -v pgdata:/data:ro -v "$PWD":/backup alpine \
tar czf /backup/pgdata.tar.gz -C /data .
docker compose start db
停止できない場合は pg_basebackup を使います。WALアーカイブと組み合わせればPITRも可能ですが、アーカイブ先をコンテナ外の永続ストレージに置き、リストア手順を実際に試しておくことが条件です。レプリケーションスロットを作ったまま放置するとWALが溜まり続けてディスクを使い切るので、PostgreSQL WAL肥大化の原因|レプリケーションスロット も併せて確認してください。
再発防止:壊れにくいPostgreSQLコンテナ構成と次に学ぶこと
ここまでの対処を踏まえた、開発環境向けの構成例です。
services:
db:
image: postgres:17.6 # パッチバージョンまで固定する
shm_size: 1gb
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?required}
POSTGRES_DB: app
TZ: Asia/Tokyo
# ソート順・文字種の既定を明示(初回初期化時のみ有効)
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=C"
volumes:
- pgdata:/var/lib/postgresql/data # データは名前付きボリューム
- ./initdb:/docker-entrypoint-initdb.d:ro # 初期化SQLはバインドマウント
ports:
- "127.0.0.1:55432:5432" # ホストのループバックにのみ公開
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app -d app"]
interval: 5s
timeout: 3s
retries: 10
start_period: 30s
command:
- postgres
- -c
- max_wal_size=2GB
- -c
- log_min_duration_statement=500ms
- -c
- log_checkpoints=on
app:
build: .
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/app # ホストはサービス名
volumes:
pdata:
設計上のポイントを整理します。タグはパッチバージョンまで固定してください。postgres:latest や postgres:17 のままだと、docker compose pull のタイミングで予期せずバージョンが上がり、互換性エラーや挙動変化の原因になります。データは名前付きボリューム、設定や初期化SQLはバインドマウントと役割を分けると、消していいものが明確になります。
公開ポートには 127.0.0.1: を付けます。これがないと、環境によってはLAN全体に5432が開きます。depends_on は condition: service_healthy まで書いてください。既定の depends_on はコンテナの起動までしか待たないため、アプリ側の初回接続が Connection refused で落ちます。ヘルスチェックが初期化中の一時サーバーに反応することもあるので、アプリ側にもリトライを実装しておくと安全です。
log_min_duration_statement と log_checkpoints を有効にすると、遅いクエリやチェックポイント頻度が docker compose logs から読め、切り分けが楽になります。
コンテナ化で解決できるのは環境を揃える部分までです。起動しない、遅い、データが破損するといった問題は、PostgreSQL本体の仕組み(データディレクトリとWAL、チェックポイント、MVCCとVACUUM、実行計画)の理解に戻ってきます。エラーメッセージを都度検索して対処する段階から抜け出したい場合は、PostgreSQL本おすすめ|レベル別の読む順番 で内部構造と運用の学習順序を確認してみてください。