DockerでPostgreSQLを立てるだけなら、compose定義は十数行で済みます。詰まるのはその先です。18系からデータの置き場所そのものが移動したため、長年コピーされてきた/var/lib/postgresql/dataを指すボリューム定義は、18系のイメージでは狙った場所を指しません。しかもエラーは出ず、コンテナは正常に起動します。
この記事は、タグ選定からcompose定義、初期SQL、CI環境での削り方、17系ボリュームの引き上げまでを、2026年8月時点の公式イメージの実装から組み立てます。OSへ直接入れる手順はPostgreSQLのインストール手順|Windows・Ubuntu別の導入とinitdbに、compose記法そのものはdocker-composeとは?複数コンテナをymlで定義し一括管理する仕組みに譲ります。種類の選定から入りたい場合はデータベースとは?種類・DBMS・RDBとNoSQLの選び方をどうぞ。
まとめ:着手前に決め切る4点
- タグ:2026年8月26日時点の最新マイナーは18.6、19系はbeta3の段階です。本番と検証はマイナーまで固定し、基盤名も書きます
- ボリューム:18以降のVOLUME宣言は
/var/lib/postgresql、PGDATAは/var/lib/postgresql/18/dockerです。17以前の書き方のままだと名前付きボリュームが空回りします - 初期化:
docker-entrypoint-initdb.dはPGDATAが空のときだけ走ります。初期化中の一時サーバはソケットのみで待ち受け、TCPは開きません - 版上げ:18のinitdbはデータチェックサムが既定で有効です。無効で作られた17系のデータからpg_upgradeを走らせると設定不一致で止まります
公式イメージのタグ選定でDebian系とalpine系が分かれる判断基準
タグは、後から変えるコストが最も高い決定です。基盤が変わればロケールの扱いも変わるため、開発機とCIと本番で揃える前提で決めます。
イメージタグをメジャー固定とマイナー固定で使い分ける判断基準
Docker Hubのpostgres公式リポジトリを2026年8月26日に実測すると、latest、18.6、18、17.11、19beta3などが並んでいます。18のようなメジャータグは可動タグで、いまは18.6を指し、マイナーリリースが出れば黙って中身が入れ替わります。
線は用途で引きます。本番と検証はマイナーまで固定し、更新をイメージ差し替えという明示的な作業にしてください。開発機とCIはメジャー固定で構いません。基盤名も併記します。18-trixieはDebian 13向けの18.6-1.pgdg13+2、18-bookwormはDebian 12向けという別物で、latestは前者に紐づきます。稼働中のサーバが何系かはPostgreSQLのバージョン確認方法|サーバ・クライアント別コマンドとEOL判定で先に確認してください。
alpine系がmuslでLC_COLLATEを持たないことによるソート順の実害
alpine系はイメージサイズが小さく、CIの取得時間を削れます。代償は標準Cライブラリがglibcではなくmuslになる点です。muslはLC_COLLATEを実装しておらず、LANGを設定してもロケール順の照合が効かないため、文字列のソートはバイト順に落ちます。
見落とすと本番で表面化する差です。開発機のalpineでは名前順の一覧が想定どおりに見えていたのに、Debian系の本番では並びが変わります。回避するなら照合の担い手をlibcからICUへ移してください。15以降のalpine系イメージはICUロケールに対応しており、公式ドキュメントはPOSTGRES_INITDB_ARGS="--locale-provider=icu --icu-locale=de-DE"という指定例を挙げています。
18で移動したPGDATAとVOLUME宣言がcompose定義に与える差
本記事で最も実害の大きい論点です。ウェブ上のcompose定義の大半は17以前の前提で、そのまま18系へ差し替えると永続化が外れます。
17以前のdata直下指定と18以降の親ディレクトリ指定の書き分け
公式イメージのDockerfileを読むと、境界は明確です。17系はPGDATAを/var/lib/postgresql/dataに置き、同じパスをVOLUMEとして宣言しています。18系ではPGDATAが/var/lib/postgresql/18/dockerへ変わり、VOLUME宣言は親側へ上がりました。19系も同じ構造で、数字だけが19です。
| 項目 | 17以前 | 18以降 |
|---|---|---|
| PGDATAの既定値 | postgresql の data 直下 | postgresql の 18 配下 |
| VOLUME宣言の位置 | data ディレクトリ | postgresql 親側 |
| composeのマウント先 | data を指す | 親ディレクトリを指す |
| 変更のねらい | 従来構造の踏襲 | pg_upgrade の link 対応 |
18以降は、名前付きボリュームのマウント先を親ディレクトリ側にします。旧構造を使い続けたい事情があるなら、環境変数でPGDATAを明示して揃える逆方向の手もあります。ただし新規構築で寄せる理由は見当たりません。
匿名ボリューム化でデータが黙って消える失敗パターンの再現条件
再現条件は単純です。17系で動いていたcompose定義のイメージ行だけを18系へ書き換え、ボリュームの記述を触らない。これだけで起こります。
分解します。名前付きボリュームは指定どおりdata直下へ付きますが、18のPGDATAはそこではないので使われません。一方でVOLUME宣言により親側に匿名ボリュームが自動生成され、実データはそちらへ書き込まれます。コンテナは正常に起動し、警告も出ません。匿名ボリュームは削除時のオプションや掃除コマンドで消えるため、ある日の再作成でデータだけが失われます。名前付きボリュームは空のまま残り、原因追跡も遅れがちです。
バインドマウントを選ぶ場合に所有者と権限で詰まる箇所の潰し方
ホスト側のディレクトリを直接マウントすると、SQLや設定ファイルを手元で編集できます。詰まるのは所有者です。コンテナ内のpostgresユーザのUIDとホスト側の所有者が食い違うと、initdbが書き込みに失敗して起動が止まります。
compose側でユーザを指定して回避しようとすると、次の壁に当たります。initdbは実行ユーザがパスワードデータベースに存在することを要求するからです。対処はnss_wrapperを噛ませる、該当ファイルをバインドマウントする、初期化と実行を分けて所有者変更を挟む、の3通り。データ本体は名前付きボリュームへ預け、バインドマウントは初期化SQLと設定ファイルに限定してください。
compose最小定義と環境変数で決まる認証方式・文字コードの初期値
公式イメージの環境変数は、単なる設定項目ではありません。その多くは初回のinitdb実行時にしか効かず、後から変えるにはデータディレクトリの作り直しが要ります。
POSTGRES_PASSWORDとホスト認証方式が書き換えるpg_hbaの行
POSTGRES_PASSWORDは省略できません。空文字も未定義も拒否されます。POSTGRES_USERを指定すればその名前でスーパーユーザが作られ、同名のデータベースも生成されます。既定名だけを変えたいならPOSTGRES_DBを書いてください。
接続経路を決めるのはPOSTGRES_HOST_AUTH_METHODです。エントリポイントは全ホストを対象にした1行にこの値を差し込み、pg_hba.confへ追記します。既定は14以降でscram-sha-256。ここにtrustを入れると、パスワードを設定していても無認証で接続できる状態になり、公式ドキュメントも推奨していません。開発機でも既定のまま使ってください。
INITDB_ARGSでロケールとチェックサムを初期化時に決める手順
POSTGRES_INITDB_ARGSはinitdbへそのまま渡る引数です。文字コードと照合順序、データチェックサムの有無をここで確定させます。--encoding=UTF8 --no-localeという指定はよく見かけますが、照合順序をCロケールへ落とすため、日本語の並び順が要る要件では選べません。
18からはチェックサムの既定が変わりました。従来は--data-checksumsを明示しなければ無効でしたが、18では既定で有効になり、外すための--no-data-checksumsが新設されました。この既定値は後述する版上げの障害に直結するので、17系からの引き継ぎがある構成では最初に決めてください。
初期SQLをdocker-entrypoint-initdb.dへ載せる条件と実行順序
初期化スクリプトは便利ですが、走る条件が限定的です。「なぜか流れない」という相談の多くは、この条件を外しています。
初回起動でしか走らないという前提を崩す典型的な操作と再実行の方法
公式ドキュメントの記述は明快で、初期化スクリプトはデータディレクトリが空の状態でコンテナを起動したときにだけ実行されます。エントリポイントはPGDATA配下のバージョンファイルの有無でこれを判定しています。
典型的な失敗は、初期SQLを書き足してからコンテナだけを作り直すケースです。ボリュームにデータが残っているため初期化は走らず、追加したSQLは無視されます。再実行したいならボリュームごと破棄が必要で、composeなら削除時にボリューム削除オプションを付けてください。本番環境でこのオプションを打つ操作は取り返しがつきません。作られたはずのテーブルが実在するかはPostgreSQLのテーブル一覧を取得する方法|メタコマンドとinformation_schemaの手順で確かめられます。
sqlとshとsql.gzで分かれる扱いと実行順が決まる並び順の規則
エントリポイントの実装では、拡張子ごとに処理が分岐しています。.sqlはSQL処理系へ渡され、.sql.gzは展開してから同じ経路を通る形です。.shは実行権限があれば子プロセスとして実行され、無ければ現在のシェルにsourceされます。sourceされる側では環境変数の変更が後続へ引き継がれるため、この差は無視できません。
実行順はファイル名のソート順になります。依存関係のあるスクリプトを置くなら00_schema.sql、10_master.sqlのように数字を前置してください。任せきりにすると、テーブル作成より先に投入スクリプトが走る事故が起きます。
初期化中はlisten_addressesが空でTCPが開かない挙動の意味
エントリポイントは初期化用の一時サーバを起動する際、listen_addressesを空にする引数を付けます。TCPでは待ち受けず、Unixドメインソケットだけを開く状態です。初期化SQLが流れている間、外部からの接続は確立できません。
実務上の意味は2つあります。ひとつは安全側の設計で、初期化の途中でアプリケーションが中途半端なスキーマを読む事故を防いでいます。もうひとつは待ち合わせに効く点です。TCPで疎通を見ていれば初期化完了まで自然に失敗し続けるので、そのまま判定材料になります。コンテナ内部からソケット経由で見る方式では、この安全弁が働きません。
アプリコンテナとの待ち合わせ設計とCI用途で削り落とす設定の線引き
単体で起動できても、アプリケーションと組むと起動順の問題が出ます。CI環境では逆に、永続化まわりを削るほど速くなります。
healthcheckにpg_isreadyを置くときに誤検知が起きる条件
healthcheckにpg_isreadyを置く書き方は広く共有されています。注意したいのは、この判定がコンテナ内部で実行される事実です。初期化中の一時サーバはソケットを開いているため、内部から接続先を指定せずに問い合わせると、初期化SQLが流れている最中でも応答が返ります。healthyなのに接続できない状態です。
回避策は接続経路の明示です。pg_isready -h 127.0.0.1 -U postgres -d appdbのようにループバックアドレスとデータベース名を指定すれば、一時サーバの段階では失敗し、本サーバが待ち受けを始めてから成功へ変わります。start_periodは初期化SQLの所要時間より長めに置きます。
depends_onのservice_healthyで起動順を確定させる書き方
depends_onを名前だけで書くと、依存先のコンテナが起動したことしか保証されません。受け入れ準備までは待たない挙動です。条件付きの記法でcondition: service_healthyを指定して、はじめてhealthcheckの結果と連動します。
それでも待ち合わせはアプリ側にも持たせておくと安全です。接続が切れる場面は起動時だけではないため、接続リトライは実装側の責務として残します。なお現行のCompose仕様では先頭のversionキーが不要です。
CIでは永続化を捨ててtmpfsとfsync無効へ振る判断の条件
CIのテスト用データベースは、ジョブが終われば捨てます。永続化する理由がないので、PGDATAをtmpfsへ載せ、起動引数でfsync=offやsynchronous_commit=offを渡してください。ディスク同期を省くぶん、テストの実行時間が縮みます。同時実行下のUPSERTの挙動を手元で再現したいときも、この構成が使えます。書き方はPostgreSQLのUPSERT実装|ON CONFLICTの競合ターゲット指定とMERGE文の使い分けで整理しました。
持ち込んではいけない場面も明確です。性能検証には使えません。ディスク同期を切った数値は本番の応答時間と無関係だからです。開発機側の入出力が遅い場合は、ランタイム側の構成も見る価値があります。macOSならDocker VMMとは?4.86の自前ハイパーバイザと切り替え判断、デーモンの持ち方を変えたいならPodmanとdockerの違いは?デーモンレス・rootlessの仕組みと移行判断が判断材料です。
17系ボリュームを18系へ引き上げるときチェックサムが阻む条件
18への移行で最初に当たる壁は、パスではなくチェックサムです。手順を知らずに始めると、pg_upgradeが冒頭で止まります。
pg_upgradeが要求する新旧クラスタのチェックサム一致という壁
pg_upgradeは、移行元と移行先でデータチェックサムの設定が一致していることを求めます。17以前のinitdbは既定でチェックサムを作らないため、公式イメージで素直に作った17系のデータは無効です。対して18のinitdbは既定で有効になりました。この組み合わせでは、旧クラスタは使っていないが新クラスタは使っている、という趣旨のエラーで停止します。
18でPGDATAがメジャーバージョン別のパスへ移ったのは、この移行を扱いやすくするためでもあります。親側を丸ごとマウントすれば新旧のデータディレクトリが同一ファイルシステム上に並び、pg_upgradeのlinkモードが使えます。ハードリンクで済むため、大きなデータでも停止時間を抑えられるのが利点です。
no-data-checksumsで作り直す手順とdump経由へ切り替える分岐
解き方は2通りです。新しい側を旧に合わせるなら、POSTGRES_INITDB_ARGSに--no-data-checksumsを渡して18のクラスタを初期化し、pg_upgradeを通してから停止状態でpg_checksumsを使って有効化します。旧を新に合わせるなら、17系を停止してpg_checksumsで有効化し、設定値を確認してから上げてください。
データ量が小さく停止枠を取れるなら、pg_dumpとpg_restoreで移すほうが手順は短く、チェックサム設定の不一致も関係なくなります。目安を言い切ります。停止時間を数分に抑えたい、あるいはデータが数百GB規模ならpg_upgradeのlinkモード。停止枠が確保でき、数十GB以下ならdump経由。この2択のどちらで行くかを、移行計画の最初に決めてください。
本番環境でコンテナ版を採用してよい3条件とマネージドへ寄せる分岐
開発とCIでDockerのPostgreSQLを使うことに異論はありません。問題は本番です。条件を付けて言い切ります。
コンテナ版を本番で採用してよい3条件と条件を外れたときの扱い
採用してよいのは次の3条件をすべて満たす場合です。第一に、単一ノードで処理量が収まり、切り替えを伴う可用性構成が要らないこと。第二に、バックアップとリストアを定期的に実地で検証する体制があること。第三に、イメージと基盤OSの更新をCIで当て直せること。
ひとつでも外れるなら、コンテナ版の本番採用は見送ります。とくに多いのが第二の条件の未達です。取得しているだけで戻せない構成は、無いのと同じ扱いになります。ボリュームをスナップショットしているという説明も、リストア手順の検証記録が無ければ根拠になりません。ホスト上へ直接入れる形との比較はPostgreSQLのインストール手順|ネイティブ導入を選ぶ3条件で整理しています。
バックアップとHAの自前運用が重くなりマネージドへ寄せる場面
コンテナ版で本番を組むと、WALアーカイブ、待機系への複製、フェールオーバーの判定、監視とアラートを自前で設計することになります。版上げのたびに検証し直す対象です。運用担当が専任でないなら、この負担は可搬性の利点を上回ります。
寄せ先の候補は、エンジンを変えずに移せるマネージドサービスです。AWSならAmazon RDS for PostgreSQLとは?マルチAZ構成と拡張機能の統制が標準的な選択で、待機系の読み取りまで要るならAurora PostgreSQLとは?対応バージョン・拡張機能とBabelfishが候補です。移行経路の設計や拡張機能の対応可否の洗い出しまで外部に任せたい場合は、AWS・Google Cloud・Azureのインフラ構築支援で現行構成の棚卸しから相談できます。
よくある質問
DockerでPostgreSQLを構築するときに検索されている質問へ、公式イメージの実装に基づいて答えます。
docker composeでPostgreSQLのバージョンを上げるにはどうすればよいですか?
タグを書き換えるだけでは上がりません。データディレクトリの形式はメジャーバージョンごとに異なるため、新しいイメージは古いデータを読めずに起動へ失敗します。マイナー更新ならタグ差し替えと再起動で完結しますが、メジャーをまたぐならpg_upgradeか、pg_dumpで論理バックアップを取って新クラスタへ流し込む手順が要ります。17系から18系ならチェックサムの既定値も確認してください。
コンテナを作り直すとデータが消えてしまうのはなぜですか?
マウント先とPGDATAが一致していない可能性が高いと考えられます。18以降の公式イメージはPGDATAが/var/lib/postgresql/18/dockerにあり、VOLUME宣言は親側です。17以前の定型どおりdata直下へ名前付きボリュームを当てていると、実データは匿名ボリューム側へ書かれます。名前付きボリュームが空かどうかを見れば判別できます。
初期化SQLが実行されないときはどこを見ればよいですか?
まずデータディレクトリが空だったかを確認します。初期化スクリプトはPGDATAが空の状態でのみ実行される仕様で、既存データが残っていれば何も起こりません。次に拡張子とファイル名を見てください。処理対象は.sql、.sql.gz、.shで、実行順はファイル名順です。マウント先と読み取り権限も確かめます。
alpine版とDebian版はどちらを選べばよいですか?
照合順序を気にする用途ならDebian系を選んでください。alpine系の標準CライブラリであるmuslはLC_COLLATEに対応しておらず、文字列のソートがバイト順になるため、日本語を含む並び順が本番と食い違う可能性があります。ICUロケールを指定して照合の担い手を移す手もありますが、初期化時の引数を揃える手間が増えます。サイズを削る必要が明確でなければ、Debian系で揃えるほうが事故は少ないでしょう。
ホストのpsqlからコンテナのPostgreSQLへ接続できないのはなぜですか?
層を分けて切り分けます。コンテナが起動しているか、公開ポートがホスト側へ割り当てられているか、同じポートを既存のPostgreSQLが占有していないか、の順に確認してください。起動直後なら、初期化中の一時サーバがlisten_addressesを空にして待ち受けているため、TCPはまだ開いていません。ログで完了行を見てから接続します。
関連記事
- PostgreSQLのインストール手順|Windows・Ubuntu別の導入とinitdb:OSへ直接入れる場合の手順
- docker-composeとは?複数コンテナをymlで定義し一括管理する仕組み:compose記法そのものの基礎
- DockerでMySQLを構築する手順|latestが26系へ変わったタグ選定とcompose定義:MySQL側の同型手順とタグ選定
- PostgreSQLのバージョン確認方法|コマンドとEOL判定・更新計画:稼働中のサーバの版を調べる手順
- Amazon RDS for PostgreSQLとは?マルチAZ構成と拡張機能の統制:マネージドへ寄せる移行先
- データベースとは?種類・DBMS・RDBとNoSQLの選び方:種類の選定から整理したい場合