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

【Day 22】C# Web API + SQL Server の連携環境をcomposeで一発構築 〜接続文字列の設定・ボリューム永続化・healthcheckによる確実な起動順序制御〜

Day 22 アイキャッチ
▲ Day 22: C# Web API + SQL Server の連携環境をcomposeで一発構築
🐳
受講生の皆さん、お疲れ様です! Docker/コンテナ実践講座の専任講師です。
前回【Day 21】では、複数のコンテナを1行でまとめて動かす「Docker Compose」の全体像と宣言的YAMLの基本を学びました。
そして本日【Day 22】は、C#/.NETエンジニアにとって最も実務直結であり、最も頻出する黄金の組み合わせ「ASP.NET Core Web API + Microsoft SQL Server」のマルチコンテナ連携ハンズオンに挑みます!
開発者PCを重厚なSQL Serverのインストールで汚す悪夢から完全に脱却し、docker compose up -d のたった1コマンドで、完全なデータベース付き開発環境を一瞬で立ち上げるモダンな開発フローを体感しましょう!

① 導入・本日の達成目標

「プロジェクト参画初日、開発PCにSQL Server Developer Editionをインストールするだけで半日終わった…」
「同僚のPCでは動くのに、私のPCではSQLの照合順序(Collation)や大文字小文字の区別設定が違っていてクエリエラーになる…」
「ローカルのSQL Serverサービスが常駐しているせいで、ノートPCのメモリが圧迫されてVisual Studioの動作がもっさりする…」

長年Windows環境でC#開発に携わってきたベテランエンジニアほど、このような「ローカルDB個別インストール地獄」に苦しめられた経験があるはずです。
しかし、現在マイクロソフトは公式のLinux版SQL Serverコンテナイメージ(mcr.microsoft.com/mssql/server)を無償提供しており、Docker Composeを使えばローカルPCにSQL Server本体を1バイトもインストールすることなく、本番と100%同一のDB環境を即座に手に入れることができます!

本日の講義を修了すると、以下の3大目標を完全に達成できます。

  • 公式SQL Serverコンテナの定義(SA_PASSWORD, ACCEPT_EULA, volumes)を習得し、安全なデータ永続化とライセンス管理を実現できる。
  • Compose内部DNSによる名前解決(Server=db,1433)を理解し、Entity Framework Core(EF Core)の接続文字列を環境変数経由で正しく注入できる。
  • 単純な depends_on では防げない起動時クラッシュを、healthcheck による「完全準備完了(Ready)待ち合わせ」で確実に撲滅できる。

② 個別手動インストールの悲劇 vs Composeコンテナ化(パラダイムシフト)

まずは、なぜローカルPCへのSQL Server個別インストールをやめ、コンテナ化へ舵を切るべきなのか、その劇的なパラダイムシフトを図解で確認しましょう。

個別手動インストール vs Composeコンテナ
▲ 【図解①】個別手動インストール vs Composeコンテナ(開発PCの環境汚染・照合順序差異から解放されるパラダイムシフト)

従来の開発スタイルでは、開発者のPCそれぞれにギガバイト級のSQL Serverインストーラを実行し、ローカルのWindowsサービスとして常駐させていました。
これには以下のような致命的な問題が常に付きまとっていました。

  • PCリソースの常時圧迫: PC起動時からバックグラウンドでSQL Serverサービスがメモリを数GB消費し、バッテリー駆動時間や動作速度を著しく低下させる。
  • 環境差異(環境ドリフト)の発生: 「田中さんはSQL Server 2019、佐藤さんは2022」「インスタンス名が SQLEXPRESS と MSSQLSERVER で混在」「Collation(Japanese_CI_AS vs Japanese_CS_AS)が異なり文字列検索の挙動が乖離」。
  • アンインストールと環境汚染: プロジェクト終了後、レジストリや共有コンポーネントがPC内に散乱し、完全にクリーンな状態へ戻すのが極めて困難。

