🌾

Flyway + PostgreSQL 18 を Dev Container で再現する最小構成

に公開
2026/02/13

1. はじめに

DB スキーマ変更は、チーム開発で最も「環境差分」が出やすい領域です。個々のローカルで手作業をすると、適用順や適用漏れが起きやすく、検証環境と同じ状態を再現するのも困難になります。

本記事では、Flyway + PostgreSQL 18 を Dev Container で最小構成化したサンプルをベースに、構成ファイルの役割と運用ポイントを整理します。

2. 全体像

構成はシンプルに 2 サービスのみです。

  • db: PostgreSQL 18
  • flyway: Flyway CLI コンテナ (マイグレーション実行)

VS Code の Dev Containers が flyway に接続します。学習用途のため、flyway migrate は手動実行します。

3. ディレクトリ構成

Flyway/
  .devcontainer/devcontainer.json
  compose.yaml
  flyway.conf
  sql/
    V1__create_schemas.sql
    V2__create_tables.sql
    V3__seed_data.sql
    V4__create_views.sql

4. devcontainer.json のポイント

devcontainer.json は「どのコンテナに VS Code で接続するか」を定義します。

{
  "name": "flyway-postgres18-dev",
  "dockerComposeFile": ["../compose.yaml"],
  "service": "flyway",
  "workspaceFolder": "/workspace",
  "remoteUser": "root"
}

ポイント:

  • service: "flyway" で Flyway コンテナへ接続。
  • flyway migrate は手動実行。意図的に postCreateCommand は設定しません。
  • workspaceFolder は compose.yaml の volume マウントと一致させることが重要。

5. compose.yaml のポイント

services:
  flyway:
    image: redgate/flyway:12.0.0
    entrypoint: ["/bin/sh", "-c", "sleep infinity"]
    environment:
      FLYWAY_CONFIG_FILES: /workspace/flyway.conf
    volumes:
      - .:/workspace:cached
    working_dir: /workspace
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    ports:
      - "15432:5432"
    volumes:
      - db-data:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d postgres"]
      interval: 5s
      timeout: 5s
      retries: 12

volumes:
  db-data:

解説:

  • entrypoint: ["/bin/sh", "-c", "sleep infinity"] で Flyway コンテナを常駐化。VS Code が接続し続けるための最小手段です。
  • FLYWAY_CONFIG_FILES を environment で固定し、flyway info など短縮コマンドを使えるようにします。
  • depends_on.condition: service_healthy により、DB が起動完了するまで Flyway 側の起動を待ちます。
  • ports: "15432:5432" はホストからの接続用。必要なときだけ利用すれば OK。
  • db-data ボリュームで DB データを永続化。コンテナ再作成でもデータが残ります。

healthcheck の意味

healthcheck は「DB が本当に利用可能か」を Docker が判定するための設定です。pg_isready で PostgreSQL の準備完了を確認しています。

  • test: コンテナ内で pg_isready -U postgres -d postgres を実行し、成功なら healthy とみなします。
  • interval: 5s: 5 秒ごとにチェックします。
  • timeout: 5s: 1 回のチェックが 5 秒以内に終わらなければ失敗扱いです。
  • retries: 12: 連続 12 回失敗すると unhealthy 判定になります。

これにより depends_on.condition: service_healthy が機能し、DB が起動完了してから Flyway を開始できます。

6. flyway.conf のポイント

flyway.url=jdbc:postgresql://db:5432/postgres
flyway.user=postgres
flyway.password=postgres
flyway.locations=filesystem:/workspace/sql
flyway.connectRetries=60

ポイント:

  • flyway.locations は SQL マイグレーションの配置先。
  • connectRetries=60 により、DB 起動直後の接続失敗に耐性を持たせています。
  • ここを環境変数に切り替えれば、CI や本番向けにも転用しやすくなります。

7. SQL マイグレーション構成

サンプルでは、スキーマ・テーブル・初期データ・ビューまでを段階的に作成します。

  • V1__create_schemas.sql: study_core / study_sales / study_audit を作成
  • V2__create_tables.sql: ユーザ・商品・注文・監査イベントなどのテーブル
  • V3__seed_data.sql: 学習用のサンプルデータ投入
  • V4__create_views.sql: 注文サマリビュー study_sales.order_summary

Flyway の命名規則は V<番号>__<説明>.sql です。番号の順序が適用順序になるため、ここはチーム内で明確にルール化しておくと安全です。

7.1 マイグレーション SQL(サンプル)

