スキップしてメイン コンテンツに移動

【Day 31】データベース初期データ(シードデータ)の自動投入テクニック 〜新人の手動DBリストアを完全撤廃! entrypoint-initdb の仕組み、C# EF Core 起動時マイグレーション、ゼロセットアップDB環境の構築〜

Day 31 アイキャッチ
▲ Day 31: データベース初期データ(シードデータ)の自動投入テクニック
🐳

「新メンバーがチームに入った日、DB環境構築で丸一日潰していませんか?」

前回のDay 30では、「コード・構成(Git)」と「実データ(Volume)」の3層分離原則を徹底解説し、巨大なDBファイルをGitにコミットしてリポジトリを破壊する事故の防止策を学びました。
しかし、ここで現場のエンジニアから最も多く寄せられる切実な疑問があります:
『mdfファイルをGitに含めないのは理解できた。では、新人が開発を始めるための初期データ(マスターデータやテスト用データ)はどうやって渡せばいいのか?』
かつてのオンプレミス現場では、先輩エンジニアがUSBメモリや共有フォルダで数GBのダンプファイルを手渡し、手作業でSSMSからリストアさせていました。
しかし現代のコンテナ開発では、『リポジトリをクローンして docker compose up を1回実行するだけで、必要な初期データがすべて自動投入されたDBが立ち上がる』という「ゼロセットアップDB環境」 が標準です。
本講義では、Docker標準の entrypoint-initdb の自動実行メカニズムから、C# EF Core による Database.Migrate() とテストデータ注入テクニックまで、現場で即座に役立つ自動化ノウハウを完全網羅します!

INDEX 本日の講義カリキュラム

  • 第1章: USB手動配布の悲劇と「ゼロセットアップDB」の必然性
  • 第2章: コンテナ初回起動時の自動実行メカニズム(entrypoint-initdb.d)
  • 第3章: C# EF Core Migration と Database.Migrate() による自動適用
  • 第4章: チーム開発におけるゼロセットアップDBの Docker Compose 実践構成
  • 第5章: 現場トラブルシューティング&ベストプラクティス
  • 第6章: 第6部総まとめと理解度チェッククイズ(次回予告:第7部 Dockerネットワーク入門)

第1章: USB手動配布の悲劇と「ゼロセットアップDB」の必然性

多くの開発現場で、新メンバー配属時やPCリプレイス時に次のような光景が繰り返されてきました:

  • 先輩社員が「これ、開発用のDBバックアップだから」とUSBメモリを新人に手渡す
  • 新人がSQL Server Management Studio(SSMS)を開き、手動で「データベースの復元」を実行
  • 「SQL Serverのバージョンが合わない(上位バージョンで作られたbakファイルは下位バージョンに復元不可)」「論理ファイル名のマッピングエラー」「ログインユーザーのSID不整合」などでエラー連発
  • 気づけば環境構築だけで新人の入社初日〜2日目が終了してしまう…
USBダンプ手動配布 vs 自動シード
▲ 【図解①】旧来のUSB手動リストア(危険・非効率) vs 現代の初期データ自動投入(安全・爆速)

上図(左側)の通り、手動によるDBダンプ配布には致命的な問題があります。手順の属人化、バージョンの不一致、そして「誰のPCのDBが一番最新の状態か分からない」という深刻な環境ドリフトです。
これに対して現代のコンテナ開発が目指すのが、上図(右側)の 「ゼロセットアップDB(Zero-Setup Database)」 です。
新人が行う操作はたった2つ:

  1. git clone <リポジトリURL> でコードを取得する
  2. docker compose up -d を実行する

これだけで、最新のスキーマ定義、商品マスタ、初期テストユーザーが完璧に整ったデータベースコンテナが自動的に構築されます。人間が手作業でSQLを実行したり、USBメモリを受け渡す必要は1ミリもありません。

第2章: コンテナ初回起動時の自動実行メカニズム(entrypoint-initdb.d)

「コンテナを起動した瞬間に、どうやって初期SQLスクリプトを実行させるのか?」
多くの公式データベースイメージ(PostgreSQL、MySQL、MariaDBなど)には、この初期データ自動投入のための標準仕様である /docker-entrypoint-initdb.d/ という仕組みが最初から組み込まれています。

初期化スクリプト自動実行の流れ
▲ 【図解②】コンテナ初回起動時における entrypoint-initdb 自動実行フローとVolume判定

entrypoint-initdb.d の動作原理

上図に示した通り、データベースコンテナの起動スクリプト(docker-entrypoint.sh)は、内部で次のような厳格な判定を行っています:

