先に要点
- バックアップを実際に別の DB へ戻して確かめる自動の訓練が「失敗」と出ても、ダンプが壊れているとは限りません。訓練の側の作りが原因のことがあります。
- Docker の公式 PostgreSQL イメージは、初期化の途中だけ、UNIX ソケットでしか受けない一時サーバーを立てます。そのため、ホストを指定しない
pg_isreadyは、一時サーバーに対して「接続を受け付けている」と答えます。 - その時点で復元を始めると、初期化の終わりの一時サーバーの停止で接続が切られ、復元が途中で止まります。
- 直し方は、待ちの確認をTCP(
-h 127.0.0.1)で行うことです。一時サーバーは TCP を受けないので、通ったときは本番用のサーバーが立っています。 - 訓練が誤って「失敗」を出し続けると、本物の失敗も見逃されます。失敗の種類(訓練の誤りか、復元の失敗か)を分けて記録します。
Docker で PostgreSQL を動かし、バックアップの復元を自動で確かめている方向けの記事です。「取れている」と「戻せる」は別なので、バックアップは実際に戻して確かめるのが確実です(バックアップの世代と復元)。その訓練が、ダンプではなく訓練そのものの作りで失敗する例を扱います。
この記事は、Docker の公式 PostgreSQL イメージの起動スクリプトと説明(Docker Hub)、PostgreSQL の公式文書をもとに、PostgreSQL 17 の公式イメージを手元で動かして確かめています(2026年11月1日)。
何が起きるのか:初期化の途中に立つ「一時サーバー」
公式イメージは、初めて起動してデータが空のとき、/docker-entrypoint-initdb.d の初期化ファイルを実行します。このとき、起動スクリプトは次の順で動きます。
公式イメージの初回起動の順序(起動スクリプトより)
Docker Hub の説明も、初期化用の一時サーバーはUNIX ソケットだけで受けるので、初期化のスクリプトでは psql のホストを省くように案内しています。
手元で確かめた:待ち方で結果が変わる
初期化に25秒かかる初期化ファイルを置いた PostgreSQL 17 の公式イメージで、起動の8秒後と、初期化の完了後に pg_isready を実行した結果です。
| 実行 | 初期化の途中 | 初期化の完了後 |
|---|---|---|
pg_isready(ホスト指定なし) |
接続を受け付けている(終了コード 0) | 接続を受け付けている(0) |
pg_isready -h 127.0.0.1 |
応答なし(終了コード 2) | 接続を受け付けている(0) |
ホストを指定しない場合、接続先は UNIX ソケットです(libpq の既定。PostgreSQL の文書)。初期化の途中でプロセスの一覧を見ると、postgres -D … -c listen_addresses= -p 5432 が動いていて、TCP では受けていません。どちらも「サーバーが動いている」という意味では正しい答えですが、「本番用のサーバーが立った」とは別の意味です。
同じ状態で、別のセッションから長い問い合わせ(select pg_sleep(60))を流すと、初期化の終わりの停止で FATAL: terminating connection due to administrator command となって切れました。pg_ctl の fast モードは「クライアントの切断を待たず、実行中のトランザクションを取り消し、強制的に切断する」と説明されています。復元の途中なら、復元が失敗として終わります。
つまり、待ちの確認が「ソケット経由の pg_isready」だと、初期化が終わる前に復元が始まり、入れ替わりのところで切られることになります。ダンプは壊れていないので、手で戻すと成功します。
直し方:待ちの確認を TCP にする
待つ側を、TCP で確かめる形に変えます。
# 本番用のサーバーが立つまで待つ(TCP で確認する)
until pg_isready -h 127.0.0.1 -q; do sleep 1; done
# 通ってから復元を始める
pg_restore -h 127.0.0.1 -U postgres -d postgres dump.custom
Docker Compose の healthcheck でも同じです。
healthcheck:
test: ["CMD-SHELL", "pg_isready -h 127.0.0.1 -U postgres"]
interval: 2s
timeout: 3s
retries: 30
なぜ TCP で直るのか
一時サーバーは TCP を受けないので、-h 127.0.0.1 で通ったときは、入れ替わりが終わったあとです。
初期化ファイルの側
初期化ファイルの中の psql は、一時サーバーに対して動くので、ホストを指定しません(Docker Hub の案内)。待つ側と書く側で、ホストの指定が逆になります。
pg_isready の意味
PostgreSQL の文書のとおり、接続を受け付けているかだけを見ます。特定のデータベースが使えるかは見ないので、復元の前に psql -c "select 1" も通すと確実です。
もう一つの誤報:何も入っていないダンプを「失敗」と数える
訓練の誤報は、待ち方だけではありません。データが何も入っていない DB のダンプを戻して、「DB が0個だった=復元に失敗」と数えてしまう形も起こります。新しく作ったばかりで、まだ中身がない DB は、復元しても何も増えません。これは復元の失敗ではなく、「空だった」という事実です。
ダンプの中身を先に見る
カスタム形式なら pg_restore --list dump.custom、プレーンな SQL なら grep -c "^COPY " dump.sql で、表のデータが入っているかを確かめます。pg_dump の形式やオプションによって、CREATE DATABASE を含むかどうかは変わります。
「空」を別の結果にする
表のデータも DB の作成も含まないものは、失敗ではなく「空」として記録だけします。成功と失敗の二択にしないのが要点です。
訓練の誤報は、本物の失敗より害になる
復元の訓練が「失敗」を誤って出し続けると、訓練を信じなくなります。そうなると、本物の失敗が出たときも「また誤報だろう」と読み飛ばされます。バックアップの確認は、手間をかけるぶん、結果の信頼が大切です。
| 失敗の種類 | 原因 | 対応 |
|---|---|---|
| 訓練の側の誤り | 待ち方、環境の準備、空の DB の扱い | 訓練の作りを直す。復元の失敗とは別に記録する |
| 復元の失敗 | ダンプの破損、版の食い違い、容量不足 | バックアップの取り方を直す |
| 空の入力 | 中身のない DB のダンプ | 記録だけして、失敗に数えない |
失敗が出たら、手で同じダンプを戻してみます。手で成功するなら、訓練の側を疑います。
まとめ
待ちは TCP で
公式イメージでは、初期化中の一時サーバーにソケット経由の確認が通ってしまうので、-h 127.0.0.1 を付けます。
手で再現する
失敗が出たら、手で戻してダンプの問題か訓練の問題かを切り分けます。
結果を分ける
訓練の誤り・復元の失敗・空の入力を、別の結果として記録します。
バックアップの対象の考え方は VPS のバックアップで何を保存するか、復旧の目標は RTO・RPO の決め方でまとめています。