第09章:初期化の王道:docker-entrypoint-initdb.d でDBに種を入れる🌱
この章は「DBコンテナを初回起動した瞬間に、スキーマ作成+初期データ投入まで終わってる状態」を作ります✨
(やることはシンプル:init/ にSQLやシェルを置くだけ!)
1) まず“仕組み”を1分で理解しよう🧠💡
Postgres公式イメージは、初回起動でデータディレクトリが空のときだけ👇をやります。
-
initdb(DBの初期作成) -
/docker-entrypoint-initdb.d配下のファイルを実行*.sqlを実行*.sql.gzも実行*.shは 実行可能なら実行/実行不可なら source(読み込み実行)
-
その後、Postgres本体を起動🚀
しかも、**実行順はファイル名のソート順(ロケール基準)**なので、00_ とか 10_ とか付けると事故りにくいです。(Docker Hub)
超重要:2回目以降は「データが既にある」扱いになり、初期化スクリプトは基本動きません。(Docker Hub) さらに、途中で初期化スクリプトが失敗して再起動されると「半端に初期化されたデータが残る→以後スクリプト続きが走らない」事故も起きがちです。(Docker Hub)
2) 2026年の注意点:Postgres 18から“データのマウント先”が変わった⚠️📦
2026/02/11時点の最新メジャーは PostgreSQL 18(2025/09/25リリース)です。(PostgreSQL)
そして **Postgres 18以降、デフォルトのPGDATAが「バージョン別のパス」**になりました。さらに DockerfileのVOLUMEも変更されています。(Docker Hub)
- 18の
PGDATA例:/var/lib/postgresql/18/docker - 18+ の
VOLUME:/var/lib/postgresql - なので、永続化用のvolumeは
/var/lib/postgresqlにマウントするのが安全💪(Docker Hub)
ここ、古い記事のまま /var/lib/postgresql/data にマウントして「なんか永続化されない…」ってなりがちなので、最初から最新の流儀でいきましょ😇
3) ハンズオン:最小構成で「初回起動で全部入る」を作る🧩✨
✅ フォルダ構成(これが“王道テンプレ”)
myapp/
compose.yml
init/
00_schema.sql
10_seed.sql
90_note.sql
README.md
✅ compose.yml(Postgres 18 + named volume + init)
services:
db:
image: postgres:18
ports:
- "5432:5432"
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: apppass
POSTGRES_DB: appdb
volumes:
# ✅ 永続化(Postgres 18+ はここが推奨)
- db-data:/var/lib/postgresql
# ✅ 初期化スクリプト置き場(ホスト→コンテナへ)
- ./init:/docker-entrypoint-initdb.d:ro
volumes:
db-data:
ポイント:Composeのトップレベルvolumes:で named volume を定義して、サービス側から参照します📦(Docker Documentation)
✅ 00_schema.sql(テーブル作成)
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
✅ 10_seed.sql(初期データ投入)
INSERT INTO users (name) VALUES
('Alice'),
('Bob'),
('Carol');
✅ 起動!
docker compose up -d
✅ 「初期化が走った証拠」をログで見る🔎
docker compose logs db -f
ログに /docker-entrypoint-initdb.d/... が出てたら勝ち🏆(実行順もファイル名順で出ます)(Docker Hub)
✅ DBを覗いて確認👀
docker compose exec db psql -U app -d appdb -c "SELECT * FROM users;"
4) ちょい上級:*.sh で「ユーザー作る」「複数DB作る」もできる🛠️😎
公式の例だと、*.shで psql を叩いてユーザーやDBを作るのも王道です。(Docker Hub)
例(20_init.sh):
#!/usr/bin/env bash
set -e
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
CREATE USER readonly;
GRANT CONNECT ON DATABASE appdb TO readonly;
EOSQL
*.sh は 実行可能なら実行、そうでなければ source されます。(Docker Hub)
(Windowsだと実行ビットが付かないこともあるので、sourceされても動く書き方に寄せるのがラク👍)
5) ありがちな罠10連発🪤😇(これだけで事故が激減する)
-
「2回目に動かない」 → 正常です。初回(データが空のとき)だけです。(Docker Hub)
-
初期化スクリプトが途中でコケたのに、再起動で続きが走らない → 公式が“よくある問題”として明記してます。(Docker Hub) 対策:まずログで失敗地点を潰す/必要ならボリュームを捨てて作り直す(次章でやるやつ💣)
-
Postgres 18なのに古いマウント先で永続化できてない → 18+ はマウント先の考え方が変わりました。
/var/lib/postgresqlへ。(Docker Hub) -
init/のファイル名の順番が悪くて、seedが先に走って死ぬ → 実行順は“ソート順”。00_10_みたいに番号付けで安定。(Docker Hub) -
.shが Windows の CRLF で死ぬ(bash\rとか) → VS Codeで改行をLFに、Gitもeol管理(.gitattributes)がおすすめ💡 -
SQLのどれかが失敗して止まってるのに気づかない →
.shでpsql -v ON_ERROR_STOP=1を使うと、失敗を即検知できて強い💪(Docker Hub) -
パスワード無しで繋がって「あれ?」 → コンテナ内部(Unix socket)だと
trustで繋がることがある、という注意が公式にあります。(Docker Hub) -
「マウントしたら中身が消えた?」 → bind mount は、コンテナ内の既存ファイルを“隠す”動きをします。(Docker Documentation) (
/docker-entrypoint-initdb.dなら問題になりにくいけど、別パスで事故りがち) -
秘密情報を init SQL に直書きしてしまう → それは危険😱 公式は
_FILEで secrets を読むやり方も案内してます。(Docker Hub) -
「初期化=migration」だと思ってしまう → 初期化は“最初の種”。変更履歴は migration で管理(次の章あたりに繋がるやつ)📜✨
6) 演習ミッション🎯(15分でできる)
ミッションA:順番事故をわざと起こす🧨→直す🧯
10_seed.sqlを00_seed.sqlにリネーム- 起動してログで失敗を確認
00_schema.sqlと10_seed.sqlに戻して成功させる → 「順番の大切さ」が体に染みます😂
ミッションB:sql.gz を使ってみる🗜️
10_seed.sqlを gzip して10_seed.sql.gzにする- 初回起動で入るのを確認 → 公式でサポートされてます。(Docker Hub)
7) AIの使いどころ🤖✨(危なくしない使い方)
- ✅ 「このテーブル定義で、最小のseedデータ案を5件作って」
- ✅ 「このDDL/seedを、初期化用途として安全にするチェックリスト出して」
- ✅ 「
docker-entrypoint-initdb.dが動かない時の原因候補を3つ、確認コマンド付きで」
⚠️ ただし、実プロダクトの秘密(本物のAPIキー/パスワード/顧客データ)を貼らないのは鉄則ね🔒
8) この章の成果物📦🎉
init/フォルダ(00_schema.sql+10_seed.sql+ README)compose.yml(Postgres 18 / 永続化 / initマウント)- 「初回だけ動く」「順番がある」「18+のマウント先が新しい」←この3点が腹落ち🧠✨
次の章(第10章)は、この章の続きで超あるあるな **「2回目に動かない罠」**を“わざと踏んで”、最短で直すリセット手順を作ります🪤➡️🧯
(down -v をどう安全に扱うか、がメイン!)