Use Cases (업데이트: 2026. 6. 2.)

Claude Code × AWS ECS/Fargate 완전 가이드 | 안전한 컨테이너 배포 자동화

Claude Code로 ECS/Fargate 배포: Docker, ECR, 태스크 정의, 로그, IAM, 운영 함정까지.

Claude Code × AWS ECS/Fargate 완전 가이드 | 안전한 컨테이너 배포 자동화

AWS에서 컨테이너를 운영하려면 ECS/Fargate가 좋은 선택지가 됩니다. 하지만 어려운 부분은 Dockerfile 자체가 아닙니다. ECR, 태스크 정의, IAM 역할, Secrets Manager, 로드 밸런서, CloudWatch Logs, 헬스 체크, 비용 통제를 한 번에 맞춰야 합니다.

이 글에서는 Claude Code를 검토 없는 자동화 도구가 아니라 구현 보조자로 사용합니다. 태스크 정의는 컨테이너 실행 명세서, Fargate는 서버 관리를 AWS에 맡기는 실행 환경, execution role은 ECS가 이미지를 pull하고 secret과 로그를 다루는 권한, task role은 애플리케이션 코드가 AWS API를 호출할 때 쓰는 권한입니다.

Masa가 작은 Node.js API를 Fargate로 옮겼을 때 첫 실패는 Docker가 아니라 헬스 체크였습니다. 앱 기동에 약 40초가 걸리는데 ECS가 너무 빨리 체크를 시작해 새 태스크가 준비되기 전에 교체되었습니다. Claude Code는 파일을 빠르게 만들 수 있지만, 기동 시간, private network, 로그, 롤백, IAM 경계를 명확히 전달해야 합니다.

목표 아키텍처

Developer
  |
  | docker build / push
  v
Amazon ECR ----> Amazon ECS Service on AWS Fargate
                       |
                       | pulls secrets / writes logs
                       v
Secrets Manager     CloudWatch Logs
                       ^
                       |
Application Load Balancer -> /health -> Node.js container

예시는 ap-northeast-1, 기존 VPC, 기존 ALB target group을 기준으로 합니다. VPC와 ALB까지 IaC로 만들고 싶다면 내부 글인 Claude Code × AWS CloudFormation/CDK 가이드와 함께 보세요. 권한 설계가 불안하다면 먼저 Claude Code × AWS IAM 가이드를 확인하는 것이 좋습니다.

실제 사용 사례 3가지

사용 사례Fargate가 맞는 이유주의점
SaaS REST APIALB 뒤에서 2개 이상의 태스크를 유지하고 롤링 배포 가능DB 연결 수와 헬스 체크를 먼저 설계
관리자 백엔드EC2 패치나 AMI 관리 없이 작게 시작 가능desiredCount를 0으로 두면 첫 요청이 느려짐
배치와 API 공용 이미지같은 이미지를 service와 run-task에서 재사용task role은 최소 권한으로 제한

짧고 이벤트 기반인 작업은 Claude Code × AWS Lambda 가이드가 더 단순할 수 있습니다. 상시 HTTP, 긴 요청, Docker 일관성, Lambda에 맞지 않는 런타임이 필요할 때 Fargate를 선택하세요.

1. 헬스 체크가 있는 최소 API

먼저 로컬에서 동작하는 앱을 만듭니다. /health는 ECS와 ALB가 함께 보기 때문에 가볍게 유지해야 합니다. DB가 잠깐 느려졌다는 이유로 500을 반환하면 정상 컨테이너까지 교체될 수 있습니다.

{
  "scripts": {
    "start": "node src/server.js"
  },
  "dependencies": {
    "express": "^4.19.2"
  }
}
// src/server.js
const express = require("express");

const app = express();
const port = Number(process.env.PORT || 3000);

app.get("/health", (_req, res) => {
  res.status(200).json({
    ok: true,
    service: "myapp",
    time: new Date().toISOString(),
  });
});

app.get("/", (_req, res) => {
  res.json({ message: "Hello from ECS Fargate" });
});