一方、Docker Composeによるコンテナ運用では、SQL Serverはただの「隔離された軽量Linuxプロセス」にすぎません。
docker compose up -d を叩けば数秒で起動し、仕事が終われば docker compose down でPC上のリソースを完全にゼロへ戻せます。
もちろん、イメージタグ(例: 2022-latest)によりチーム全員が1ビットの狂いもなく完全同一のSQL Server環境を共有できます!

③ docker-compose.yml の2大サービス完全定義(SQL Server & Web API)

それでは、C# Web API と SQL Server を連携させる完全な docker-compose.yml の中身を見ていきましょう。

docker-compose.yml の2大サービス定義
▲ 【図解②】docker-compose.yml における SQL Server と Web API の2大サービス宣言(環境変数・ポート・ボリューム集約)
# docker-compose.yml
services:
  db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    container_name: my-sqlserver
    environment:
      - ACCEPT_EULA=Y  # マイクロソフトのライセンス条項に同意
      - MSSQL_SA_PASSWORD=YourStrong@Pass123  # SAパスワード(強度要件必須)
    ports:
      - "1433:1433"  # ホストPCのSSMSやAzure Data Studioからの接続用
    volumes:
      - sqldata:/var/opt/mssql  # DBファイルをボリュームで永続化!
    healthcheck:
      test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'YourStrong@Pass123' -C -Q 'SELECT 1' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s

  webapi:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: my-csharp-webapi
    ports:
      - "5000:8080"  # ホストPCのブラウザから http://localhost:5000 でアクセス
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
      - ConnectionStrings__DefaultConnection=Server=db,1433;Database=CatalogDb;User Id=sa;Password=YourStrong@Pass123;TrustServerCertificate=True;MultipleActiveResultSets=true;
    depends_on:
      db:
        condition: service_healthy  # DBがクエリ受付可能になるまで待機!

volumes:
  sqldata:  # Docker管理の名前付きボリューム

④ コンテナ間通信と内部DNS:EF Core接続文字列の正しい書き方

Docker Composeで初心者が最も躓くのが「接続文字列のサーバー名(ホスト名)」の指定です。

コンテナ間通信と名前解決
▲ 【図解③】Compose内部DNSによる名前解決(Server=db で繋がるコンテナ間通信ネットワーク)

⚠️ なぜ Server=localhost では絶対に繋がらないのか?

「ローカルPC上で動かしているのだから、接続文字列は Server=localhost でいいのでは?」と考えてしまう方が非常に多いです。
しかし、コンテナ内部における localhost とは「そのコンテナ自身の隔離ネットワーク」を意味します。
webapi コンテナの中から localhost:1433 を探しても、そこにSQL Serverは存在しないため、即座に SqlException: A network-related or instance-specific error occurred がスローされて起動クラッシュします。

✅ 正解は Compose サービス名「Server=db,1433」

Docker Composeは、起動時に全コンテナが参加する専用の仮想ネットワーク(デフォルトブリッジネットワーク)を自動生成します。
そして、Compose内部に組み込まれたDNSサーバーが、サービス名(db や webapi)を該当コンテナのIPアドレスへと自動的に名前解決してくれます!
したがって、C#の接続文字列には以下のようにサービス名を指定するのが唯一絶対の正解です。

Server=db,1433;Database=CatalogDb;User Id=sa;Password=YourStrong@Pass123;TrustServerCertificate=True;

また、ASP.NET Coreの構成システムは、環境変数名における「二重アンダースコア(__)」を appsettings.json のコロン(:)区切り階層とみなす仕様があります。
Compose側で ConnectionStrings__DefaultConnection=... と設定するだけで、appsettings.json 内の値を自動的にオーバーライドできるため、Git管理するソースコードにパスワードをハードコードする必要もありません!

⑤ 単純なdepends_onの落とし穴と healthcheck による起動順序制御