🔍 初回起動判定の3ステップロジック

  1. データディレクトリの確認: マウントされたVolume(例: /var/lib/postgresql/data)を検査し、既存のデータベースファイルが存在するかチェックする。
  2. 未初期化(空)の場合のみ実行: データが空の場合(初回起動時)、/docker-entrypoint-initdb.d/ 配下に存在する .sql や .sh スクリプトを ファイル名のアルファベット昇順(例: 01-schema.sql ➡ 02-seed.sql) で順番に自動実行する。
  3. データが存在する場合はスキップ: 既にデータベースが作成されている場合(2回目以降の docker compose up)、スクリプトの実行を自動的にスキップし、既存データを完全に保護する。

この「Volumeが空の初回起動時のみ実行される」という特性が極めて重要です。日々の開発でコンテナを停止・再起動しても、データが勝手に初期化されたり上書きされて消える心配は一切ありません!

SQL Server(MSSQL)における初期スクリプト実行テクニック

ここでMicrosoft SQL Server(mcr.microsoft.com/mssql/server)を扱うC#開発者が必ず突き当たる壁があります:
「SQL Serverの公式イメージには /docker-entrypoint-initdb.d が用意されていない!」 という事実です。
SQL Serverでコンテナ起動時に自動で初期SQLを流し込むには、現場では以下の 「ワンショット初期化コンテナ(initコンテナパターン)」 を採用するのが最も堅牢で推奨されるベストプラクティスです。

# docker-compose.yml での init コンテナパターン
services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      - ACCEPT_EULA=Y
      - MSSQL_SA_PASSWORD=${SA_PASSWORD}
    volumes:
      - sqlserver_data:/var/opt/mssql/data
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$$MSSQL_SA_PASSWORD" -C -Q 'SELECT 1' || exit 1"]
      interval: 5s
      timeout: 5s
      retries: 10

  # ★DB本体が健全起動した後に1度だけ実行されて終了する初期化専用サービス
  db-init:
    image: mcr.microsoft.com/mssql-tools
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./init-scripts:/scripts:ro
    entrypoint: >
      /bin/bash -c "
      echo 'SQL Server初期化スクリプトを実行中...';
      /opt/mssql-tools/bin/sqlcmd -S db -U sa -P "$$SA_PASSWORD" -d master -i /scripts/01_create_tables_and_seed.sql;
      echo '初期データの投入が完了しました!';
      "
    environment:
      - SA_PASSWORD=${SA_PASSWORD}
    restart: "no"

この構成では、db-init サービスが depends_on と service_healthy により 「SQL Serverがクエリを受け付けられる状態になった瞬間」 を検知して自動実行されます。SQLの実行が終われば自動的に停止するため、余計なリソースを消費しません。

第3章: C# EF Core Migration と Database.Migrate() による自動適用

初期データを投入するもう一つの強力なアプローチが、.NET / C# の標準ORMである Entity Framework Core(EF Core) を活用したコードベースのマイグレーションとシーディングです。

EF Coreマイグレーション vs SQL配置
▲ 【図解③】C#現場でのベストプラクティス:EF CoreマイグレーションとSQLスクリプトの役割分担

Program.cs での起動時マイグレーション

C# Web API アプリケーションの起動時(Program.cs)に以下のコードを記述しておくことで、コンテナ起動時に自動で最新マイグレーションをSQL Serverに適用させることができます:

// Program.cs (.NET 8 / 9 形式)
var builder = WebApplication.CreateBuilder(args);

// DbContextの登録
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

// ★コンテナ起動時にデータベースを自動マイグレーション&シード
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    try
    {
        var context = services.GetRequiredService<AppDbContext>();
        // 未適用のマイグレーションがあれば自動適用(テーブル自動生成)
        await context.Database.MigrateAsync();
        // 必要に応じて開発用テストデータをシード
        await DbInitializer.SeedDataAsync(context);
    }
    catch (Exception ex)
    {
        var logger = services.GetRequiredService<ILogger<Program>>();
        logger.LogError(ex, "データベースのマイグレーション中にエラーが発生しました。");
        throw;
    }
}

app.MapGet("/api/products", async (AppDbContext db) => await db.Products.ToListAsync());

app.Run();

HasData によるマスターデータのコード化

EF Coreの modelBuilder.Entity<T>().HasData(...) を使用すると、初期マスタデータ(例: 都道府県マスタ、権限ロール、ステータス定義)をC#コードとして完全にGit管理できます:

// AppDbContext.cs
public class AppDbContext : DbContext
{
    public DbSet<Role> Roles { get; set; }
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        // 必須マスタデータをMigrationファイル内に生成
        modelBuilder.Entity<Role>().HasData(
            new Role { Id = 1, Name = "Administrator" },
            new Role { Id = 2, Name = "Member" },
            new Role { Id = 3, Name = "Guest" }
        );
    }
}

💡 現場の使い分け黄金ルール