app.listen(port, "0.0.0.0", () => {
  console.log(`myapp listening on ${port}`);
});
# Dockerfile
FROM node:22-alpine

WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

COPY src ./src
ENV NODE_ENV=production
ENV PORT=3000
EXPOSE 3000

CMD ["node", "src/server.js"]

ECS로 올리기 전에 curl -f http://localhost:3000/health가 성공하는지 확인하세요. Claude Code에 요청할 때는 ALB와 ECS가 같은 health path를 본다는 점, 앱이 0.0.0.0에 listen한다는 점, startPeriod로 기동 시간을 보호한다는 점을 함께 적습니다.

2. ECR로 빌드하고 push

아래 스크립트는 필요한 경우 ECR repository를 만들고, 로그인하고, 이미지를 빌드한 뒤 버전 태그로 push합니다. latest만 쓰면 롤백과 감사가 어려워집니다.

set -euo pipefail

export AWS_REGION="ap-northeast-1"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export ECR_REPOSITORY="myapp"
export IMAGE_TAG="$(git rev-parse --short HEAD 2>/dev/null || date +%Y%m%d%H%M%S)"
export IMAGE_URI="${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${IMAGE_TAG}"

aws ecr describe-repositories \
  --repository-names "${ECR_REPOSITORY}" \
  --region "${AWS_REGION}" >/dev/null 2>&1 || \
aws ecr create-repository \
  --repository-name "${ECR_REPOSITORY}" \
  --image-scanning-configuration scanOnPush=true \
  --region "${AWS_REGION}"

aws ecr get-login-password --region "${AWS_REGION}" | \
  docker login --username AWS --password-stdin \
  "${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com"

docker build -t "${IMAGE_URI}" .
docker push "${IMAGE_URI}"

echo "Pushed ${IMAGE_URI}"

AWS 공식 ECR 흐름도 get-login-password를 사용합니다. CI에서는 장기 AWS access key를 저장하기보다 GitHub Actions OIDC와 단기 자격 증명을 쓰는 구성이 더 안전합니다.

3. ECS 태스크 정의 등록

Fargate 태스크는 awsvpc 네트워크 모드를 사용합니다. Secrets Manager 값을 주입할 때는 execution role에 secretsmanager:GetSecretValue가 필요합니다. 고객 관리 KMS 키를 사용한다면 kms:Decrypt도 필요합니다. task role은 애플리케이션 코드용이며 image pull이나 로그 설정용이 아닙니다.

set -euo pipefail

export AWS_REGION="ap-northeast-1"
export AWS_ACCOUNT_ID="$(aws sts get-caller-identity --query Account --output text)"
export IMAGE_URI="${IMAGE_URI:?Run the ECR push script first}"
export EXECUTION_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/ecsTaskExecutionRole"
export TASK_ROLE_ARN="arn:aws:iam::${AWS_ACCOUNT_ID}:role/myapp-task-role"
export SECRET_ARN="arn:aws:secretsmanager:${AWS_REGION}:${AWS_ACCOUNT_ID}:secret:prod/myapp/DATABASE_URL"

aws logs create-log-group --log-group-name /ecs/myapp --region "${AWS_REGION}" 2>/dev/null || true
aws logs put-retention-policy --log-group-name /ecs/myapp --retention-in-days 30 --region "${AWS_REGION}"

cat > ecs-task-definition.json <<EOF
{
  "family": "myapp-task",
  "networkMode": "awsvpc",
  "requiresCompatibilities": ["FARGATE"],
  "cpu": "512",
  "memory": "1024",
  "executionRoleArn": "${EXECUTION_ROLE_ARN}",
  "taskRoleArn": "${TASK_ROLE_ARN}",
  "runtimePlatform": {
    "cpuArchitecture": "X86_64",
    "operatingSystemFamily": "LINUX"
  },
  "containerDefinitions": [
    {
      "name": "app",
      "image": "${IMAGE_URI}",
      "essential": true,
      "portMappings": [
        { "containerPort": 3000, "hostPort": 3000, "protocol": "tcp" }
      ],
      "environment": [
        { "name": "NODE_ENV", "value": "production" },
        { "name": "PORT", "value": "3000" }
      ],
      "secrets": [
        { "name": "DATABASE_URL", "valueFrom": "${SECRET_ARN}" }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/ecs/myapp",
          "awslogs-region": "${AWS_REGION}",
          "awslogs-stream-prefix": "ecs"
        }
      },
      "healthCheck": {
        "command": ["CMD-SHELL", "wget -qO- http://localhost:3000/health || exit 1"],
        "interval": 30,
        "timeout": 5,
        "retries": 3,
        "startPeriod": 60
      }
    }
  ]
}
EOF