「depends_on: - db と書いているのに、なぜかWeb APIが起動時にDB接続エラーで死んでしまう…」という現象に遭遇したことはありませんか?
その原因と解決策を図解で整理しましょう。

healthcheck と起動順序制御
▲ 【図解④】healthcheck と depends_on による完全な起動待ち合わせ(初期化完了待ちによる接続クラッシュの根絶)

「プロセス起動」と「クエリ受付準備完了(Ready)」の決定的なタイムラグ

Dockerの単純な depends_on は、「前段のコンテナプロセス(PID)が立ち上がったこと」しか検知しません。
しかし、SQL Serverのような本格的RDBMSは、OSプロセスが起動してから内部で master データベースの復旧、トランザクションログのスキャン、TCPリスナーの開放を行うまでに、通常10秒〜20秒程度の初期化タイムラグが発生します。
プロセスが動いた直後にWeb APIが起動して接続しにいくと、リスナーがまだ開いていないため接続拒否(Connection Refused)で即死してしまうのです。

healthcheck + condition: service_healthy で完全同期

これを解決するのが、SQL Serverの稼働状態を内部からポーリング監視する healthcheck です。
SQL Serverコンテナ内にプリインストールされている sqlcmd ツールを使い、SELECT 1 クエリが正常に応答する(終了コード0を返す)かテストします。

  • interval: 10s: 10秒ごとにヘルスチェックを実行
  • timeout: 5s: 応答が5秒以上無ければ失敗と判定
  • retries: 5: 5回連続で成功/失敗を判定
  • start_period: 15s: コンテナ起動直後の15秒間は失敗してもカウントしない

そして、Web API側の depends_on に condition: service_healthy を指定することで、SQL Serverがクエリを正常処理できる状態になるまで、Web APIの起動を安全に待機させることができます!

⑥ 実践ハンズオン:C# EF Coreの自動マイグレーション実装

Web APIとSQL Serverが安全に起動したら、初回起動時に自動的にデータベースとテーブルを作成する「EF Core 自動マイグレーション」を組み込みましょう。
Program.cs に以下のコードを追加します。

// Program.cs
var builder = WebApplication.CreateBuilder(args);

// DbContextの登録(SQL Serverプロバイダー)
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<AppDbContext>(options =>
    options.UseSqlServer(connectionString));

builder.Services.AddControllers();
var app = builder.Build();

// 起動時に自動で未適用のマイグレーションをSQL Serverへ流し込む
using (var scope = app.Services.CreateScope())
{
    var services = scope.ServiceProvider;
    var logger = services.GetRequiredService<ILogger<Program>>();
    try
    {
        var dbContext = services.GetRequiredService<AppDbContext>();
        logger.LogInformation("SQL Serverへのマイグレーション適用中...");
        await dbContext.Database.MigrateAsync();
        logger.LogInformation("データベースマイグレーション完了!");
    }
    catch (Exception ex)
    {
        logger.LogError(ex, "マイグレーション実行中にエラーが発生しました。");
        throw;
    }
}

app.MapControllers();
app.Run();

これで準備は完了です!ターミナルで以下のコマンドを実行してみましょう。

# ビルドと起動を1行で実行
$ docker compose up -d --build

# ログをリアルタイムで追尾確認
$ docker compose logs -f

SQL Serverコンテナが立ち上がり、ヘルスチェックがhealthyになった瞬間にWeb APIコンテナが起動し、自動的に CatalogDb データベースとテーブル群が生成されます!
ホストPCのブラウザから http://localhost:5000/api/catalog にアクセスすれば、コンテナ内のSQL Serverから取得したJSONデータが即座に返ってきます。

⑦ YouTube動画連動:霊夢と魔理沙の掛け合い解説

本講義の内容は、YouTubeの動画講義(ゆっくり解説)でも楽しくビジュアルに学べます!
手動インストールの悲哀とComposeの快感を、霊夢と魔理沙の会話で振り返ってみましょう。

