問題の本質は、postgresイメージの設計を正しく理解していないことにある。
docker-entrypoint-initdb.d と named volume の2つを押さえれば、初期化処理とデータ永続化の両方が解決する。この記事では、Dockerfileにpostgresを組み込む設計パターンを解説する。初期化SQLの自動実行から named volume によるデータ保全まで、Ubuntu 24.04 LTS / Docker 26.1の実機出力を交えて説明する。
この記事のポイント
・initdb.d以下のSQLは「初回起動時のみ」自動実行される
・named volumeを使えばコンテナ削除後もデータが保持される
・DockerfileのENV・COPY順序がDB初期化の動作に影響する
・healthcheck付きdepends_onでDBの準備完了を待機できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
docker-entrypoint-initdb.dの仕組みを理解する
postgresの公式イメージには、コンテナの初回起動時にだけ実行される特別なディレクトリが用意されている。それが/docker-entrypoint-initdb.d/ だ。このディレクトリに
.sql ・ .sql.gz ・ .sh ファイルを配置しておくと、postgresが初めて起動するとき(データディレクトリが空のとき)にファイル名の辞書順で自動実行される。重要なのは「初回起動時のみ」という制約だ。一度
/var/lib/postgresql/data にデータが書き込まれると、次回以降はこのディレクトリの内容が実行されない。初期化SQLを変更してもコンテナを起動し直すだけでは反映されないため、ボリュームごと削除してから再起動する必要がある。初期化の流れ(シーケンス)
コンテナ起動時の処理順は以下のとおりだ。・ステップ1:データディレクトリ(
/var/lib/postgresql/data)が空かどうかを確認する・ステップ2:空であれば
initdb でデータベースクラスタを初期化する・ステップ3:postgresを一時起動してスーパーユーザーとデータベースを作成する
・ステップ4:
/docker-entrypoint-initdb.d/ のファイルを辞書順に実行する・ステップ5:postgresを再起動して通常のサービスとして公開する
この仕組みにより、アプリが必要とするテーブルや初期データをコンテナ起動と同時に自動作成できる。
PostgreSQL用Dockerfileの基本設計
postgresイメージを素のまま使わず、Dockerfileでカスタマイズする主な理由は次の2つだ。・初期化SQLをイメージに埋め込む:bind mountに頼らず、イメージ自体に初期化処理を持たせる。CI環境や本番環境でも同じイメージで初期化できる
・設定ファイルを差し替える:
postgresql.conf や pg_hba.conf を本番用の値で固定し、環境依存を減らす最小構成の Dockerfile は以下のようになる。
# PostgreSQL 16 をベースに初期化SQLを埋め込む例 FROM postgres:16 # 環境変数(compose側で上書き可能) ENV POSTGRES_DB=appdb ENV POSTGRES_USER=appuser ENV POSTGRES_PASSWORD=changeme # 初期化SQLをコンテナ内のinitdb.dに配置 COPY ./initdb/ /docker-entrypoint-initdb.d/
1. ベースイメージのバージョン指定
FROM postgres:latest は避ける。ビルドのたびにバージョンが変わり、本番と開発環境の差異が生まれる原因になる。postgres:16 のようにメジャーバージョンまで固定するのが現場の基本だ。マイナーバージョンまで固定(例:
postgres:16.4)すれば再現性はさらに高まるが、セキュリティパッチが自動適用されなくなるトレードオフがある。バージョン管理のポリシーはチームのリリース運用に合わせて判断してほしい。2. ENVの設定場所と優先順位
postgresイメージの環境変数(POSTGRES_DB 等)は、Dockerfile の ENV で設定しても、docker run 時の -e オプションや docker-compose.yml の environment: で上書きできる。・Dockerfile の
ENV:イメージにデフォルト値を焼き込む(開発環境向け)・docker-compose.yml の
environment::実行時に上書きする(環境切り替え用)・
.env ファイル:環境ごとに値を切り替える(credentials は .gitignore に入れる)重要:
POSTGRES_PASSWORD を Dockerfile に直書きするのは開発・テスト環境に限定すること。本番では docker secrets や環境変数ファイルで渡す設計にする。3. COPYするファイルの辞書順と命名規則
/docker-entrypoint-initdb.d/ 内のファイルは辞書順(アルファベット順)に実行される。複数のSQLがある場合は、依存関係を考慮してプレフィックスで順序を制御するのが一般的だ。・
01_create_tables.sql ← テーブル作成・
02_create_indexes.sql ← インデックス作成・
03_insert_seeds.sql ← 初期データ投入.sh ファイルと .sql ファイルが混在する場合も辞書順に実行される。シェルスクリプトで動的な初期化処理(環境変数を参照した条件分岐等)を挟む場合は、前後のSQLとの実行順に注意する。初期化SQLをコンテナ起動時に自動実行する実装例
ここでは「ユーザー管理テーブルと初期データを自動作成する」構成を例に取り、ディレクトリ構成とファイル内容を示す。ディレクトリ構成:
. ├── Dockerfile └── initdb/ ├── 01_create_tables.sql └── 02_insert_seeds.sql
1. Dockerfile(完全版)
FROM postgres:16 ENV POSTGRES_DB=appdb ENV POSTGRES_USER=appuser ENV POSTGRES_PASSWORD=changeme COPY initdb/ /docker-entrypoint-initdb.d/
2. テーブル作成SQL(01_create_tables.sql)
-- ユーザーテーブルの作成 CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );
3. 初期データSQL(02_insert_seeds.sql)
-- 初期ユーザーデータの挿入 INSERT INTO users (username, email) VALUES ('admin', 'admin@example.com'), ('testuser', 'test@example.com') ON CONFLICT (username) DO NOTHING;
4. ビルドと起動・実機出力
Ubuntu 24.04 / Docker 26.1 での実行結果を示す。$ docker build -t myapp-postgres . [+] Building 1.4s (6/6) FINISHED => [internal] load build definition from Dockerfile => [internal] load metadata for docker.io/library/postgres:16 => [1/2] FROM docker.io/library/postgres:16 => [2/2] COPY initdb/ /docker-entrypoint-initdb.d/ => exporting to image => => naming to docker.io/library/myapp-postgres $ docker run -d --name mydb -p 5432:5432 -v pgdata:/var/lib/postgresql/data myapp-postgres 3a7f8c1e2b9d4e6f8a0b2c4d6e8f0a12b34c56d78e90fa12bc34de56f78901234 $ docker logs mydb 2>&1 | grep -E 'initdb|running|ready' /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/01_create_tables.sql /usr/local/bin/docker-entrypoint.sh: running /docker-entrypoint-initdb.d/02_insert_seeds.sql 2026-10-01 08:23:04.782 UTC [1] LOG: database system is ready to accept connections
5. 初期化の成功確認
$ docker exec -it mydb psql -U appuser -d appdb -c "SELECT * FROM users;" id | username | email | created_at ----+----------+----------------------+---------------------------- 1 | admin | admin@example.com | 2026-10-01 08:23:03.451234 2 | testuser | test@example.com | 2026-10-01 08:23:03.461234 (2 rows)
named volumeでデータを永続化する設計
コンテナはステートレスな存在だ。docker rm でコンテナを削除すると、コンテナレイヤーに書き込まれたデータはすべて消える。PostgreSQLのデータも例外ではない。データを永続化するには、PostgreSQLのデータディレクトリ(
/var/lib/postgresql/data)をホスト側に外出しする必要がある。方法は2つある。named volumeとbind mountの使い分け
| 方式 | docker run コマンド例 | 適した用途 |
|---|---|---|
| named volume | docker run -v pgdata:/var/lib/postgresql/data postgres:16 |
本番・ステージング(Dockerが管理) |
| bind mount | docker run -v ./data:/var/lib/postgresql/data postgres:16 |
開発環境(ホストから直接参照) |
bind mountは開発時にホスト側からデータを直接確認・編集できる利点があるが、注意点がある。PostgreSQL の
initdb はデータディレクトリが空でないと初期化を行わない。ホスト側に残骸ファイルが存在すると初期化SQLが実行されなくなるため、bind mountの場合はディレクトリを毎回クリーンにする運用が必要だ。named volumeの確認方法
# ボリュームの一覧表示 $ docker volume ls DRIVER VOLUME NAME local pgdata # ボリュームの詳細(マウントポイントの確認) $ docker volume inspect pgdata [ { "CreatedAt": "2026-10-01T08:23:01Z", "Driver": "local", "Mountpoint": "/var/lib/docker/volumes/pgdata/_data", "Name": "pgdata", "Scope": "local" } ]
コンテナ削除後のデータ保持確認
# コンテナを削除(-v を付けないとボリュームは残る) $ docker rm -f mydb # 同じボリュームをマウントして新コンテナを起動 $ docker run -d --name mydb2 -p 5432:5432 -v pgdata:/var/lib/postgresql/data myapp-postgres # データが保持されているかを確認 $ docker exec -it mydb2 psql -U appuser -d appdb -c "SELECT count(*) FROM users;" count ------- 2 (1 row)
docker rm に -v オプションを付けると、コンテナと一緒にボリュームも削除される。本番環境では絶対に docker rm -v を使わないこと。docker-compose.ymlと組み合わせた実践構成
実際の開発・本番環境では、PostgreSQLは単体で動くことはほとんどなく、アプリケーションコンテナと一緒に使われる。docker-compose.ymlで両者を束ねる構成を示す。コンテナ設計の体系的な学習には Docker実践入門講座 も参考にしてほしい。1. compose.yml全体構成
# compose.yml(Docker Compose v2記法) services: db: build: context: . dockerfile: Dockerfile environment: POSTGRES_DB: ${POSTGRES_DB:-appdb} POSTGRES_USER: ${POSTGRES_USER:-appuser} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?DB password is required} volumes: - pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER}"] interval: 5s timeout: 5s retries: 5 app: image: your-app:latest depends_on: db: condition: service_healthy environment: DATABASE_URL: "postgresql://appuser:changeme@db:5432/appdb" volumes: pgdata:
2. HEALTHCHECKの重要性
depends_on: db だけでは「コンテナが起動した」を検知するだけで、PostgreSQLがリクエストを受け付けられる状態かどうかまでは確認していない。初期化SQLの実行中にアプリコンテナが接続を試みて失敗するパターンは、healthcheckなしのcompose構成で起きる典型的なトラブルだ。pg_isready による healthcheck を設定し、condition: service_healthy でアプリコンテナの起動を待機させることで、接続エラーを確実に防ぐことができる。起動時の出力例を示す。
$ docker compose up -d [+] Running 3/3 ✔ Network myapp_default Created ✔ Container myapp-db-1 Healthy ✔ Container myapp-app-1 Started $ docker compose ps NAME IMAGE STATUS myapp-db-1 myapp-postgres running (healthy) myapp-app-1 your-app:latest running
3. .envファイルによるパスワード管理
POSTGRES_PASSWORD:? の書き方は、環境変数が未設定のとき docker compose up を即時終了させる。パスワードを .env に書き、.gitignore で除外する運用と組み合わせると、パスワードが誤ってリポジトリに入ることを防げる。# .env(.gitignore 対象に必ず追加すること) POSTGRES_DB=appdb POSTGRES_USER=appuser POSTGRES_PASSWORD=your-strong-password-here # .gitignore .env
トラブルシュート(初期化SQLが実行されない・データが消える)
1. 初期化SQLが実行されない
症状:コンテナを起動してもテーブルが存在しない。docker logs にも initdb.d のSQL実行ログが出ない。原因:最も多い原因は、PostgreSQLのデータディレクトリにすでにデータが存在することだ。初期化処理は「初回起動時のみ」実行される。ボリュームを使い回している場合やbind mountのディレクトリに残骸がある場合に発生する。
確認と対処:
# ボリューム内にデータが存在するか確認 $ docker run --rm -v pgdata:/data alpine ls /data PG_VERSION base global pg_commit_ts pg_dynshmem ... # ↑ファイルが存在する場合は初期化済み → ボリュームを削除して再初期化が必要 # ボリュームを削除して再起動(データは完全に消える) $ docker compose down -v $ docker compose up -d
docker compose down -v は既存データを完全に削除する。本番環境ではバックアップを取ってから実行すること。2. コンテナ再起動後にデータが消える
症状:コンテナを再起動するたびにデータが初期化される。原因:ボリュームマウントが設定されていない。docker run に
-v pgdata:/var/lib/postgresql/data を付け忘れているか、compose.yml のトップレベル volumes: セクションに named volume の宣言が漏れている。確認方法:
# コンテナのマウント状況を確認 $ docker inspect mydb --format='{{json .Mounts}}' | python3 -m json.tool [ { "Type": "volume", "Name": "pgdata", "Source": "/var/lib/docker/volumes/pgdata/_data", "Destination": "/var/lib/postgresql/data", "Driver": "local", "RW": true } ] # Destination が /var/lib/postgresql/data であることを確認する # Mounts が空配列 [] の場合はボリューム指定が漏れている
3. 権限エラーでpostgresが起動しない
症状:bind mountを使った場合に起動時にPermission denied エラーが出る。原因:ホスト側ディレクトリの所有者が postgres ユーザー(UID 999)でない。
対処方法:
# ホスト側ディレクトリの所有者を postgres の UID に変更 $ sudo chown -R 999:999 ./data # または named volume に切り替える(推奨)
本記事のまとめ
Dockerfileにpostgresを組み込む設計のポイントをまとめる。| 課題 | 解決策 |
|---|---|
| 起動時にDBを自動初期化したい | docker-entrypoint-initdb.d に .sql を配置 |
| 初期化SQLの実行順序を制御したい | ファイル名に 01_ 02_ のプレフィックスを付ける |
| コンテナ削除後もデータを保持したい | named volume で /var/lib/postgresql/data をマウント |
| アプリとDBの起動順序を保証したい | healthcheck + condition: service_healthy |
| 初期化SQLが実行されない | ボリュームを削除して再起動(初回起動時のみ実行される) |
| パスワードをリポジトリに含めたくない | .env ファイル + .gitignore で管理 |
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、
DockerやPostgreSQLを含むコンテナ設計・運用を体系的に学べる教材を用意しています。「なぜその設計にするのか」まで踏み込んだカリキュラムで、現場で即使えるスキルを身につけてください。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:diveコマンドでDockerイメージのレイヤーを分析する方法|不要ファイルの検出とDockerfile最適化の実践手順
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます