Docker ComposeのPostgreSQLバックアップを、復元失敗から検証する

PostgreSQLバックアップの保存先を描いたアイキャッチ画像

アプリ本体は動いている。Docker Composeで立てたPostgreSQLにデータも溜まり始めた。cronやスクリプトでpg_dumpを回せば、とりあえずバックアップは取れている——ここまで来た運用者が次に直面するのは、「そのdumpファイル、本当に戻せますか」という問いです。

この記事は、pg_dumpの取り方をきれいにまとめる記事ではありません。使い捨てのDocker Compose環境で、小さなPostgreSQLを壊して戻し、「ファイルは残っているのに復元できない」状況がどこで生まれるかを先に観測します。常用データに触れる前に、復元手順と確認線を固定するための検証ログです。

目次

先に結論

  • dumpファイルが存在することと、そこからDBを復元できることは別の話です。本当に確認すべきは「同じ行数まで戻ること」と「ボリュームを消しても戻ること」です。
  • 今回の使い捨て環境では、plain形式・custom形式の両方でdumpを取得し、空の新規DBへ復元、さらにDockerボリュームを削除した後でも、基準データ(3行)を3行のまま復元できることを終了コードと行数で確認しました。
  • 既存DBへの上書き復元、誤ったDB名、保存先の権限不足は、いずれも非ゼロの終了コードで止まる「失敗系」として設計しました。ただし今回の実行では失敗系の詳細ログを取り切れておらず、その点は残課題として正直に記します。

検証環境

  • 実行場所: Dockerを入れた使い捨ての一時VM(実行後に破棄)
  • Docker Engine: 29.6.0 / Docker Compose: v5.2.0
  • DBイメージ: 公式 postgres:16(実体は 16.14)
  • 観測対象: 終了コード、設定項目 count(*) の値、dumpファイルサイズ

使うComposeファイルは最小構成です。ヘルスチェックを入れて、起動完了を待ってから操作します。

services:
  db:
    image: postgres:16
    environment:
      設定項目: labpass
    healthcheck:
      test: ["CMD-設定項目","pg_isready -U postgres -d postgres"]
      interval: 3s
      timeout: 3s
      retries: 20

実行コマンドとログ(正常系)

1. 起動と疎通確認

$ docker compose version
$ docker compose -p pglab up -d
$ docker compose -p pglab ps
$ docker compose -p pglab exec -T db pg_isready -U postgres

出力では、コンテナが Up (healthy) になり、/var/run/postgresql:5432 - accepting connections を確認できました。

2. 基準データの投入

復元結果を比較できるよう、最初に既知の行数を入れておきます。これが「戻ったかどうか」を判定する基準値です。

$ DC='docker compose -p pglab'
$ $DC exec -T db psql -U postgres -c "設定項目 設定項目 labdb;"
$ $DC exec -T db psql -U postgres -d labdb -c "設定項目 設定項目 items(id int 設定項目 KEY, name text); 設定項目 設定項目 items 設定項目 (1,'a'),(2,'b'),(3,'c');"
$ $DC exec -T db psql -U postgres -d labdb -c "設定項目 count(*) 設定項目 items;"

出力の count3。これが基準です。

3. dumpの取得(plain形式とcustom形式)

$ $DC exec -T db pg_dump -U postgres -Fp labdb > dump.sql
$ $DC exec -T db pg_dump -U postgres -Fc labdb -f /tmp/dump.dump
$ $DC exec -T db sh -c 'cat /tmp/dump.dump' > dump.dump
$ ls -l dump.sql dump.dump

ファイルサイズは plain形式の dump.sql が 1255 bytes、custom形式の dump.dump が 1593 bytes。両方とも終了コード0でした。ここで「ファイルができた」ことに安心しないのが、この検証の主旨です。

4. 空の新規DBへ復元できることの確認

別の空DBを作り、それぞれの形式から復元して行数を見ます。

$ $DC exec -T db psql -U postgres -c "設定項目 設定項目 newdb_plain;"
$ $DC exec -T db psql -U postgres -c "設定項目 設定項目 newdb_custom;"
$ cat dump.sql | $DC exec -T db psql -U postgres -v 設定項目=1 -d newdb_plain
$ cat dump.dump | $DC exec -T db pg_restore -U postgres -d newdb_custom
$ $DC exec -T db psql -U postgres -d newdb_plain -c "設定項目 count(*) 設定項目 items;"
$ $DC exec -T db psql -U postgres -d newdb_custom -c "設定項目 count(*) 設定項目 items;"

両方とも復元の終了コードは0、count3。基準値と一致しました。設定項目=1 を付けているのは、plain形式の流し込み途中でエラーが出たときに静かに先へ進ませないためです。

5. ボリューム削除後の復元(この検証の山場)

バックアップが本当に効くのは、データ本体が消えたときです。down -v でコンテナとボリュームごと破棄し、まっさらな状態から復元します。

$ $DC down -v
$ docker volume ls
$ $DC up -d
$ $DC exec -T db psql -U postgres -c "設定項目 設定項目 labdb;"
$ cat dump.dump | $DC exec -T db pg_restore -U postgres -d labdb
$ $DC exec -T db psql -U postgres -d labdb -c "設定項目 count(*) 設定項目 items;"