-- V1__create_schemas.sql
CREATE SCHEMA IF NOT EXISTS study_core;
CREATE SCHEMA IF NOT EXISTS study_sales;
CREATE SCHEMA IF NOT EXISTS study_audit;
-- V2__create_tables.sql
CREATE TABLE study_core.users (
  user_id BIGSERIAL PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  display_name TEXT NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE study_core.products (
  product_id BIGSERIAL PRIMARY KEY,
  sku TEXT NOT NULL UNIQUE,
  name TEXT NOT NULL,
  price_cents INTEGER NOT NULL CHECK (price_cents >= 0),
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE study_sales.orders (
  order_id BIGSERIAL PRIMARY KEY,
  user_id BIGINT NOT NULL REFERENCES study_core.users(user_id),
  order_status TEXT NOT NULL,
  ordered_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE TABLE study_sales.order_items (
  order_id BIGINT NOT NULL REFERENCES study_sales.orders(order_id),
  product_id BIGINT NOT NULL REFERENCES study_core.products(product_id),
  quantity INTEGER NOT NULL CHECK (quantity > 0),
  unit_price_cents INTEGER NOT NULL CHECK (unit_price_cents >= 0),
  PRIMARY KEY (order_id, product_id)
);

CREATE TABLE study_audit.events (
  event_id BIGSERIAL PRIMARY KEY,
  event_type TEXT NOT NULL,
  payload JSONB NOT NULL,
  created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- V3__seed_data.sql
INSERT INTO study_core.users (email, display_name) VALUES
  ('alice@example.com', 'Alice'),
  ('bob@example.com', 'Bob');

INSERT INTO study_core.products (sku, name, price_cents) VALUES
  ('SKU-001', 'Notebook', 1200),
  ('SKU-002', 'Pen', 300),
  ('SKU-003', 'Sticker', 150);

INSERT INTO study_sales.orders (user_id, order_status) VALUES
  (1, 'PAID'),
  (2, 'PENDING');

INSERT INTO study_sales.order_items (order_id, product_id, quantity, unit_price_cents) VALUES
  (1, 1, 1, 1200),
  (1, 2, 2, 300),
  (2, 3, 5, 150);

INSERT INTO study_audit.events (event_type, payload) VALUES
  ('order_created', '{"order_id": 1, "user_id": 1}'),
  ('order_created', '{"order_id": 2, "user_id": 2}');
-- V4__create_views.sql
CREATE VIEW study_sales.order_summary AS
SELECT
  o.order_id,
  u.display_name,
  o.order_status,
  o.ordered_at,
  SUM(oi.quantity * oi.unit_price_cents) AS total_cents
FROM study_sales.orders AS o
JOIN study_core.users AS u ON u.user_id = o.user_id
JOIN study_sales.order_items AS oi ON oi.order_id = o.order_id
GROUP BY
  o.order_id,
  u.display_name,
  o.order_status,
  o.ordered_at;

8. 起動手順 (VS Code)

  1. Infra/Database/Flyway を VS Code で開く
  2. Dev Containers: Reopen in Container を実行
  3. flyway migrate を手動で実行してスキーマを構築

状態確認(短縮版 / 環境変数設定済み):

flyway info
flyway validate

環境変数を設定していない場合(明示指定):

flyway -configFiles=/workspace/flyway.conf info
flyway -configFiles=/workspace/flyway.conf validate

8.1 -configFiles を省略したい場合

Dev Container 内で flyway info のように短縮したい場合は、設定ファイルのパスを環境変数で固定します。最も確実なのは compose.yaml の flyway.environment に FLYWAY_CONFIG_FILES を設定する方法です。

services:
  flyway:
    environment:
      FLYWAY_CONFIG_FILES: /workspace/flyway.conf

本サンプルは compose.yaml に設定済みのため、コンテナ内では flyway info / flyway validate で動作します。
環境変数を使わない場合は、毎回 -configFiles=/workspace/flyway.conf を付けて実行してください。

一時的に試すだけなら、コンテナ内で bash から次のように設定できます。

export FLYWAY_CONFIG_FILES=/workspace/flyway.conf
flyway info
flyway validate

8.2 Dev Containers: Rebuild Container の使いどころ

Rebuild は、設定変更を確実に反映させるための再作成です。以下のときに使います。

  • compose.yaml / devcontainer.json を変更した
  • .devcontainer の有無や参照先を切り替えた
  • 拡張機能や features が反映されない
  • コンテナが不安定・起動しない

補足:

  • Rebuild だけでは DB ボリュームは削除されません。初期化したい場合は docker compose ... down -v を実行します。

8.3 手動で flyway migrate を実行する

学習用途として、常に手動実行します。

コンテナ内で実行:

flyway migrate

環境変数を設定していない場合(明示指定):

flyway -configFiles=/workspace/flyway.conf migrate

ホストから実行:

docker compose -f Infra/Database/Flyway/compose.yaml exec flyway flyway migrate

環境変数を設定していない場合(明示指定):

docker compose -f Infra/Database/Flyway/compose.yaml exec flyway flyway -configFiles=/workspace/flyway.conf migrate

9. 動作確認

DBクライアントツールで接続する場合の情報(compose.yaml の該当項目も併記):

  • ホスト: localhost(db.ports: 15432:5432 の左側)
  • ポート: 15432(db.ports)
  • データベース: postgres(db.environment.POSTGRES_DB)
  • ユーザー: postgres(db.environment.POSTGRES_USER)
  • パスワード: postgres(db.environment.POSTGRES_PASSWORD)

SQL 例:

SELECT * FROM study_sales.order_summary;

Flyway の動作確認用 SQL(履歴テーブルの確認):

SELECT
  installed_rank,
  version,
  description,
  type,
  script,
  installed_on,
  success
FROM public.flyway_schema_history
ORDER BY installed_rank;

Flyway の履歴は public.flyway_schema_history に記録されます。検証時に便利なので覚えておくと良いです。

9.1 Dev Container 起動トラブルシュート

調査手順

  1. Dev Containers のログを確認: Dev Containers: Show Log
  2. Docker 側の状態確認:
docker ps -a --filter name=flyway
docker logs flyway-db-1
docker logs flyway-flyway-1

よくある原因と対応

  • PostgreSQL 18 のデータ形式変更で db が起動しない
    症状: /var/lib/postgresql/data の警告 / database files are incompatible
    対応: 学習用途ならボリューム削除で初期化

    docker compose -f Infra/Database/Flyway/compose.yaml down -v
    

    もしくは DB のボリュームを /var/lib/postgresql にマウントする構成に変更します(本サンプルは対応済み)。

  • flyway コンテナが即終了する
    症状: flyway-flyway-1 が Exited / Invalid flag: sleep
    対応: Flyway 公式イメージは entrypoint が flyway 固定のため、command: ["sleep", "infinity"] は不可。
    entrypoint: ["/bin/sh", "-c", "sleep infinity"] で常駐させます(本サンプルは対応済み)。

  • postCreateCommand が実行されない
    症状: flyway migrate が自動実行されずスキーマが作成されない
    対応: 本サンプルは手動実行前提。手動で flyway migrate を実行します。

10. 運用で意識したいポイント

  • 既存の migration ファイルは編集しない: 適用済みファイルの改変は checksum 不一致の原因になるため、新規ファイルで差分を追加します。
  • validate を CI に組み込む: 想定外の改変を早期に検知できます。
  • 開発環境と本番で接続情報を分離: flyway.conf を環境変数化して安全性を高めるのが定番です。

11. validate の検証エラーと復旧手順

validate は 履歴テーブルと SQL ファイルの整合性チェックです。まずは状況を整理します。

  1. flyway info(環境変数未設定なら flyway -configFiles=/workspace/flyway.conf info)で状態確認
  2. public.flyway_schema_history で該当バージョンの履歴を見る

代表的なエラーと復旧の考え方:

  • Checksum mismatch: 適用済みファイルを編集したのが原因です。VCS から元に戻すか、新しい Vx__ で差分を追加します。学習用の開発環境だけなら flyway repair で checksum を合わせる手もあります。
  • Applied migration not resolved: 適用済みだがファイルが見つからない状態です。削除/改名したファイルを復元し、命名も元に戻します。やむを得ず消す場合は開発環境で repair を検討します。
  • Failed migration: 途中失敗で履歴に失敗レコードが残ります。SQL 失敗箇所を修正し、部分適用された変更を手動でクリーンアップしてから flyway repair → migrate が基本です。
  • Out of order: バージョン番号が逆転しています。番号を付け直すか、運用方針に合わせて outOfOrder=true を設定します。

補足: 学習用のローカル環境でのみ、DB ボリュームを削除して初期化するのが最短ルートになる場合があります。実運用環境では避けるべきです。

12. まとめ

このサンプルは、Flyway の「マイグレーション管理」と Dev Container の「環境再現性」を最小構成で組み合わせています。

13. 付録: 主要コマンド

# マイグレーション状況の確認
flyway info

# マイグレーションの適用
flyway migrate

# チェックサム検証
flyway validate

環境変数を設定していない場合(明示指定):

# マイグレーション状況の確認
flyway -configFiles=/workspace/flyway.conf info

# マイグレーションの適用
flyway -configFiles=/workspace/flyway.conf migrate

# チェックサム検証
flyway -configFiles=/workspace/flyway.conf validate

以上で、Flyway + PostgreSQL の Dev Container 最小構成は完了です。運用で困ったら、まず flyway_schema_history と validate を確認する癖を付けるとトラブルシュートが速くなります。

Discussion