aws ecs register-task-definition \
  --cli-input-json file://ecs-task-definition.json \
  --region "${AWS_REGION}"

startPeriod는 작지만 중요합니다. 애플리케이션이 기동할 시간을 주기 전에 ECS가 실패로 판단하는 문제를 막아줍니다.

4. Fargate 서비스 생성

다음 명령은 기존 private subnet, task security group, ALB target group을 사용합니다. task security group은 ALB security group에서 오는 3000번 포트만 허용해야 합니다.

set -euo pipefail

export AWS_REGION="ap-northeast-1"
export CLUSTER_NAME="myapp-cluster"
export SERVICE_NAME="myapp-service"
export TASK_FAMILY="myapp-task"
export SUBNET_1="subnet-xxxxxxxx"
export SUBNET_2="subnet-yyyyyyyy"
export TASK_SECURITY_GROUP="sg-xxxxxxxx"
export TARGET_GROUP_ARN="arn:aws:elasticloadbalancing:ap-northeast-1:123456789012:targetgroup/myapp/abc123"

aws ecs create-cluster \
  --cluster-name "${CLUSTER_NAME}" \
  --region "${AWS_REGION}" >/dev/null

aws ecs create-service \
  --cluster "${CLUSTER_NAME}" \
  --service-name "${SERVICE_NAME}" \
  --task-definition "${TASK_FAMILY}" \
  --desired-count 2 \
  --launch-type FARGATE \
  --platform-version LATEST \
  --health-check-grace-period-seconds 90 \
  --network-configuration "awsvpcConfiguration={subnets=[${SUBNET_1},${SUBNET_2}],securityGroups=[${TASK_SECURITY_GROUP}],assignPublicIp=DISABLED}" \
  --load-balancers "targetGroupArn=${TARGET_GROUP_ARN},containerName=app,containerPort=3000" \
  --region "${AWS_REGION}"

aws ecs wait services-stable \
  --cluster "${CLUSTER_NAME}" \
  --services "${SERVICE_NAME}" \
  --region "${AWS_REGION}"

private subnet에서 assignPublicIp=DISABLED를 사용하면 태스크가 ECR, CloudWatch Logs, Secrets Manager에 접근할 경로가 필요합니다. NAT Gateway나 VPC endpoint를 사용하세요. NAT는 편하지만 작은 테스트 환경에서는 비용의 큰 부분이 될 수 있습니다.

5. CloudWatch Logs와 ECS 이벤트 확인

ECS 장애 분석은 보통 service 이벤트, stopped task 이유, 애플리케이션 로그를 함께 봐야 합니다. Claude Code에 진단을 요청할 때도 세 가지를 같이 전달하세요.

export AWS_REGION="ap-northeast-1"
export CLUSTER_NAME="myapp-cluster"
export SERVICE_NAME="myapp-service"

aws ecs describe-services \
  --cluster "${CLUSTER_NAME}" \
  --services "${SERVICE_NAME}" \
  --query "services[0].events[0:5].[createdAt,message]" \
  --output table \
  --region "${AWS_REGION}"

aws ecs list-tasks \
  --cluster "${CLUSTER_NAME}" \
  --service-name "${SERVICE_NAME}" \
  --desired-status STOPPED \
  --region "${AWS_REGION}"

aws logs tail /ecs/myapp \
  --follow \
  --since 10m \
  --region "${AWS_REGION}"

자주 나오는 실패는 ECR pull 차단, secret 접근 거부, ALB가 task security group에 닿지 못함, 앱이 0.0.0.0이 아니라 localhost에만 listen하는 경우입니다.

Claude Code 구현 요청 템플릿

Node.js API를 AWS ECS/Fargate에 배포하는 구현을 만들어 주세요.

전제:
- Region: ap-northeast-1
- ECR repository: myapp
- Container port: 3000
- Health endpoint: /health
- ECS launch type: FARGATE
- Network mode: awsvpc
- Desired count: 2
- Task CPU/memory: 512 / 1024
- Secret: Secrets Manager에서 DATABASE_URL 주입
- Logs: CloudWatch Logs /ecs/myapp, retention 30 days

산출물:
1. production용 Dockerfile
2. ECR push용 bash script
3. ECS task definition 등록 script
4. Fargate service 생성 script
5. CloudWatch Logs 확인 script
6. execution role과 task role을 분리해 설명

제약:
- 의사코드 금지. 변수를 채우면 AWS CLI로 실행 가능해야 함
- secret 값을 직접 쓰지 말 것
- task를 public subnet에 직접 노출하지 말 것
- 마지막에 확인해야 할 AWS 공식 문서 포인트를 나열할 것

AWS 공식 문서 확인 포인트

구체적인 함정

첫째, execution role과 task role을 섞는 것입니다. ECR, logging, secret retrieval은 execution role입니다. DynamoDB, S3, SQS처럼 앱이 호출하는 권한은 task role입니다.

둘째, secret 리전이 맞지 않는 경우입니다. ECS task, Secrets Manager secret, KMS key의 리전이 맞아야 합니다. 멀티 리전에서는 secret ARN을 환경별 배포 변수로 관리하세요.

셋째, 비용입니다. Fargate는 사용량 기반이지만 ALB, NAT Gateway, CloudWatch Logs, ECR storage도 비용이 듭니다. 테스트 환경은 로그 보존 기간을 줄이고 사용하지 않는 service를 중지하며 NAT 사용량을 확인해야 합니다.

넷째, 헬스 체크가 너무 엄격한 경우입니다. /health는 가볍게 두고 깊은 의존성 검사는 /ready나 smoke test로 분리하세요.

다섯째, 이미지 아키텍처입니다. Apple Silicon에서 ARM64 이미지를 만들고 task는 X86_64로 정의하면 시작 실패가 납니다. docker buildx build --platform linux/amd64를 쓰거나 runtimePlatform을 맞추세요.

CTA와 다음 단계

개인 개발자는 이 스크립트를 작은 API에 붙여 보고 무료 Claude Code 치트시트로 프롬프트를 개선하면 좋습니다. 팀에서 ECS, IAM, CI/CD, 관측, 롤백을 함께 설계해야 한다면 Claude Code 교육 및 상담으로 시작하세요. 재사용 가능한 프롬프트와 리뷰 자료가 필요하면 ClaudeCodeLab 제품도 도움이 됩니다.

정리

ECS/Fargate는 Docker 이미지를 올리는 장소만이 아닙니다. IAM, 네트워크, secret, 로그, 헬스 체크, 비용이 결합된 설계 문제입니다. Claude Code는 운영 규칙을 먼저 주고 실행 가능한 파일을 만들게 할 때 가장 유용합니다.

이 흐름을 실제로 시도해 보니 가장 큰 개선은 Dockerfile이 아니라 프롬프트에서 나왔습니다. execution role과 task role 분리, CloudWatch Logs 명령 포함, health check grace period 설정을 명시하자 같은 실패가 반복되지 않았습니다. 첫 실행에서는 secret 권한 누락이 드러났지만, ECS 이벤트와 로그를 Claude Code에 다시 전달하자 IAM 수정과 재배포 절차가 한 번에 정리되었습니다.

#claude-code #aws #ecs #fargate #container #devops
무료

무료 PDF: Claude Code 치트시트

이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.

개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.

Masa

작성자 소개

Masa

Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.