0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【第5回】AWS ECS Fargate 11回シリーズ|Phase 2-2|ECR & Docker Image Push

0
Last updated at Posted at 2026-01-23

この記事について

記事 タイトル 状態
第0回 全体ガイド ✅ 完了
第1回 構成図編 ✅ 完了
第2回 VPC & Subnet ✅ 完了
第3回 NAT Gateway & Route Table ✅ 完了
第4回 Security Group ✅ 完了
第5回 ECR & Docker Image 📍今回
第6回 Public ALB ⬜ 未読
第7回 ECS Front & IAM ⬜ 未読
第8回 ECS API & Internal ALB ⬜ 未読
第9回 RDS MySQL ⬜ 未読
第10回 Secrets Manager & 完成 ⬜ 未読

進捗: 45% (5/11記事) | Phase: 2-2 | 🎯 マイルストーン2達成: Phase 2完了!

📁 完全なコードはGitHubで公開:👉 GitHub: fargate-iac-02


1. はじめに

この記事は、Phase 2の後半として、ECRとDockerイメージのpushを行います。

1-1. 前回までの振り返り

Phase 2-1(第4回)で作成したもの:

  • ✅ Security Group × 5

今回(第5回: Phase 2-2)で作成するもの:

  • ECR Repository × 2(Front、API)
  • Lifecycle Policy × 2
  • Dockerイメージのビルド & push

1-2. この記事で学べること

  • ECRの基本と役割
  • Dockerイメージのビルド方法
  • ECRへのpush手順
  • Lifecycle Policyの設定

1-3. 対象読者

  • Phase 2-1(第4回)を完了した方
  • Dockerの基本を知っている方

1-4. 想定環境

  • Terraform: v1.9.x
  • Docker: v20.x以上
  • AWS CLI: v2.x
  • Phase 1-2: 実行済み

2. Phase 2-2で作成するもの

2-1. リソース一覧

リソース 数量 用途 月額費用
ECR Repository 2 コンテナイメージ保存 約$0.10/GB
Lifecycle Policy 2 古いイメージ自動削除 無料

合計: 4リソース

コスト: 約$0.10/GB(イメージサイズによる)

2-2. ECRの用途

Repository名 格納するイメージ サイズ(目安)
myproject/front Nginx + 静的ファイル 約50MB
myproject/api Go + ビジネスロジック 約20MB

合計: 約70MB → 月額約$0.01

📝 注意: リポジトリ名は myproject/front のようにスラッシュを含みます


3. ECRの役割

3-1. ECRとは

ECR (Elastic Container Registry) は、AWSのマネージドなDockerレジストリです。

特徴:

  • Docker Hubのようなコンテナイメージ保存場所
  • AWSサービスとの統合(IAM、VPC Endpoint等)
  • 暗号化、脆弱性スキャン機能

料理で例えると:

  • ECR = レストランの冷蔵庫
  • Dockerイメージ = 調理済みの料理(冷凍保存)
  • ECS Task = 料理を温めて提供

3-2. Docker Hubとの違い

項目 ECR Docker Hub
料金 使用量課金 無料プランあり
速度 AWS内で高速 インターネット経由
セキュリティ IAMで制御 DockerID認証
用途 本番環境 開発環境、公開イメージ

3-3. Lifecycle Policyとは

Lifecycle Policyは、古いイメージを自動削除するルールです。

設定例:

  • 最新30個のイメージを保持
  • それ以外は自動削除

メリット:

  • ✅ ストレージコストの削減
  • ✅ 手動削除の手間が不要

3-4. Terraformだけでは完結できないのか?

素朴な疑問:

「なぜDockerイメージのビルドやPushも必要なの?」
「すべてTerraformで完結できないの?」


3-4-1. Terraformの役割とできること / できないこと

作業 Terraformでできる? 分類
VPC作成 ✅ できる AWSリソース
ALB作成 ✅ できる AWSリソース
ECR Repository作成 ✅ できる AWSリソース
Dockerイメージ作成 ❌ できない アプリケーション
ECRにPush ❌ できない データ転送
ECS Task Definition作成 ✅ できる AWSリソース

重要なポイント:

Terraformはインフラ管理ツールであって、アプリケーションビルドツールではありません。


3-4-2. 料理の比喩で理解する

🏢 Terraform = レストランの建設会社
   - レストランを建てる(VPC、ALB、ECS)
   - 冷蔵庫を設置する(ECR Repository作成)
   - 「冷蔵庫の中の料理Aを使ってください」と指示(Task Definition)

