サーバー ソフトウェア セキュリティ 公開日 2026.11.01 更新日 2026.11.01

Docker の PostgreSQL は初期化中だけ TCP で繋がらない?pg_isready が通っても復元が途切れる理由

DB のバックアップを実際に戻して確かめる自動の訓練が、「失敗」を出しました。ダンプは壊れておらず、原因は訓練の側でした。公式の PostgreSQL イメージは、初期化の途中だけ UNIX ソケットでしか受けない一時サーバーを立てるため、ホストを指定しない pg_isready が通ってしまい、復元の途中で切り替えに巻き込まれます。手元で再現した結果と、TCP で待つ直し方、訓練の誤報を減らす考え方を整理しました。

目次(8項目)

先に要点

  • バックアップを実際に別の 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 の初期化ファイルを実行します。このとき、起動スクリプトは次の順で動きます。

公式イメージの初回起動の順序(起動スクリプトより)

  1. 1
    一時サーバーを起動する

    listen_addresses を空にして、UNIX ソケットだけで受ける。外部の TCP は受けない。

  2. 2
    データベースを作る

    POSTGRES_DB が指定されていれば、このサーバーで作る。

  3. 3
    初期化ファイルを順に実行する

    /docker-entrypoint-initdb.d の *.sql や *.sh。ここは一時サーバーに対して動く。

  4. 4
    一時サーバーを止める

    pg_ctl を fast モードで停止する。接続中のクライアントは強制的に切られる。

  5. 5
    本番用のサーバーに入れ替わる

    exec で本来のサーバー(postgres)に置き換わり、TCP でも受け始める。

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 の決め方でまとめています。

参考にした情報

あとで見返すならここで保存

読み終わったあとに残しておきたい記事は、お気に入りからまとめて辿れます。