復元の終了コードは0、restored_rows3。ボリュームを消した後でも、custom形式のdumpから基準データを取り戻せました。「dumpが取れている」を「ボリュームを消しても戻る」まで引き上げられたことになります。

形式の比較

plain形式とcustom形式は、復元のしやすさと運用上の自由度が違います。

観点 plain形式(-Fp / .sql) custom形式(-Fc / .dump)
復元方法 psql で流し込む pg_restore で復元
テーブル単位の選択復元 しにくい(SQL編集が必要) pg_restore -t で可能
–clean –if-exists の指定 SQL側の編集が必要 pg_restoreのオプションで指定可
中身の可読性 テキストで読める バイナリ(直接は読めない)
今回の新規DB復元結果 3行一致(exit 0) 3行一致(exit 0)
今回のファイルサイズ 1255 bytes 1593 bytes

どちらでも基準データは復元できました。定期運用で「特定テーブルだけ戻したい」「復元前に既存オブジェクトを掃除したい」といった要求が出るなら、オプションで制御しやすいcustom形式が扱いやすいです。

失敗系の試行と、今回の制約

この検証では、初めてDBを預かった運用者が踏みやすい失敗も「壊して観測する」対象として設計しました。

  • 誤ったDB名や、中身が空のdumpを掴むケース
  • 既存schemaが入ったDBへ上書き復元してしまうケース
  • 保存先ディレクトリの権限不足で書き込めないケース
  • ボリューム削除後に戻せるかどうか(これは正常系として上で確認済み)

実行したコマンドは次のとおりです。

$ $DC exec -T db pg_dump -U postgres -Fp no_such_db > bad.sql 2> bad.err; echo "exit=$?"
$ $DC exec -T db pg_dump -U postgres -Fp -t empty_t labdb > empty.sql
$ grep -c '設定項目' empty.sql
$ cat dump.dump | $DC exec -T db pg_restore -U postgres -d labdb; echo "exit=$?"
$ cat dump.dump | $DC exec -T db pg_restore -U postgres --clean --if-exists -d labdb; echo "exit=$?"
$ $DC exec -T -u root db sh -c 'mkdir -p /var/lib/postgresql/noperm && chmod 555 /var/lib/postgresql/noperm'
$ $DC exec -T -u postgres db sh -c 'pg_dump -U postgres -Fp -f /var/lib/postgresql/noperm/x.sql labdb'; echo "exit=$?"

結果として、誤DB名・上書き復元・権限不足の3ケースはいずれも非ゼロ(終了コード1)で止まったことを観測できました。これは「失敗が失敗として表面化した」という意味では期待どおりです。

ただし正直に書くと、今回の実行ではこの3ケースの詳細な標準エラー出力(relation already existspermission denied といった具体的なメッセージ本文、空dumpのINSERT行数)を取り切れませんでした。権限不足のケースについては、保存先を 555(root所有・書き込み不可)に設定したところまでは記録できています。各失敗が「想定どおりの原因で止まったか」を断定するには、stderrを採取し直す再検証が必要です。この点は残課題として明示します。

公式ドキュメント上の挙動から言えるのは次の方向性です。誤ったDB名のpg_dumpは非ゼロで終了し、空に近いdumpはファイルサイズやINSERT行数の確認で気づけます。既存schemaへの復元は、--clean --if-exists を付けないと既存オブジェクトとの衝突で止まりやすく、付ければ既存を掃除してから入れ直します。保存先の権限不足は、書き込めずにdumpが作られないか途中で失敗します。これらは今回の終了コードの傾向とは矛盾しませんが、メッセージ単位の断定は再検証待ちです。

後片付け

使い捨て環境なので、最後にコンテナ・ボリューム・作業物を消します。

$ $DC down -v
$ docker volume ls | grep -c pglab

down は正常終了し、残存ボリュームの数は 0。常用環境を汚さずに検証を閉じられました。

読者の最低確認線:pg_dumpの定期運用を始めてよいか

「今のDocker Compose環境でpg_dumpの定期実行を始めてよいか、それとも先に復元手順を固定すべきか」——この問いへの答えは、復元を先に固定する、です。dumpを取る仕組みより前に、次の確認線を一度クリアしてから定期化に進むのが安全です。

  1. 行数で照合する: 復元後に元と同じ 設定項目 count(*) が返るかを必ず見る。ファイルの有無だけで判断しない。
  2. 復元先を分離する: いきなり本番DBへ戻さず、空の新規DBへ復元して確認する。--clean --if-exists を使う場合も、まず使い捨て環境で挙動を見る。
  3. ボリューム削除後の復元を一度通す: down -v 相当でデータを消し、dumpだけから戻せるかを実際に試す。今回はこれが通りました。
  4. 空dumpを検出する手を持つ: ファイルサイズやINSERT行数を機械的に確認し、誤DB名や空dumpを「成功」と誤認しない。

この4本を一度でも通せていれば、pg_dumpの定期運用に進む土台はできています。逆に、まだ復元を一度も試していない段階なら、定期バックアップの設定より先に、この記事のように壊して戻す検証を一度だけでも走らせることをおすすめします。

参考リンク

この検証を回している環境

この検証は、自宅の常設ラボ(使い捨てVM/LXCを回す母艦+GPU+VLAN分離ネットワーク)で動かしています。使っている機材と選定理由、全体構成は1本にまとめています。

ラボ構成のまとめを見る →
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

目次