🍳 Docker = 料理人
   - 料理を作る(Docker Image作成)
   - 冷蔵庫に料理を入れる(ECR Push)

📦 ECR = 冷蔵庫
   - 料理(Dockerイメージ)を保存

🍽️ ECS = ウェイター
   - 冷蔵庫から料理を取り出して、温めて提供

Terraformでできないこと:

  • ❌ 料理を作る(Docker Image作成)
  • ❌ 冷蔵庫に料理を入れる(ECR Push)

3-4-3. 実際の作業の流れ(Phase 2-2)

# ステップ1: Terraformでインフラ構築(✅ できる)
cd terraform/phase2-security
terraform apply
  → ECR Repository "myproject/front" 作成 ✅
  → 空の冷蔵庫ができた状態

# ステップ2: Dockerイメージ作成(❌ Terraformではできない)
cd ../../front
docker build -t myproject/front:latest .
  → Dockerイメージ作成 ✅
  → 料理ができた状態

# ステップ3: ECRにPush(❌ Terraformではできない)
docker push 014936...ecr.../myproject/front:latest
  → ECRに保存 ✅
  → 冷蔵庫に料理を入れた状態

もしステップ2・3をスキップすると:

Phase 3実行(ECS起動)
  ↓
ECS Taskが起動しようとする
  ↓
ECRから "myproject/front:latest" を取得しようとする
  ↓
イメージがない!(冷蔵庫が空)
  ↓
CannotPullContainerError ❌
  ↓
503 Service Temporarily Unavailable ❌

3-4-4. なぜ分離する必要があるのか?

理由1: 技術的制約

TerraformはAWSリソースの管理に特化しています。

Terraformの得意分野:
  - AWSリソースの作成・削除・更新
  - インフラの状態管理(terraform.tfstate)
  - 依存関係の解決

Terraformの苦手分野:
  - アプリケーションのビルド
  - データの転送(Docker Image Push)
  - 継続的なデプロイメント

理由2: 設計思想(関心の分離)

インフラ(Terraform)とアプリケーション(Docker)は分離すべき

なぜ?
  - 変更頻度が違う
  - 管理者が違う(インフラチーム vs 開発チーム)
  - ライフサイクルが違う

例: 実務での変更頻度

インフラ変更: 月1回
  → terraform apply

アプリ変更: 週10回
  → docker build & push
  → ECS Serviceが自動的に新イメージをPull
  → terraform apply は不要

3-4-5. 実務での一般的なフロー

【開発フェーズ】
1. ローカル開発
   docker-compose up
   → ローカルで動作確認

【移行準備フェーズ】
2. インフラ構築
   terraform apply(ECR作成)
   → 冷蔵庫を設置

3. イメージ準備
   docker build & push(イメージ準備)
   → 料理を作って冷蔵庫に入れる

【本番運用フェーズ】
4. サービス起動
   terraform apply(ECS起動)
   → ECRのイメージを使って起動

5. アプリ更新時(頻繁)
   docker build & push(イメージ更新)
   ECS Service更新(新イメージを使う)
   ※ terraform apply は不要

3-4-6. まとめ

項目 説明
Terraformの役割 インフラ構築(箱を作る)
Dockerの役割 アプリケーション準備(中身を作る)
なぜ分離? 技術的制約 + 設計思想(関心の分離)
実務での使い分け インフラ変更: Terraform、アプリ変更: Docker

このセクションのポイント:

✅ Terraformは「インフラ管理ツール」であり、アプリケーションビルドはできない
✅ DockerイメージのビルドとPushは別途必要
✅ インフラとアプリは変更頻度が違うため、分離して管理する

次のセクションから、実際にDockerイメージを作成してECRにPushする手順を進めていきます。


4. ディレクトリ構成

4-1. プロジェクト全体の構成

my-ecs-project/
├── terraform/
│   ├── phase1-network/      ← Phase 1完了
│   └── phase2-security/     👉 今回更新
│       ├── main.tf
│       ├── variables.tf
│       ├── terraform.tfvars
│       ├── data.tf
│       ├── security_group.tf   ← 第4回で作成済み
│       ├── ecr.tf              👉 今回追加
│       └── outputs.tf          ← 更新
│
├── front/                   👉 今回作成(Dockerイメージ用)
│
└── api/                     👉 今回作成(Dockerイメージ用)

4-2. Phase 2-2で追加するファイル

ファイル名 役割
ecr.tf ECR Repository × 2、Lifecycle Policy × 2

📝 補足:front/api/ について

本シリーズはTerraformによるインフラ構築がメインのため、アプリケーションコード(HTML/CSS/JS/Go)の詳細解説は省略します。

ディレクトリ 内容 用途
front/ Nginx + 静的HTML/JS API Test / DB Test ボタンUI
api/ Go言語 API ヘルスチェック、DB接続テスト

5. ファイル別コード解説

5-1. ecr.tf - ECR Repository × 2

ファイルの役割:

  • ECR Repository × 2を作成
  • Lifecycle Policyを設定

📝 重要: リポジトリ名は ${var.project_name}/front のようにスラッシュを含みます

コード骨格図解:

簡易コード:

# terraform/phase2-security/ecr.tf

# === Frontend ===
resource "aws_ecr_repository" "front" {
  name                 = "${var.project_name}/front"  # スラッシュあり!
  image_tag_mutability = "MUTABLE"
  image_scanning_configuration { scan_on_push = true }
  encryption_configuration { encryption_type = "AES256" }
}

resource "aws_ecr_lifecycle_policy" "front" {
  repository = aws_ecr_repository.front.name
  policy     = jsonencode({
    rules = [{
      rulePriority = 1
      description  = "Keep last 30 images"
      selection    = { tagStatus = "any", countType = "imageCountMoreThan", countNumber = 30 }
      action       = { type = "expire" }
    }]
  })
}

# === API === (Frontend と同じ構成)
resource "aws_ecr_repository" "api" {
  name                 = "${var.project_name}/api"
  image_tag_mutability = "MUTABLE"
  image_scanning_configuration { scan_on_push = true }
  encryption_configuration { encryption_type = "AES256" }
}

resource "aws_ecr_lifecycle_policy" "api" {
  repository = aws_ecr_repository.api.name
  policy     = jsonencode({ /* Frontend と同じ */ })
}

ポイント解説:

項目 説明
name ${var.project_name}/front スラッシュあり(ハイフンではない)
image_tag_mutability MUTABLE タグ上書き許可(開発向け。本番はIMMUTABLE推奨)
scan_on_push true Push時に脆弱性スキャン実行
encryption_type AES256 イメージを暗号化して保存
Lifecycle Policy 最新30個保持 古いイメージを自動削除してコスト削減

📁 完全なコードは GitHub: fargate-iac-02 を参照


5-2. outputs.tf - 出力の更新

既存のoutputs.tfに、ECR URLを追加:

# terraform/phase2-security/outputs.tf

# ... 既存のSG出力 ...

output "ecr_front_url" {
  description = "ECR Repository URL for Frontend"
  value       = aws_ecr_repository.front.repository_url
}

output "ecr_front_arn" {
  description = "ECR Repository ARN for Frontend"
  value       = aws_ecr_repository.front.arn
}

output "ecr_api_url" {
  description = "ECR Repository URL for API"
  value       = aws_ecr_repository.api.repository_url
}

output "ecr_api_arn" {
  description = "ECR Repository ARN for API"
  value       = aws_ecr_repository.api.arn
}

6. Terraform実行手順

6-1. ecr.tfの追加

cd my-ecs-project/terraform/phase2-security

# ecr.tfを作成
# outputs.tfを更新

6-2. terraform plan

terraform plan

実行結果:

Plan: 4 to add, 0 to change, 0 to destroy.

内訳:

  • ECR Repository × 2
  • Lifecycle Policy × 2

6-3. terraform apply

terraform apply

所要時間: 約30秒

実行結果:

aws_ecr_repository.front: Creating...
aws_ecr_repository.api: Creating...
aws_ecr_repository.front: Creation complete
aws_ecr_repository.api: Creation complete
aws_ecr_lifecycle_policy.front: Creating...
aws_ecr_lifecycle_policy.api: Creating...

Apply complete! Resources: 4 added, 0 changed, 0 destroyed.

Outputs:

ecr_front_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front"
ecr_front_arn = "arn:aws:ecr:ap-northeast-1:014936403628:repository/myproject/front"
ecr_api_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api"
ecr_api_arn = "arn:aws:ecr:ap-northeast-1:014936403628:repository/myproject/api"

📝 確認ポイント:

  • ECR URLに myproject/front のようにスラッシュが含まれている
  • これが正しい形式です

7. Dockerイメージのビルド & Push

このセクションでは、実際にDockerイメージをビルドしてECRにpushします。


7-1. 前提: frontとapiディレクトリの準備

7-1-1. プロジェクト構造の確認

cd my-ecs-project

# 現在の構造
my-ecs-project/
├── terraform/
│   ├── phase1-network/
│   └── phase2-security/
│
# 以下を作成します
├── front/    👈 これから作成
└── api/      👈 これから作成

7-1-2. Frontendのファイル準備

⚠️ 重要: Dockerイメージをビルドする前に、frontディレクトリとファイルを準備してください。

ステップ1: frontディレクトリ作成
# プロジェクトルートに移動
cd my-ecs-project

# frontディレクトリ作成
mkdir -p front
cd front
ステップ2: Dockerfile作成
cat > Dockerfile << 'EOF'
FROM nginx:alpine
COPY index.html /usr/share/nginx/html/
EXPOSE 80
EOF

Dockerfileの説明:

  • FROM nginx:alpine: 軽量なNginxイメージをベースに使用
  • COPY index.html ...: HTMLファイルをNginxの公開ディレクトリにコピー
  • EXPOSE 80: ポート80を公開

ステップ3: index.html作成
cat > index.html << 'EOF'
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Frontend - ECS Fargate</title>
    <style>
        body {
            font-family: 'Arial', 'Helvetica', sans-serif;
            max-width: 900px;
            margin: 50px auto;
            padding: 30px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
        }
        .container {
            background-color: white;
            padding: 40px;
            border-radius: 12px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.3);
        }
        h1 {
            color: #333;
            text-align: center;
            font-size: 2.5em;
            margin-bottom: 10px;
        }
        .status {
            text-align: center;
            color: #666;
            margin: 20px 0;
            font-size: 1.1em;
        }
        .status p {
            margin: 5px 0;
        }
        .buttons {
            text-align: center;
            margin: 40px 0;
        }
        .button {
            display: inline-block;
            margin: 10px;
            padding: 15px 30px;
            background-color: #4CAF50;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            font-size: 16px;
            font-weight: bold;
            transition: all 0.3s ease;
            box-shadow: 0 4px 6px rgba(0,0,0,0.1);
        }
        .button:hover {
            background-color: #45a049;
            transform: translateY(-2px);
            box-shadow: 0 6px 8px rgba(0,0,0,0.15);
        }
        .button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
            transform: none;
        }
        .button.secondary {
            background-color: #008CBA;
        }
        .button.secondary:hover {
            background-color: #007399;
        }
        #result {
            margin-top: 30px;
            padding: 20px;
            background-color: #f9f9f9;
            border-left: 5px solid #4CAF50;
            border-radius: 4px;
            min-height: 60px;
            font-family: 'Courier New', monospace;
            font-size: 14px;
            line-height: 1.6;
        }
        .success {
            color: #4CAF50;
            font-weight: bold;
        }
        .error {
            color: #f44336;
            font-weight: bold;
        }
        .info {
            color: #2196F3;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🚀 ECS Fargate Frontend</h1>
        
        <div class="status">
            <p class="success">✅ Frontend Container is Running!</p>
            <p class="info">📍 Region: ap-northeast-1 (Tokyo)</p>
            <p class="info">⚙️ Powered by: AWS ECS Fargate + Nginx</p>
        </div>
        
        <div class="buttons">
            <button id="apiTestButton" class="button">🔗 API Test (Phase 4)</button>
            <button id="databaseTestButton" class="button secondary" disabled>
                🗄️ Database Test (Phase 5)
            </button>
        </div>
        
        <div id="result"></div>
    </div>

    <script>
        const resultDiv = document.getElementById('result');
        const apiTestButton = document.getElementById('apiTestButton');
        const databaseTestButton = document.getElementById('databaseTestButton');

        // API Test Button
        apiTestButton.addEventListener('click', async () => {
            resultDiv.innerHTML = '<p class="info">⏳ Calling API...</p>';
            apiTestButton.disabled = true;
            
            try {
                // Phase 4でInternal ALB経由でAPIを呼び出す
                // 現時点ではAPIがないのでエラーメッセージを表示
                resultDiv.innerHTML = `
                    <p class="error">⚠️ API is not available yet.</p>
                    <p>Please complete <strong>Phase 4</strong> to enable API functionality.</p>
                    <p class="info">Phase 4では、Internal ALB と ECS API コンテナを作成します。</p>
                `;
            } catch (error) {
                resultDiv.innerHTML = `<p class="error">❌ Error: ${error.message}</p>`;
            } finally {
                apiTestButton.disabled = false;
            }
        });

        // Database Test Button (Phase 5で有効化)
        databaseTestButton.addEventListener('click', async () => {
            resultDiv.innerHTML = '<p class="info">⏳ Testing database connection...</p>';
        });
        
        // 初期メッセージ
        window.addEventListener('load', () => {
            resultDiv.innerHTML = `
                <p class="success">✨ Frontend is ready!</p>
                <p>This is <strong>Phase 3</strong> (ECS Frontend).</p>
                <p class="info">Click buttons above to test API and Database connections.</p>
            `;
        });
    </script>
</body>
</html>
EOF

index.htmlの説明:

  • シンプルで見やすいUI
  • 「API Test」ボタン(Phase 4で機能)
  • 「Database Test」ボタン(Phase 5で有効化)
  • レスポンシブデザイン対応
ステップ4: 作成確認
# ファイルが作成されたか確認
ls -la

# 出力例:
# Dockerfile
# index.html

7-1-3. APIのファイル準備

ステップ1: apiディレクトリ作成
# プロジェクトルートに移動
cd my-ecs-project

# apiディレクトリ作成
mkdir -p api
cd api
ステップ2: Dockerfile作成
cat > Dockerfile << 'EOF'
# ビルドステージ
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod ./
COPY main.go ./
RUN go mod download
RUN go build -o server main.go

# 実行ステージ
FROM alpine:latest
RUN apk add --no-cache curl ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]
EOF

Dockerfileの説明:

  • マルチステージビルドでイメージサイズを削減
  • Go 1.23を使用
  • ポート8080で起動

ステップ3: main.go作成
cat > main.go << 'EOF'
package main

import (
    "encoding/json"
    "log"
    "net/http"
    "os"
    "time"
)

type Response struct {
    Message   string    `json:"message"`
    Timestamp time.Time `json:"timestamp"`
    Hostname  string    `json:"hostname"`
    Status    string    `json:"status"`
}

func healthHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    hostname, _ := os.Hostname()
    
    response := Response{
        Message:   "API is healthy",
        Timestamp: time.Now(),
        Hostname:  hostname,
        Status:    "ok",
    }
    
    json.NewEncoder(w).Encode(response)
}

func apiHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("Access-Control-Allow-Origin", "*")
    
    hostname, _ := os.Hostname()
    
    response := Response{
        Message:   "Hello from ECS Fargate API!",
        Timestamp: time.Now(),
        Hostname:  hostname,
        Status:    "success",
    }
    
    json.NewEncoder(w).Encode(response)
}

func main() {
    http.HandleFunc("/health", healthHandler)
    http.HandleFunc("/api/test", apiHandler)
    
    log.Println("API Server starting on :8080...")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}
EOF

main.goの説明:

  • /health: ヘルスチェック用エンドポイント
  • /api/test: API Test用エンドポイント
  • CORS対応
  • ホスト名を返す(どのコンテナから応答したかわかる)

ステップ4: go.mod作成
cat > go.mod << 'EOF'
module api

go 1.23
EOF
ステップ5: 作成確認
# ファイルが作成されたか確認
ls -la

# 出力例:
# Dockerfile
# main.go
# go.mod

7-2. AWS CLIでECRログイン

ステップ1: ECR URL取得

# Phase 2のディレクトリに移動
cd my-ecs-project/terraform/phase2-security

# ECR URLを取得
terraform output ecr_front_url
terraform output ecr_api_url

出力例:

ecr_front_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front"
ecr_api_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api"

📝 確認ポイント:

  • リポジトリ名に myproject/front のようにスラッシュが含まれている
  • これが正しい形式です

ステップ2: ECRにログイン

# AWSアカウントID部分を取得(最初の数字部分)
ECR_REGISTRY=$(terraform output -raw ecr_front_url | cut -d'/' -f1)
echo $ECR_REGISTRY
# 出力例: 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com

# ECRにログイン
aws ecr get-login-password --region ap-northeast-1 | \
  docker login --username AWS --password-stdin $ECR_REGISTRY

成功メッセージ:

Login Succeeded

ポイント:

  • ECRログインは12時間有効
  • 有効期限が切れたら再度ログインが必要

7-3. Frontendイメージのビルド & Push

ステップ1: ECR URL取得(変数に保存)

cd my-ecs-project/terraform/phase2-security

# ECR URLを変数に保存
ECR_FRONT_URL=$(terraform output -raw ecr_front_url)
echo $ECR_FRONT_URL
# 出力例: 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front

ステップ2: frontディレクトリに移動

cd my-ecs-project/front

ステップ3: Dockerイメージをビルド

# イメージをビルド(ECR URLを直接タグに指定)
docker build -t $ECR_FRONT_URL:latest .

実行結果:

[+] Building 15.2s (8/8) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 98B
 => [internal] load .dockerignore
 => [internal] load metadata for docker.io/library/nginx:alpine
 => [1/2] FROM docker.io/library/nginx:alpine
 => [internal] load build context
 => => transferring context: 1.23kB
 => [2/2] COPY index.html /usr/share/nginx/html/
 => exporting to image
 => => exporting layers
 => => writing image sha256:abc123...
 => => naming to 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front:latest

ステップ4: ECRにPush

docker push $ECR_FRONT_URL:latest

実行結果:

The push refers to repository [014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front]
5f70bf18a086: Pushed
a08e4f2d9859: Pushed
latest: digest: sha256:def456... size: 1234

ステップ5: Push成功確認

# ECRイメージ確認
aws ecr describe-images --repository-name myproject/front

実行結果:

{
    "imageDetails": [
        {
            "registryId": "014936403628",
            "repositoryName": "myproject/front",
            "imageDigest": "sha256:def456...",
            "imageTags": [
                "latest"
            ],
            "imageSizeInBytes": 9876543,
            "imagePushedAt": "2024-12-20T10:30:00+00:00",
            "imageScanStatus": {
                "status": "COMPLETE",
                "description": "The scan completed successfully"
            }
        }
    ]
}

✅ Push成功!


7-4. APIイメージのビルド & Push

ステップ1: ECR URL取得(変数に保存)

cd my-ecs-project/terraform/phase2-security

# ECR URLを変数に保存
ECR_API_URL=$(terraform output -raw ecr_api_url)
echo $ECR_API_URL
# 出力例: 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api

ステップ2: apiディレクトリに移動

cd my-ecs-project/api

ステップ3: Dockerイメージをビルド

# イメージをビルド
docker build -t $ECR_API_URL:latest .

実行結果:

[+] Building 45.3s (15/15) FINISHED
 => [internal] load build definition from Dockerfile
 => => transferring dockerfile: 250B
 => [internal] load .dockerignore
 => [internal] load metadata for docker.io/library/golang:1.23-alpine
 => [builder 1/5] FROM docker.io/library/golang:1.23-alpine
 => [internal] load build context
 => => transferring context: 890B
 => [builder 2/5] WORKDIR /app
 => [builder 3/5] COPY go.mod ./
 => [builder 4/5] COPY main.go ./
 => [builder 5/5] RUN go build -o server main.go
 => [stage-1 1/3] FROM docker.io/library/alpine:latest
 => [stage-1 2/3] RUN apk add --no-cache curl ca-certificates
 => [stage-1 3/3] COPY --from=builder /app/server .
 => exporting to image
 => => exporting layers
 => => writing image sha256:ghi789...
 => => naming to 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api:latest

ステップ4: ECRにPush

docker push $ECR_API_URL:latest

実行結果:

The push refers to repository [014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api]
1a2b3c4d5e6f: Pushed
7g8h9i0j1k2l: Pushed
latest: digest: sha256:jkl012... size: 2345

ステップ5: Push成功確認

# ECRイメージ確認
aws ecr describe-images --repository-name myproject/api

実行結果:

{
    "imageDetails": [
        {
            "registryId": "014936403628",
            "repositoryName": "myproject/api",
            "imageDigest": "sha256:jkl012...",
            "imageTags": [
                "latest"
            ],
            "imageSizeInBytes": 12345678,
            "imagePushedAt": "2024-12-20T10:35:00+00:00",
            "imageScanStatus": {
                "status": "COMPLETE",
                "description": "The scan completed successfully"
            }
        }
    ]
}

✅ Push成功!


7-5. シェルスクリプト化(推奨)

今後のイメージ更新を簡単にするため、シェルスクリプトを作成します。

7-5-1. スクリプト作成

cd my-ecs-project

cat > push-images.sh << 'SCRIPT'
#!/bin/bash

set -e  # エラーで停止

echo "========================================="
echo " Docker Image Push to ECR"
echo "========================================="
echo ""

# Terraform outputからECR URLを取得
cd terraform/phase2-security
ECR_FRONT_URL=$(terraform output -raw ecr_front_url)
ECR_API_URL=$(terraform output -raw ecr_api_url)
ECR_REGISTRY=$(echo $ECR_FRONT_URL | cut -d'/' -f1)

echo "ECR URLs:"
echo "  Front: $ECR_FRONT_URL"
echo "  API:   $ECR_API_URL"
echo ""

# ECRログイン
echo "Logging in to ECR..."
aws ecr get-login-password --region ap-northeast-1 | \
  docker login --username AWS --password-stdin $ECR_REGISTRY

echo "✅ ECR Login successful"
echo ""

# プロジェクトルートに移動
cd ../..

# Frontendイメージ
echo "========================================="
echo " Building Frontend image..."
echo "========================================="
cd front
docker build -t $ECR_FRONT_URL:latest .
echo "✅ Frontend image built"
echo ""

echo "Pushing Frontend image..."
docker push $ECR_FRONT_URL:latest
echo "✅ Frontend image pushed"
echo ""

# APIイメージ
echo "========================================="
echo " Building API image..."
echo "========================================="
cd ../api
docker build -t $ECR_API_URL:latest .
echo "✅ API image built"
echo ""

echo "Pushing API image..."
docker push $ECR_API_URL:latest
echo "✅ API image pushed"
echo ""

# Push確認
echo "========================================="
echo " Verifying images in ECR..."
echo "========================================="
echo "Frontend:"
aws ecr describe-images --repository-name myproject/front \
  --query 'imageDetails[0].[imageTags[0],imagePushedAt]' \
  --output table

echo ""
echo "API:"
aws ecr describe-images --repository-name myproject/api \
  --query 'imageDetails[0].[imageTags[0],imagePushedAt]' \
  --output table

echo ""
echo "========================================="
echo " ✅ All images pushed successfully!"
echo "========================================="
SCRIPT

chmod +x push-images.sh

7-5-2. スクリプト実行

./push-images.sh

メリット:

  • 一度の実行でFrontendとAPI両方をPush
  • ECR URLを自動取得
  • エラーハンドリング付き
  • Push後の確認も自動

8. 動作確認

8-1. AWSコンソールで確認

ECR Repository:

  1. AWSコンソール → ECR → Repositories
  2. 2つのRepositoryが作成されていることを確認
    • myproject/front(スラッシュあり)
    • myproject/api(スラッシュあり)

イメージの確認:

  1. Repository名をクリック
  2. latestタグのイメージが存在することを確認
  3. Image scan結果を確認(脆弱性の有無)

8-2. Terraform出力で確認

cd my-ecs-project/terraform/phase2-security
terraform output

結果:

ecr_front_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front"
ecr_front_arn = "arn:aws:ecr:ap-northeast-1:014936403628:repository/myproject/front"
ecr_api_url = "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/api"
ecr_api_arn = "arn:aws:ecr:ap-northeast-1:014936403628:repository/myproject/api"

確認ポイント:

  • リポジトリ名に myproject/front のようにスラッシュが含まれている

8-3. AWS CLIで確認

# Frontendイメージ確認
aws ecr describe-images --repository-name myproject/front \
  --query 'imageDetails[*].[imageTags[0],imageSizeInBytes,imagePushedAt]' \
  --output table

# APIイメージ確認
aws ecr describe-images --repository-name myproject/api \
  --query 'imageDetails[*].[imageTags[0],imageSizeInBytes,imagePushedAt]' \
  --output table

9. トラブルシューティング

9-1. エラー1: ECRログイン失敗

エラーメッセージ:

Error saving credentials: error storing credentials

原因:

  • Docker Desktopの認証情報ストアの問題

解決策:

# ~/.docker/config.jsonを確認
cat ~/.docker/config.json

# credsStoreを削除または修正
# "credsStore": "desktop" の行を削除

9-2. エラー2: イメージのpush失敗

エラーメッセージ:

denied: Your authorization token has expired

原因:

  • ECRログインの有効期限切れ(12時間)

解決策:

# 再度ログイン
aws ecr get-login-password --region ap-northeast-1 | \
  docker login --username AWS --password-stdin 014936403628.dkr.ecr.ap-northeast-1.amazonaws.com

9-3. エラー3: Dockerfileが見つからない

エラーメッセージ:

unable to prepare context: unable to find 'Dockerfile'

原因:

  • ディレクトリが間違っている

解決策:

# 正しいディレクトリに移動
cd my-ecs-project/front  # Frontendの場合
cd my-ecs-project/api    # APIの場合

# Dockerfileが存在することを確認
ls -la Dockerfile

9-4. エラー4: ECRリポジトリ名が間違っている

エラーメッセージ:

repository myproject-front not found

原因:

  • リポジトリ名が間違っている
  • 正しくは myproject/front(スラッシュあり)

確認方法:

# Terraform outputで正しいリポジトリ名を確認
cd terraform/phase2-security
terraform output ecr_front_url

