---
title: "Docker PostgreSQL構築の手順｜18で変わったボリューム位置とcompose定義・initdb初期化を実装目線で解説"
url: "https://www.issoh.co.jp/tech/details/16976/"
published: 2026-08-26
updated: 2026-09-16
categories: ["データベース"]
publisher: "株式会社一創"
---

# Docker PostgreSQL構築の手順｜18で変わったボリューム位置とcompose定義・initdb初期化を実装目線で解説

DockerでPostgreSQLを立てるだけなら、compose定義は十数行で済みます。詰まるのはその先です。18系からデータの置き場所そのものが移動したため、長年コピーされてきた`/var/lib/postgresql/data`を指すボリューム定義は、18系のイメージでは狙った場所を指しません。しかもエラーは出ず、コンテナは正常に起動します。

この記事は、タグ選定からcompose定義、初期SQL、CI環境での削り方、17系ボリュームの引き上げまでを、2026年8月時点の公式イメージの実装から組み立てます。OSへ直接入れる手順は[PostgreSQLのインストール手順｜Windows・Ubuntu別の導入とinitdb](https://www.issoh.co.jp/tech/details/16963/)に、compose記法そのものは[docker-composeとは？複数コンテナをymlで定義し一括管理する仕組み](https://www.issoh.co.jp/tech/details/13282/)に譲ります。種類の選定から入りたい場合は[データベースとは？種類・DBMS・RDBとNoSQLの選び方](https://www.issoh.co.jp/tech/details/13012/)をどうぞ。

## まとめ：着手前に決め切る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判定](https://www.issoh.co.jp/tech/details/16965/)で先に確認してください。

### 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](https://www.issoh.co.jp/tech/details/16966/)の手順で確かめられます。

### 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文の使い分け](https://www.issoh.co.jp/tech/details/16978/)で整理しました。

持ち込んではいけない場面も明確です。性能検証には使えません。ディスク同期を切った数値は本番の応答時間と無関係だからです。開発機側の入出力が遅い場合は、ランタイム側の構成も見る価値があります。macOSなら[Docker VMMとは？4.86の自前ハイパーバイザと切り替え判断](https://www.issoh.co.jp/tech/details/16896/)、デーモンの持ち方を変えたいなら[Podmanとdockerの違いは？デーモンレス・rootlessの仕組みと移行判断](https://www.issoh.co.jp/tech/details/13202/)が判断材料です。

## 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条件](https://www.issoh.co.jp/tech/details/16963/)で整理しています。

### バックアップとHAの自前運用が重くなりマネージドへ寄せる場面

コンテナ版で本番を組むと、WALアーカイブ、待機系への複製、フェールオーバーの判定、監視とアラートを自前で設計することになります。版上げのたびに検証し直す対象です。運用担当が専任でないなら、この負担は可搬性の利点を上回ります。

寄せ先の候補は、エンジンを変えずに移せるマネージドサービスです。AWSなら[Amazon RDS for PostgreSQLとは？マルチAZ構成と拡張機能の統制](https://www.issoh.co.jp/tech/details/16972/)が標準的な選択で、待機系の読み取りまで要るなら[Aurora PostgreSQLとは？対応バージョン・拡張機能とBabelfish](https://www.issoh.co.jp/tech/details/16970/)が候補です。移行経路の設計や拡張機能の対応可否の洗い出しまで外部に任せたい場合は、[AWS・Google Cloud・Azureのインフラ構築支援](https://www.issoh.co.jp/service/system/aws/)で現行構成の棚卸しから相談できます。

## よくある質問

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](https://www.issoh.co.jp/tech/details/16963/)：OSへ直接入れる場合の手順
- [docker-composeとは？複数コンテナをymlで定義し一括管理する仕組み](https://www.issoh.co.jp/tech/details/13282/)：compose記法そのものの基礎
- [DockerでMySQLを構築する手順｜latestが26系へ変わったタグ選定とcompose定義](https://www.issoh.co.jp/tech/details/17642/)：MySQL側の同型手順とタグ選定
- [PostgreSQLのバージョン確認方法｜コマンドとEOL判定・更新計画](https://www.issoh.co.jp/tech/details/16965/)：稼働中のサーバの版を調べる手順
- [Amazon RDS for PostgreSQLとは？マルチAZ構成と拡張機能の統制](https://www.issoh.co.jp/tech/details/16972/)：マネージドへ寄せる移行先
- [データベースとは？種類・DBMS・RDBとNoSQLの選び方](https://www.issoh.co.jp/tech/details/13012/)：種類の選定から整理したい場合

---

出典: [Docker PostgreSQL構築の手順｜18で変わったボリューム位置とcompose定義・initdb初期化を実装目線で解説](<https://www.issoh.co.jp/tech/details/16976/>)（株式会社一創）