① システム必須の小規模マスタデータ(権限・区分値): EF Core の HasData() でMigrationコードとしてGit管理。
② 開発・ローカルテスト用の大量ダミーデータ(1万件の注文履歴など): init.sql や独立したSeederスクリプトで管理し、本番マイグレーションを汚さない!

第4章: チーム開発におけるゼロセットアップDBの Docker Compose 実践構成

それでは、実際に現場でそのまま導入できる 「ゼロセットアップDB環境」の完全なリポジトリ構造と設定ファイル を見ていきましょう。

C#現場のゼロセットアップDB環境
▲ 【図解④】新人が5分で開発開始!Docker ComposeによるゼロセットアップDB環境

推奨ディレクトリ構造

my-csharp-project/
├── .env.example              # パスワード設定のテンプレート(Git共有)
├── .gitignore                 # .env や *.mdf を完全ブロック
├── docker-compose.yml         # サービス定義(WebAPI, DB, 初期化設定)
├── db-init/                   # 初期データ用SQLスクリプト配置フォルダ
│   ├── 01_create_schema.sql  # テーブル生成DDL(EF Core不使用時)
│   └── 02_seed_test_data.sql # テスト用顧客・商品レコードINSERT
└── src/
    └── MyWebApi/
        ├── Dockerfile
        ├── Program.cs
        └── appsettings.json

PostgreSQLを採用した場合の超シンプル Compose 例

PostgreSQLを採用している場合、設定は驚くほどシンプルです。./db-init フォルダを /docker-entrypoint-initdb.d にマウントするだけで、初回起動時に自動実行されます:

services:
  webapi:
    build: ./src/MyWebApi
    ports:
      - "5000:5000"
    environment:
      - ConnectionStrings__DefaultConnection=Host=db;Port=5432;Database=shop_dev;Username=postgres;Password=${POSTGRES_PASSWORD}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_DB=shop_dev
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
    volumes:
      # 実データはNamed Volumeで永続保持
      - postgres_data:/var/lib/postgresql/data
      # ★初回起動時のみ実行される初期化SQLフォルダを読み取り専用マウント
      - ./db-init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d shop_dev"]
      interval: 3s
      timeout: 3s
      retries: 5

volumes:
  postgres_data:

新人がプロジェクトに着任した初日、リポジトリを git clone して .env.example を .env にコピーし、docker compose up -d と打つだけで、数分後には完全なテスト環境がブラウザやSwaggerで動作します。

第5章: 現場トラブルシューティング&ベストプラクティス

初期データ自動投入を運用する中で、チームが必ず遭遇する 「3大ハマりポイント」とその即効解決策 を解説します。

🚨 トラブル①: 「init.sql を書き換えたのに、コンテナを再起動しても反映されない!」

【原因】: entrypoint-initdb.d は 「Volumeが空の初回起動時のみ」 実行されます。既にNamed VolumeにDBデータが作成されている場合、2回目以降の起動では完全にスキップされます。
【解決策】: Volumeを破棄して再作成します:
docker compose down -v
docker compose up -d
-v オプションを付けることで古いVolumeが削除され、初期化スクリプトが再び実行されます。

🚨 トラブル②: 「複数のSQLファイルの実行順序がバラバラで外部キー制約エラーになる!」

【原因】: entrypoint-initdb.d はファイルを辞書順(アルファベット順)に実行します。schema.sql より先に seed.sql が呼ばれるとテーブル未作成エラーになります。
【解決策】: ファイル名の先頭に必ず 01_, 02_ のような連番プレフィックスを付与します:
01_create_tables.sql ➡ 02_seed_master.sql ➡ 03_seed_test_orders.sql

🚨 トラブル③: 「本番環境でも Database.Migrate() を起動時に実行していいの?」

【現場プラクティス】: 単一インスタンスのローカル開発や検証環境では非常に便利ですが、本番環境で複数インスタンス(KubernetesやECS等)が同時にスケール起動する場合、マイグレーション競合やデッドロックのリスク があります。
本番環境では、CI/CDパイプライン(GitHub Actionsなど)のデプロイステップで dotnet ef database update を単独実行するか、専用のマイグレーションJobを実行するのがエンタープライズの標準手法です。

第6章: 第6部総まとめと理解度チェッククイズ

これで第6部「データの永続化とボリューム(Volume & Bind Mount)」の全カリキュラム(Day 27〜31)がすべて完了しました!
コンテナの基本原則であるステートレス設計から、Named Volumeによる堅牢なデータ保持、Bind Mountによる爆速開発、GitとVolumeの境界線ルール、そして今回のゼロセットアップDB環境の構築まで、「データ永続化と開発スピードを完璧に両立させるプロフェッショナルの技術」 を完全にマスターしました!

🎯 理解度チェッククイズ!