# 出力例(正しい):
# "014936403628.dkr.ecr.ap-northeast-1.amazonaws.com/myproject/front"
#                                                      ↑
#                                            スラッシュがある

# AWSコンソールでも確認
# ECR → Repositories → リポジトリ名を確認

解決策:

# 正しいリポジトリ名でpush
ECR_FRONT_URL=$(terraform output -raw ecr_front_url)
echo $ECR_FRONT_URL  # スラッシュが含まれているか確認

docker build -t $ECR_FRONT_URL:latest .
docker push $ECR_FRONT_URL:latest

よくある間違い:

間違い 正しい
myproject-front myproject/front
myproject_front myproject/front
myprojectfront myproject/front

9-5. エラー5: Go buildエラー(API)

エラーメッセージ:

go: finding module for package ...
build failed

原因:

  • go.modが正しく作成されていない

解決策:

cd my-ecs-project/api

# go.modを再作成
cat > go.mod << 'EOF'
module api

go 1.23
EOF

# 再度ビルド
docker build -t $ECR_API_URL:latest .

9-6. エラー6: イメージが大きすぎる

現象:

  • pushに時間がかかる
  • ECRのストレージコストが高い

原因:

  • マルチステージビルドを使用していない

解決策(API Dockerfile改善):

# 現在のDockerfile(シングルステージ)
FROM golang:1.23
WORKDIR /app
COPY . .
RUN go build -o server .
CMD ["./server"]

# 改善版(マルチステージ)
FROM golang:1.23-alpine AS builder
WORKDIR /app
COPY go.mod ./
COPY main.go ./
RUN go mod download
RUN go build -o server main.go

FROM alpine:latest
RUN apk add --no-cache curl ca-certificates
WORKDIR /root/
COPY --from=builder /app/server .
EXPOSE 8080
CMD ["./server"]

効果:

  • イメージサイズが約1/10に削減(700MB → 70MB)
  • セキュリティリスクの低減

10. ベストプラクティス

10-1. イメージタグの付け方

❌ 悪い例:

# latestタグのみ
docker tag myproject-front:latest ecr.../myproject/front:latest
docker push ecr.../myproject/front:latest

✅ 良い例:

# latestタグ + バージョンタグ
docker build -t ecr.../myproject/front:latest .
docker tag ecr.../myproject/front:latest ecr.../myproject/front:v1.0.0
docker push ecr.../myproject/front:latest
docker push ecr.../myproject/front:v1.0.0

理由:

  • バージョン管理が容易
  • ロールバックが簡単
  • トラブル時にどのバージョンか特定できる

10-2. 環境変数の管理

❌ 悪い例:

# Dockerfile内にハードコード
ENV DB_PASSWORD="my-secret-password"

✅ 良い例:

# Dockerfileでは定義しない
# ECS Task Definitionで環境変数を設定
# または Secrets Managerを使用

10-3. .dockerignoreの活用

推奨設定:

# front/.dockerignore
node_modules/
.git/
*.log
.DS_Store
# api/.dockerignore
vendor/
.git/
*.log
.DS_Store
go.sum

効果:

  • ビルド時間の短縮
  • イメージサイズの削減
  • 不要なファイルの混入防止

11. 🎉 Phase 2完了!

11-1. できたこと

✅ ECR Repository × 2の作成
✅ Lifecycle Policyの設定
✅ Dockerイメージのビルド & Push
front/api/ ディレクトリの作成
✅ Dockerfile と アプリケーションファイルの作成

11-2. Phase 2で作成したリソース(合計)

Phase リソース数 主要リソース
Phase 2-1 15 Security Group × 5
Phase 2-2 4 ECR Repository × 2
合計 19 -

11-3. 作成したファイル

ディレクトリ 主要ファイル
terraform/phase2-security/ ecr.tf, outputs.tf
front/ Dockerfile, index.html 等
api/ Dockerfile, main.go 等

📁 front/ api/ の詳細は GitHub: fargate-iac-02 を参照

11-4. 🎯 マイルストーン2達成!

🎉 セキュリティ基盤が完成しました!

Security Group × 5、ECR × 2が稼働し、
DockerイメージがECRにpush完了しています。

次のPhaseでは、これらのイメージを使って
ECS Fargateでコンテナを起動します!

12. 次回予告

第6回: Phase 3-1 - Public ALB

次回は、Phase 3として以下を作成します:

  • Public ALB(Application Load Balancer)
  • Target Group
  • Listener
  • ALBの役割とヘルスチェック

Phase 3-1が完了すると、外部からのアクセスを受け付ける準備が整います!


13. 参考リンク


(続く)

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?