霊夢: 「魔理沙!私、今まで新しいPC買うたびにSQL Server Developer Editionをインストールしてたんだけど、あれって何ギガも容量食うし、アンインストールしても変な共有ファイルが残って気持ち悪かったのよね…」
魔理沙: 「全エンジニアが通る道だな!だが今日の講義で学んだように、Composeを使えば公式Linux版SQL Serverがたった1行で動く!PCのレジストリもサービスも一切汚さず、使わないときはdownで綺麗さっぱり消せるんだぜ!」
霊夢: 「接続文字列も Server=db,1433 ってサービス名を書くだけで、Composeが勝手に内部DNSで繋いでくれるのがすごく便利ね!localhostって書いちゃダメな理由もよくわかったわ!」
魔理沙: 「そして一番大事なのが『healthcheck』だ!SQL Serverがクエリを受け付けられる状態になるまでWebアプリの起動を待たせることで、起動時クラッシュを100%防止できる!新メンバーが来ても、git cloneしてup一発で5秒後に開発開始だぜ!」

⑧ 実務でありがちな落とし穴&ベストプラクティス

💡 現場で必ず遭遇する Compose 運用の4大落とし穴

落とし穴① SA_PASSWORDの強度ポリシー違反でコンテナが即死する
SQL Serverコンテナは、デフォルトで厳格なパスワードポリシーが有効になっています。
「最低8文字以上」かつ「英大文字・英小文字・数字・特殊記号(@, #, $など)」のうち3種類以上を含む必要があります。単純な password123 などにすると、起動直後に内部エラーでコンテナが強制終了します。

落とし穴② ホストPC側の 1433 ポートが衝突する
過去にホストPCに直接SQL Serverをインストールしたことがある場合、すでにローカルの 1433 ポートが使用されており、bind: address already in use で起動に失敗することがあります。
その場合は、ports: ["11433:1433"] のようにホスト側の受付ポートを 11433 等に変更してください。(コンテナ同士は db:1433 で通信するため、Web API側の接続文字列の変更は不要です)

落とし穴③ TrustServerCertificate=True の指定漏れによるSSL/TLSエラー
.NET 7 / 8 や Microsoft.Data.SqlClient の最新版では、SQL Serverとの通信暗号化がデフォルトで必須(Encrypt=True)となっています。
コンテナ内の自己署名証明書を受け入れるため、接続文字列に必ず TrustServerCertificate=True; を付与してください。

落とし穴④ volumes による永続化を忘れてデータが消滅する
volumes: - sqldata:/var/opt/mssql の記述を忘れると、コンテナを docker compose down して再作成した際、内部のmdf/ldfファイルがコンテナと一緒に破棄されてしまいます。開発用テストデータを保護するため、必ず名前付きボリュームを定義しましょう。

📌 本日のまとめ & 明日へのステップ
  • SQL Serverのコンテナ化: 開発PCへの重厚な個別インストールを廃止し、公式Linuxイメージ(mssql/server)で環境汚染ゼロのイミュータブル運用を実現する。
  • 名前付きボリュームによる永続化: sqldata:/var/opt/mssql を定義し、コンテナを破棄・再作成してもDBデータを安全に維持する。
  • 内部DNS名前解決: コンテナ間通信では localhost ではなくComposeサービス名(Server=db,1433)を接続文字列に指定する。
  • healthcheck による確実な起動制御: sqlcmd によるクエリ応答確認と condition: service_healthy を組み合わせ、初期化前の接続クラッシュを完全撲滅する。
次回【Day 23】予告:
『Redisキャッシュを追加した3層コンテナアーキテクチャ』〜Web API + SQL Server + Redis の分散キャッシュ・セッション管理をComposeで構築〜

コメント

このブログの人気の投稿

【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版のオンラインプレーを楽しむ方法