問: 開発チームのメンバーが「初期投入用スクリプト(init.sql)にテストユーザーを追加したのですが、docker compose restart を実行してもデータベースに反映されません」と困っています。理由と正しい反映手順を説明してください。

正解と解説を確認する

【正解と解説】

理由: データベースコンテナの entrypoint-initdb.d 機構は、データディレクトリ(Volume)が空である「初回起動時のみ」動作する安全設計になっているためです。再起動(restart)では既にデータが存在するため、スクリプトの実行はスキップされます。

正しい反映手順:
① docker compose down -v を実行して既存のNamed Volumeを削除する。
② docker compose up -d を実行して空のVolumeで再起動し、更新された init.sql を自動実行させる。

🚀 次回予告:第7部「Dockerネットワークの基礎とサービス間通信」開幕!

次回Day 32からは、いよいよ待望の 第7部「Dockerネットワーク」 に突入します!
「なぜ docker-compose.yml の中では localhost ではなくサービス名(db)で通信できるのか?」
Docker内部に自動生成される仮想ブリッジネットワーク(Bridge Network)や組み込みDNSサーバーの驚くべき仕組みを徹底的に解き明かします。ご期待ください!

コメント

このブログの人気の投稿

【Perfume】Perfume 3rd Tour 「JPN」さいたまスーパーアリーナに行って参りました~

Perfume 3rd Tour 「JPN」1月28日、29日の2daysで参戦して参りました。 ファンクラブ会員の選考抽選会に28日、29日の両日で申し込んでいたんですが、両方とも当選していたという嬉しい誤算。 仕事だぁ、なんだかんだと紆余曲折ありまして28日の参戦は危ぶまれていたのですが、なんとかかんとか28日は40分遅刻しながらも参戦、29日はフル参戦する事が出来ました(∩´∀`)∩

Perfume、2025年末で“コールドスリープ(活動休止)”へ──東京ドーム2daysの記録と、公式・各界・ファンの声

  1) 公式発表・基本情報 活動休止(コールドスリープ)を正式発表 2025年9月21日、公式サイト「 Perfumeより 」で、2026年から一度コールドスリープに入ると発表。再始動の意思(“新しいPerfume”として戻る)も明記。 Perfume Official Site 今後の運用告知(配信・上映など) 同21日付の「 今後のPerfumeに関して 」で、9/23(火・祝)東京ドーム公演の LIVESHIP全世界生配信 および 全国ディレイビューイング 決定を案内。特設サイトも併記。 Perfume Official Site +1 公式グッズ **「Perfume Calendar 2026」**発売決定(B2壁掛け/表紙+2カ月1枚×6枚+ポスター)。テーマは“フルーツ”。 Perfume Official Site 2) 東京ドーム2days(9/22・23)要点 公演タイトル: ZO/Z5 Anniversary “ネビュラロマンス” Episode TOKYO DOME 。日程・開場/開演・会場・物販等の実務情報は特設で公開。 Perfume Official Site 9/23公演を世界配信(LIVESHIP) /ディレイビューイング実施。 Perfume Official Site 海外向け報道(英語)も「 indefinite hiatus 」として広く配信。 The Times of India 国内英字メディアも速報。 朝日新聞 3) 現地レポ( メリ爺 )から見える空気 あなたの現地記録(9/24公開)を、ファクトと現場情景の両面で反映: 両日参戦 。22日は アリーナ 、23日は 1階席から俯瞰 。前日の発表を受け、開演前のザワつき→MC後に“いつもの一体感”へ。開演は22日 18:45 頃。オープニングは**P³ツアー東京ドーム(2020/2)**と同趣向で“回収”の演出。 メリ爺のゲーム万歳 MC所感 :解散ではなく前向きな休止で不安を和らげる内容。 かしゆか の涙、 のっち の気丈さ、 あ〜ちゃん の思い出語り──3人の結束と「 また戻る 」約束が強く刻まれた。 メリ爺のゲーム万歳 体感時間 : “3時間弱”級のボリューム ながら“過去一...

ニンテンドースイッチでオンラインゲームを遊ぶには?

モンスターハンターライズが発売になって、家族でモンハン楽しみたい人が増えたみたいですね。 某ブログの検索キーワードを調べると、モンハンを家族でやるにはどうしたらいいのか?を調べている人が沢山いるのがわかります。 ※これでも一部  家族で協力するにはSwitch2台、ソフトも2つ購入する必要があります。 1台のスイッチでは遊べませんし、ソフト1つでも遊べません。 でも、1台のスイッチで別々アカウントがあれば交代で遊ぶこともできますよ。セーブデータもしっかり分かれます。 Switchのオンラインゲームを家族で遊ぶ方法については以下の記事にまとめてますのでよろしければ参考にしてみてくださいね。 家族でモンスターハンターライズDL版のオンラインプレーを楽しむ方法