前回【Day 23】では、Web API + SQL Server + Redis という本格的なモダン3層コンテナ構成をDocker Composeで立ち上げました。
チーム開発でこの便利な環境を共有しようとした時、絶対に避けて通れない最重要テーマが「機密情報(シークレット)のセキュリティ管理」です!
YAMLファイルに書かれたデータベースのパスワードや接続文字列を、そのままGitにコミットしてしまっていませんか?
本日【Day 24】は、設計図(
docker-compose.yml)を安全にチーム共有しながら、パスワードを完全にGitから隔離する「.env と .gitignore によるセキュリティ運用」と、「C#(IConfiguration)への環境変数注入設計」を徹底解説します!
① 導入・本日の達成目標
「docker-compose.yml が便利だから、Gitリポジトリにプッシュしてチームメンバーにも共有しよう!」
そう思った瞬間、ふと手が止まった経験はありませんか?
MSSQL_SA_PASSWORD: "MyStrongPassword123!" と書かれたComposeファイルを、そのままGitリポジトリへコミット&プッシュ。その瞬間、GitHubや社内GitLabの全閲覧者に対してデータベースの管理者パスワードが丸見えになり、コミット履歴(git log)にも永久に刻み込まれてしまう…!
オンプレミスや従来のWindowsサーバー運用では、サーバー上の web.config や構築手順書のExcelにパスワードを直接書き込み、閉じたネットワーク内で管理していました。
しかし、インフラ構成をコードで管理するIaC(Infrastructure as Code)やGit中心のチーム開発においては、この旧来の感覚のまま作業すると「即座に重大なセキュリティインシデント」へと直結します。
🎯 本日の到達目標(ゴール宣言)
- 平文コミットの危険性とGitの履歴性質: なぜコードやYAMLにパスワードを直書きしてはいけないのか、漏洩のメカニズムを理解する。
- Gitコミット対象 vs 除外の境界線:
docker-compose.yml/.env.example(Git管理)と.env(.gitignore除外)の完璧な棲み分けを身につける。 - Docker Compose の変数展開仕様:
.envファイルがComposeによって自動読み込みされ、${VARIABLE}に置換される仕組みをマスターする。 - C#(ASP.NET Core)へのセキュア設定注入:
appsettings.jsonのデフォルト値を、コンテナ実行時の環境変数(ConnectionStrings__*)で透過的にオーバーライドする実践コードを習得する。
② パスワード平文コミットの恐怖 vs .env による安全管理(パラダイムシフト)
まずは、なぜGit環境においてパスワードの取り扱いにこれほど神経を尖らせる必要があるのか、その本質的な理由を図解で確認しましょう。
Gitコミット履歴の「不変性」という罠
「もし間違えてパスワードをコミットしても、すぐにファイルからパスワードを消して、もう一度コミット(修正コミット)すれば大丈夫なのでは?」
これは初心者やGitに不慣れなエンジニアが最も陥りやすい致命的な誤解です。
Gitは、すべてのコミット時点のスナップショットを暗号学的ハッシュ値(SHA-1/SHA-256)で厳密にチェーン化して保持する分散バージョン管理システムです。
最新の HEAD コミットでファイル内のパスワードを削除しても、過去のコミット履歴(git log や git show)を開けば、過去にプッシュされたファイルの内容は1文字の狂いもなく閲覧・復元可能です。
現代のGitHubやGitLabには、リポジトリにプッシュされたコードを自動スキャンしてパスワードやAPIトークンを検知する「Secret Scanning」機能が備わっています。
パブリックリポジトリに誤ってコミットした場合、数秒以内に世界中の自動クローラーに拾われ、悪用されてしまうのが今のサイバーセキュリティの過酷な現実です。
| 比較項目 | ❌ 危険: 平文ベタ書き&コミット | ⭕ 安全: .env による環境分離 |
|---|---|---|
| パスワードの所在 | YAMLやコード内に直接ハードコード | ローカル端末の .env ファイル内のみ |
| Gitリポジトリ共有 | パスワードごと全員・全履歴に共有(危険) | プレースホルダー(${変数})のみ共有(安全) |
| 環境ごとの切り替え | 開発/本番でファイルを書き換え事故多発 | 環境ごとの .env を差し替えるだけで完結 |
| 漏洩時の影響 | 全リポジトリ閲覧者・Git履歴から永続漏洩 | ローカル漏洩なし(Git上に実値が一切存在しない) |
③ Gitコミット対象 vs .gitignore 除外の徹底分類
では、マルチコンテナ連携プロジェクトにおいて、「何をGitで共有し、何を各自のPC内に隔離すべきなのか」、その境界線を明確に定義しましょう。
1. Gitコミットすべきファイル(チームの設計図)
-
docker-compose.yml: 複数コンテナの構成、ポートマッピング、ボリューム定義、サービス依存関係(depends_on)などのアーキテクチャ設計図です。パスワード値そのものは書かず、後述する${SA_PASSWORD}形式の環境変数参照で記述し、全員で共有します。 -
.env.example(超重要!運用の要): 本物のパスワードではなく、「このプロジェクトを動かすためにどんなキー名の環境変数が必要か」を示したひな型(テンプレート)ファイルです。
値にはYourStrongPasswordHere!やlocalhost:1433といったダミー値・説明用サンプル値を記述してGit管理します。 -
Dockerfile/.dockerignore/ ソースコード: 当然ながらGit管理対象です。
2. 絶対にGitコミットしてはいけないファイル(秘匿情報)
-
.env(実値ファイル): 開発者個人のローカルパスワード、本番DBの管理者パスワード、クラウド接続文字列、外部API秘密鍵などの実値が記載されたファイルです。
絶対に.gitignoreに追加し、Gitの追跡対象から完全に除外しなければなりません! -
.env.local/.env.production: 特定環境用の実値ファイルも同様にGit除外対象です。
📁 チーム開発における黄金のワークフロー
- プロジェクト作成者は
.env.exampleを作成してGitコミットする。 - リポジトリルートの
.gitignoreに.envを忘れずに追記する。 - 新しい開発者が
git cloneしたら、まずcp .env.example .env(Windowsならcopy .env.example .env)を実行。 - 生成されたローカルの
.envに自分専用の開発パスワードを入力してdocker compose up -dを実行!
.gitignore の実践的な設定例
プロジェクトのルートディレクトリにある .gitignore には、以下のように記述を徹底します。
# ==========================================
# 環境変数・機密設定ファイル(Git除外徹底)
# ==========================================
.env
.env.*
!.env.example
# ==========================================
# .NET / C# ビルド成果物・キャッシュ
# ==========================================
bin/
obj/
*.user
appsettings.Development.json
💡 .env.* で関連ファイルを一括除外した上で、!.env.example でテンプレートファイルだけを明示的に除外解除(コミット対象化)するのがプロのベストプラクティスです。
④ Docker Compose の環境変数展開メカニズム
それでは、.env に定義した機密情報が、どのようにして docker-compose.yml 経由でコンテナ内へ注入されるのか、その自動展開メカニズムを紐解いていきましょう。
Composeの自動読み込み(Default .env File)仕様
Docker Compose には非常に洗練されたビルトイン仕様が備わっています。
docker compose up コマンドを実行した作業ディレクトリに .env という名前のファイルが存在する場合、Composeはコマンドライン引数で何も指定しなくても、自動的にその .env ファイルを読み込んで内部の変数として展開します。
YAMLファイル側では、${変数名} というシンプルなプレースホルダー構文を記述しておくだけで、Composeが起動時に実値へと透過的に置換してくれるのです。
実践コード比較:.env と .env.example
まずは2つの環境変数ファイルの内容を比較してみましょう。
# ==========================================
# Docker Compose 共通環境変数テンプレート
# ※ 新規開発者はこのファイルをコピーして .env を作成してください
# ==========================================
# SQL Server 設定
DB_PASSWORD=YourStrongPasswordHere_123!
DB_PORT=1433
# Redis 設定
REDIS_PORT=6379
# ASP.NET Core 設定
ASPNETCORE_ENVIRONMENT=Development
API_HOST_PORT=8080
# ==========================================
# ローカル開発用 実値設定(Gitコミット絶対禁止)
# ==========================================
# SQL Server 設定(自分だけの安全なパスワード)
DB_PASSWORD=MySecr3t_P@ssw0rd_2026!
DB_PORT=1433
# Redis 設定
REDIS_PORT=6379
# ASP.NET Core 設定
ASPNETCORE_ENVIRONMENT=Development
API_HOST_PORT=8080
変数展開に対応した docker-compose.yml 完全版
続いて、上記の環境変数を安全に参照する docker-compose.yml の記述です。パスワードが完全に変数化されている点に注目してください。
version: '3.8'
services:
# 1. SQL Server データベース
db:
image: mcr.microsoft.com/mssql/server:2022-latest
container_name: sql_server_day24
environment:
ACCEPT_EULA: "Y"
# .env からパスワードを安全に置換・注入
MSSQL_SA_PASSWORD: "${DB_PASSWORD}"
ports:
- "${DB_PORT}:1433"
volumes:
- mssqldata:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "$${MSSQL_SA_PASSWORD}" -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 5s
retries: 5
start_period: 15s
# 2. Redis 高速キャッシュ
redis:
image: redis:7-alpine
container_name: redis_day24
ports:
- "${REDIS_PORT}:6379"
volumes:
- redisdata:/data
# 3. ASP.NET Core Web API
api:
build:
context: .
dockerfile: Dockerfile
container_name: web_api_day24
environment:
- ASPNETCORE_ENVIRONMENT=${ASPNETCORE_ENVIRONMENT}
# C# の IConfiguration に合わせて接続文字列を環境変数でオーバーライド
- ConnectionStrings__DefaultConnection=Server=db,1433;Database=DockerDemoDb;User Id=sa;Password=${DB_PASSWORD};TrustServerCertificate=True;
- ConnectionStrings__Redis=redis:6379
ports:
- "${API_HOST_PORT}:8080"
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
volumes:
mssqldata:
redisdata:
$${MSSQL_SA_PASSWORD} について:Docker Composeでは、
${VARIABLE} は「Compose自体の変数展開」として解釈されます。一方、コンテナ内のシェルスクリプトにそのままドル記号を渡したい場合は、$$ と2つ重ねて記述(エスケープ)します。healthcheckコマンド内でコンテナ内環境変数を参照する際の必須テクニックです!
⑤ C# ASP.NET Core でのセキュアな設定注入(IConfiguration連携)
Docker側でパスワードを変数化できても、C#アプリケーション側が機密情報をハードコードしていたら意味がありません。
.NETの極めて強力な設定管理システム「IConfiguration(構成プロバイダー)」とDockerの連携パターンを確認しましょう。
.NETの階層キーと「二重アンダースコア(__)」ルール
ASP.NET Coreの appsettings.json では、設定値は階層構造(JSONオブジェクト)で管理されます。
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=Demo;User Id=sa;Password=dummy;",
"Redis": "localhost:6379"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
この appsettings.json はGitコミットされます。そのため、パスワード部分にはダミー文字列やローカル開発用の仮の値を記載しておきます。
そして、Docker Composeでコンテナを起動する際、環境変数として「ConnectionStrings__DefaultConnection」を渡します。
.NETの構成システムは、環境変数名に含まれる二重アンダースコア(__)を、JSON階層のコロン(:)へと自動的にマッピングして解釈します。
さらに、.NETのデフォルトの読み込み優先順位では「環境変数が appsettings.json を無条件で上書き(オーバーライド)」するため、コードを1行も変えることなく、コンテナ外から安全に本物の接続文字列を注入できるのです!
Program.cs での透過的な接続文字列取得コード
C#アプリケーション側は、値が appsettings.json から来たのか、Dockerの環境変数から上書き注入されたのかを意識する必要は一切ありません。
標準の GetConnectionString メソッドを呼ぶだけで、完全にセキュアに取得できます。
using Microsoft.EntityFrameworkCore;
using StackExchange.Redis;
var builder = WebApplication.CreateBuilder(args);
// ============================================================
// 1. 接続文字列の取得(環境変数が自動でオーバーライド適用済み)
// ============================================================
var dbConnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
if (string.IsNullOrEmpty(dbConnectionString))
{
throw exciting InvalidOperationException("DefaultConnection 接続文字列が設定されていません。.env を確認してください。");
}
var redisConnectionString = builder.Configuration.GetConnectionString("Redis") ?? "localhost:6379";
// ============================================================
// 2. サービス登録(EF Core & 分散キャッシュ)
// ============================================================
builder.Services.AddControllers();
// SQL Server 登録
builder.Services.AddDbContext(options =>
options.UseSqlServer(dbConnectionString));
// Redis 分散キャッシュ登録
builder.Services.AddStackExchangeRedisCache(options =>
{
options.Configuration = redisConnectionString;
options.InstanceName = "DockerDemo_";
});
var app = builder.Build();
app.UseAuthorization();
app.MapControllers();
app.Run();
⑥ YouTube動画連動:霊夢と魔理沙の掛け合い解説
動画講義での霊夢と魔理沙のトークセッションから、実務の核心に迫るハイライトシーンをお届けします!
ConnectionStrings__DefaultConnection)による透過的な環境変数オーバーライドを使えば、コードは完全に無菌状態(パスワードゼロ)を維持できる。これが現場のプロが実践する堅牢なコンテナセキュリティ設計だぜ!」
⑦ 実務でありがちな落とし穴&トラブルシューティング
落とし穴①: .gitignore に書いたのに .env がコミット対象から消えない!
【現象】 .gitignore に .env と追記したのに、git status を叩くと modified: .env と表示され、Gitが追跡を止めない。
.gitignore は「まだ一度もGitに追跡(インデックス登録)されていない新規ファイル」にのみ効果を発揮します。過去に一度でも git add .env を実行してしまった場合、Gitのキャッシュに残っているため除外されません。以下のコマンドでGitの追跡インデックスからのみ削除(ローカルの実ファイルは維持)してください。
git rm --cached .env
git commit -m "fix: .env をGit追跡から完全に除外"
落とし穴②: パスワードに特殊記号($ や #)が含まれていて展開が壊れる
【現象】 DB_PASSWORD=P@ss$$word#1 のように、パスワードにドル記号($)やシャープ(#)が含まれていると、Docker Composeが別の環境変数として誤認識したり、# 以降がコメントとして切り捨てられて認証エラーになる。
.env ファイル内では、特殊記号を含む値はダブルクォートで囲むのが安全です。また、Compose YAMLファイル内でドル記号をリテラルとして扱いたい場合は
$$ とエスケープしてください。
# .env 内
DB_PASSWORD="MyStr0ng#P@ssword!"
落とし穴③: CI/CD(GitHub Actions等)での環境変数注入はどうする?
【疑問】 .env をGitコミットしない場合、GitHub Actions などの自動テストやビルドパイプラインではどうやって環境変数を渡すのか?
GitHubリポジトリの設定(Settings ➡ Secrets and variables ➡ Actions)に
DB_PASSWORD をシークレットとして登録します。ワークフローYAML側で、
env: DB_PASSWORD: ${{ secrets.DB_PASSWORD }} と指定して docker compose up -d を実行すれば、Composeが自動でシェル環境変数を最優先展開して起動してくれます!
⑧ 本日のまとめ・理解したい最重要ポイント
🎉 Day 24 の最重要エッセンス(復習)
- Gitの履歴にパスワードを残さない: YAMLやコードに機密情報を直接書くのは厳禁。修正コミットを打っても過去ログから流出する。
- .env.example と .env の役割分担: キー一覧とダミー値のテンプレート(
.env.example)だけをGit管理し、本物の値が入った.envは.gitignoreで完全隔離する。 - Docker Compose の自動展開: カレントディレクトリの
.envをComposeが自動検出し、${VARIABLE}に置換してコンテナへ注入する。 - C#(IConfiguration)の階層マッピング:
ConnectionStrings__DefaultConnectionのように二重アンダースコアで環境変数を渡すことで、コード無修正のままセキュアに接続文字列を上書きできる。
『コンテナのデータ永続化(Named Volume vs Bind Mount)の決定版』〜コンテナ破棄でもDBデータが絶対に消えないストレージ設計の真髄〜
コメント
コメントを投稿