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

Claude Code로 AWS 배포 자동화하기: CDK, GitHub Actions, IAM, 로그, 롤백

Claude Code로 작은 API를 AWS에 안전하게 배포하는 CDK, OIDC, 최소 권한, 로그, 롤백, 비용 가드레일 실전 가이드.

Claude Code로 AWS 배포 자동화하기: CDK, GitHub Actions, IAM, 로그, 롤백

Claude Code로 AWS 배포를 자동화할 때 가장 위험한 패턴은 “AWS에 배포하는 구조를 만들어줘”라고만 말하고 결과를 그대로 믿는 것입니다. 운영 가능한 배포는 멋진 구성도가 아니라, 실행되는 IaC, 제한된 IAM 권한, 시크릿 관리, 로그, 롤백, 비용 제한까지 포함한 반복 가능한 절차입니다.

이 글에서는 작은 웹 앱이나 API에 맞는 안전한 경로 하나를 구현합니다. 선택한 구조는 API Gateway + Lambda + AWS CDK + GitHub Actions입니다. CDK는 Infrastructure as Code, 즉 AWS 콘솔에서 손으로 만들 리소스를 코드로 정의하는 방식입니다. IAM은 “누가 어떤 AWS 작업을 할 수 있는지”를 정하는 권한 체계이고, OIDC는 GitHub Actions가 장기 AWS 키 없이 임시 자격 증명으로 AWS 역할을 맡게 하는 방식입니다.

Claude Code는 저장소를 읽고 여러 파일을 수정하며, cdk synthcdk diff를 실행하고, 실패 로그를 원인과 수정안으로 바꾸는 데 유용합니다. 제품 동작은 Claude Code 공식 문서를 기준으로 확인하고, AWS 관련 내용은 AWS CDK NodejsFunction, API Gateway Lambda proxy integration, Lambda environment variables, Lambda CloudWatch Logs, IAM best practices를 참고하세요.

먼저 AWS 배포 대상을 정한다

서비스 선택이 흐리면 Claude Code도 과한 구성을 만들기 쉽습니다. 먼저 워크로드 모양을 정합니다.

구성적합한 경우장점주의할 점
Lambda + API Gateway작은 API, 문의 폼, Webhook, 가벼운 관리자 백엔드서버 관리가 적고 낮은 트래픽으로 시작하기 쉽다긴 작업, 지속 연결, 큰 컨테이너에는 맞지 않는다
ECS/Fargate + ALBDocker API, 상시 실행 서비스, API와 worker 혼합컨테이너 자유도가 높고 기존 앱 이전이 쉽다VPC, ALB, task definition, 스케일링 설계가 늘어난다
Amplify 또는 S3 + CloudFront정적 사이트, SPA, 프런트엔드 중심 앱CDN 배포가 빠르고 운영 부담이 낮다API, 인증, 백엔드 작업은 별도 설계가 필요하다

이 글은 상담에서 가장 자주 나오는 “작은 제품 API를 과하게 만들지 않고 AWS에 안전하게 올리고 싶다”는 상황에 맞춰 Lambda + API Gateway를 구현합니다. 더 깊게 보려면 Claude Code AWS Lambda 가이드, Claude Code AWS IAM 가이드, Claude Code API Gateway 가이드, Claude Code 보안 모범 사례를 함께 보세요.

flowchart LR
  User[사용자] --> Api[API Gateway]
  Api --> Fn[Lambda Node.js API]
  Fn --> Secret[Secrets Manager]
  Fn --> Logs[CloudWatch Logs]
  GitHub[GitHub Actions OIDC] --> CDK[AWS CDK deploy]
  CDK --> Api
  CDK --> Fn

실제로 많이 쓰는 4가지 유스케이스

첫 번째는 문의 폼이나 리드 수집 폼입니다. 프런트엔드는 기존 사이트에 그대로 두고 제출 API만 API Gateway + Lambda로 분리합니다. API Gateway throttling, API key, CloudWatch Logs를 붙이면 스팸과 장애 원인을 찾기 쉬워집니다.

두 번째는 SaaS MVP API입니다. 처음부터 VPC, RDS, EKS를 만들지 말고 /v1/health, /v1/contact, /v1/webhook처럼 작은 경계로 시작합니다. Claude Code에는 “지금은 Lambda로 충분하며 명확한 요구가 생길 때까지 VPC와 컨테이너를 만들지 않는다”고 적어둡니다.

세 번째는 사내 자동화 Webhook입니다. Slack 알림, CMS 배포 콜백, 승인 API, 가벼운 운영 작업은 짧게 끝나는 Lambda와 잘 맞습니다. 재시도, 장시간 처리, fan-out이 필요해지면 SQS나 Step Functions를 의도적으로 추가합니다.

네 번째는 수동 배포를 CI/CD로 옮기는 작업입니다. 로컬 노트북에서 배포하거나 AWS 콘솔을 클릭하는 팀이라면, Claude Code가 CDK와 GitHub Actions로 이 과정을 옮기는 데 도움을 줄 수 있습니다. 다만 production 승인과 rollback 기준은 사람이 정해야 합니다.

Claude Code에 배포 규칙을 준다

프로젝트 루트에 CLAUDE.md를 만들고 제약을 적습니다. 작은 API가 비싼 플랫폼 리라이트로 변하는 것을 막기 위해서입니다.

# AWS deployment rules

- Use AWS CDK v2 and TypeScript.
- Target region: ap-northeast-2 unless explicitly changed.
- Do not create VPC resources for this small API unless required.
- Prefer API Gateway + Lambda for the first release.
- Runtime secrets must come from AWS Secrets Manager, not plaintext env vars.
- Use IAM grants such as secret.grantRead(handler) instead of wildcard policies.
- Add CloudWatch log retention and reserved concurrency.
- Before deploy, run npm test, npx cdk synth, and npx cdk diff.
- Never paste AWS access keys into files or GitHub Secrets.

Claude Code에는 산출물과 금지 사항을 같이 요청합니다.

이 저장소에 CDK v2 SmallApiStack을 추가해 주세요.
API Gateway REST API와 Node.js 20 Lambda로 /v1/health, /v1/contact를 만듭니다.
런타임 설정은 Secrets Manager의 prod/claude-code-demo/api에서 읽습니다.
Lambda 실행 역할은 최소 권한으로 유지하고 CloudWatch 로그 보존 기간, reserved concurrency, API Gateway throttling을 넣어 주세요.
GitHub Actions 배포는 OIDC만 사용하고 장기 AWS key는 사용하지 마세요.
수정 후 npm test, npx cdk synth, npx cdk diff를 실행하고 결과를 설명해 주세요.

CDK 프로젝트 만들기

Node.js, AWS CLI v2, AWS CDK CLI, 인증된 AWS profile이 필요합니다. 아래 명령은 Bash 기준입니다. Windows에서는 WSL이나 Git Bash가 편합니다.

mkdir claude-code-aws-api
cd claude-code-aws-api
npx cdk init app --language typescript
npm install @aws-sdk/client-secrets-manager
npm install --save-dev esbuild @types/aws-lambda
mkdir -p lambda
aws sts get-caller-identity

런타임 설정을 먼저 만듭니다. 실제 API key나 DB password는 Git에 커밋하거나 평문 환경 변수로 넣지 않습니다.

aws secretsmanager create-secret \
  --name prod/claude-code-demo/api \
  --secret-string '{"supportQueue":"aws-consulting"}' \
  --region ap-northeast-2

Lambda handler 구현

lambda/handler.ts를 만듭니다. 운영에 필요한 메타데이터만 로그에 남기고, 요청 본문 전체나 secret 값은 출력하지 않습니다.

import type {
  APIGatewayProxyEvent,
  APIGatewayProxyResult,
} from "aws-lambda";
import {
  GetSecretValueCommand,
  SecretsManagerClient,
} from "@aws-sdk/client-secrets-manager";

const secrets = new SecretsManagerClient({});
let cachedConfig: Record<string, string> | undefined;

async function loadConfig(): Promise<Record<string, string>> {
  if (cachedConfig) return cachedConfig;

  const secretArn = process.env.SECRET_ARN;
  if (!secretArn) throw new Error("SECRET_ARN is not configured");

  const result = await secrets.send(
    new GetSecretValueCommand({ SecretId: secretArn }),
  );
  cachedConfig = JSON.parse(result.SecretString ?? "{}");
  return cachedConfig;
}

function json(statusCode: number, body: unknown): APIGatewayProxyResult {
  return {
    statusCode,
    headers: {
      "content-type": "application/json",
      "cache-control": "no-store",
    },
    body: JSON.stringify(body),
  };
}

export async function handler(
  event: APIGatewayProxyEvent,
): Promise<APIGatewayProxyResult> {
  try {
    if (event.httpMethod === "GET" && event.path.endsWith("/v1/health")) {
      return json(200, {
        ok: true,
        stage: process.env.STAGE ?? "dev",
      });
    }

    if (event.httpMethod === "POST" && event.path.endsWith("/v1/contact")) {
      const body = JSON.parse(event.body ?? "{}") as {
        email?: string;
        message?: string;
      };

      if (!body.email || !body.message) {
        return json(400, { ok: false, message: "email and message required" });
      }

      const config = await loadConfig();
      const emailDomain = body.email.split("@")[1] ?? "unknown";

      console.log(
        JSON.stringify({
          event: "contact_received",
          emailDomain,
          messageLength: body.message.length,
        }),
      );

      return json(202, {
        ok: true,
        routedTo: config.supportQueue ?? "manual",
      });
    }

    return json(404, { ok: false, message: "not found" });
  } catch (error) {
    console.error("handler_error", error);
    return json(500, { ok: false, message: "internal error" });
  }
}

CDK stack 구현

lib/small-api-stack.ts를 아래로 교체합니다. 핵심은 appSecret.grantRead(handler), 한 달 로그 보존, reserved concurrency, API Gateway throttling, 그리고 production 로그에 요청 본문을 남기지 않는 dataTraceEnabled: false입니다.

import * as cdk from "aws-cdk-lib";
import * as apigateway from "aws-cdk-lib/aws-apigateway";
import * as iam from "aws-cdk-lib/aws-iam";
import * as lambda from "aws-cdk-lib/aws-lambda";
import * as nodejs from "aws-cdk-lib/aws-lambda-nodejs";
import * as logs from "aws-cdk-lib/aws-logs";
import * as secretsmanager from "aws-cdk-lib/aws-secretsmanager";
import { Construct } from "constructs";
import * as path from "path";

export class SmallApiStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const appSecret = secretsmanager.Secret.fromSecretNameV2(
      this,
      "AppSecret",
      "prod/claude-code-demo/api",
    );

    const handler = new nodejs.NodejsFunction(this, "ApiHandler", {
      functionName: "claude-code-small-api-prod",
      entry: path.join(__dirname, "../lambda/handler.ts"),
      handler: "handler",
      runtime: lambda.Runtime.NODEJS_20_X,
      architecture: lambda.Architecture.ARM_64,
      memorySize: 256,
      timeout: cdk.Duration.seconds(10),
      logRetention: logs.RetentionDays.ONE_MONTH,
      reservedConcurrentExecutions: 20,
      environment: {
        STAGE: "prod",
        SECRET_ARN: appSecret.secretArn,
      },
      bundling: {
        minify: true,
        sourceMap: true,
        externalModules: ["@aws-sdk/*"],
      },
    });

    appSecret.grantRead(handler);

    handler.addToRolePolicy(
      new iam.PolicyStatement({
        actions: ["cloudwatch:PutMetricData"],
        resources: ["*"],
        conditions: {
          StringEquals: {
            "cloudwatch:namespace": "ClaudeCodeLab/SmallApi",
          },
        },
      }),
    );

    const api = new apigateway.RestApi(this, "SmallApi", {
      restApiName: "claude-code-small-api",
      cloudWatchRole: true,
      deployOptions: {
        stageName: "prod",
        metricsEnabled: true,
        loggingLevel: apigateway.MethodLoggingLevel.INFO,
        dataTraceEnabled: false,
        throttlingRateLimit: 20,
        throttlingBurstLimit: 40,
      },
    });

    const v1 = api.root.addResource("v1");
    v1.addResource("health").addMethod(
      "GET",
      new apigateway.LambdaIntegration(handler),
    );

    v1.addResource("contact").addMethod(
      "POST",
      new apigateway.LambdaIntegration(handler),
      { apiKeyRequired: true },
    );

    const apiKey = api.addApiKey("ClientApiKey", {
      apiKeyName: "claude-code-small-api-prod-client",
    });

    const usagePlan = api.addUsagePlan("BasicUsagePlan", {
      throttle: { rateLimit: 10, burstLimit: 20 },
      quota: { limit: 1000, period: apigateway.Period.MONTH },
    });
    usagePlan.addApiKey(apiKey);
    usagePlan.addApiStage({ stage: api.deploymentStage });

    new cdk.CfnOutput(this, "ApiUrl", { value: api.url });
  }
}

bin/ 아래 entry file도 맞춥니다.

#!/usr/bin/env node
import * as cdk from "aws-cdk-lib";
import { SmallApiStack } from "../lib/small-api-stack";

const app = new cdk.App();

new SmallApiStack(app, "SmallApiProdStack", {
  env: {
    account: process.env.CDK_DEFAULT_ACCOUNT,
    region: process.env.CDK_DEFAULT_REGION ?? "ap-northeast-2",
  },
});

배포 전 diff 확인

CDK를 특정 계정과 리전에 처음 배포할 때는 bootstrap이 필요합니다.

export AWS_REGION=ap-northeast-2
export AWS_ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

npx cdk bootstrap "aws://${AWS_ACCOUNT_ID}/${AWS_REGION}"
npm test
npx cdk synth
npx cdk diff SmallApiProdStack
npx cdk deploy SmallApiProdStack --require-approval never

cdk diff에서 IAM, Lambda 이름, 로그 보존 기간, throttling을 확인합니다. Claude Code에게 diff를 설명하게 하고, 필요성을 설명하지 못하는 권한은 production 전에 사람이 검토합니다.

API 테스트와 로그 확인

CloudFormation output에서 API URL을 가져오고 두 route를 호출합니다.

API_URL=$(aws cloudformation describe-stacks \
  --stack-name SmallApiProdStack \
  --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
  --output text)

curl "${API_URL}v1/health"

KEY_ID=$(aws apigateway get-api-keys \
  --name-query claude-code-small-api-prod-client \
  --query "items[0].id" \
  --output text)

API_KEY=$(aws apigateway get-api-key \
  --api-key "$KEY_ID" \
  --include-value \
  --query "value" \
  --output text)

curl -X POST "${API_URL}v1/contact" \
  -H "content-type: application/json" \
  -H "x-api-key: ${API_KEY}" \
  -d '{"email":"masa@example.com","message":"AWS deployment consultation"}'

CloudWatch Logs에서 Lambda 로그를 확인합니다.

aws logs tail "/aws/lambda/claude-code-small-api-prod" \
  --since 1h \
  --follow

로그가 몇 분 늦게 보일 수 있습니다. 방금 배포했는데 로그가 없다는 이유만으로 실패라고 판단하지 마세요.

GitHub Actions OIDC로 배포

AWS Access Key를 GitHub Secrets에 저장하지 않습니다. AWS IAM OIDC provider와 배포 role을 만들고, trust policy를 특정 repository와 branch로 제한합니다.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": "repo:your-org/your-repo:ref:refs/heads/main"
        }
      }
    }
  ]
}

.github/workflows/deploy-aws.yml:

name: deploy-aws-cdk

on:
  push:
    branches: ["main"]
  workflow_dispatch:

permissions:
  id-token: write
  contents: read

concurrency:
  group: prod-aws-cdk
  cancel-in-progress: false

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm

      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-cdk-deploy-prod
          aws-region: ap-northeast-2

      - run: npm ci
      - run: npm test
      - run: npx cdk synth
      - run: npx cdk diff SmallApiProdStack
      - run: npx cdk deploy SmallApiProdStack --require-approval never

배포 role의 permission policy는 CDK bootstrap 방식, CloudFormation execution role, 조직의 permission boundary에 따라 달라집니다. production에서 AdministratorAccess를 기본값으로 두지 말고, OIDC trust condition을 좁힌 뒤 IAM Access Analyzer와 permission boundary로 줄이는 접근이 현실적입니다.

비용 가드레일 추가

작은 serverless API도 반복 호출, 스팸, 과도한 로그 때문에 비용이 늘 수 있습니다. 이 stack은 API throttling, usage plan, Lambda reserved concurrency를 포함합니다. production 전에는 AWS Budgets도 만듭니다.

{
  "BudgetName": "small-api-monthly-guardrail",
  "BudgetLimit": {
    "Amount": "20",
    "Unit": "USD"
  },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST"
}
[
  {
    "Notification": {
      "NotificationType": "ACTUAL",
      "ComparisonOperator": "GREATER_THAN",
      "Threshold": 80,
      "ThresholdType": "PERCENTAGE"
    },
    "Subscribers": [
      {
        "SubscriptionType": "EMAIL",
        "Address": "owner@example.com"
      }
    ]
  }
]
aws budgets create-budget \
  --account-id "$AWS_ACCOUNT_ID" \
  --budget file://budget.json \
  --notifications-with-subscribers file://notifications-with-subscribers.json

Budgets 그래프와 알림은 생성 직후 바로 보이지 않을 수 있습니다. 비용 문제가 생긴 뒤가 아니라 출시 전에 설정하세요.

롤백과 실패 모드

첫 Lambda + API Gateway 서비스에서는 복잡한 blue/green보다 나쁜 commit을 되돌리고 다시 배포하는 절차가 더 실용적일 때가 많습니다.

git revert <bad-commit-sha>
git push origin main

이후 API, CloudFormation events, 로그를 확인합니다.

curl "${API_URL}v1/health"
aws cloudformation describe-stack-events \
  --stack-name SmallApiProdStack \
  --max-items 20
aws logs tail "/aws/lambda/claude-code-small-api-prod" --since 30m

흔한 실수는 세 가지입니다. 첫째, CDK 배포 후 AWS 콘솔에서 손으로 수정해 diff가 혼란스러워지는 경우입니다. 둘째, secret을 Lambda 환경 변수나 로그에 남기는 경우입니다. 셋째, production에서 API Gateway request body tracing을 켜서 민감한 payload가 로그에 남는 경우입니다. 위 stack은 dataTraceEnabled: false로 세 번째 문제를 막지만, 팀 리뷰 습관은 별도로 필요합니다.

Claude Code에 배포 전 리뷰를 시킨다

Claude Code 리뷰에는 근거를 요구합니다.

AWS 배포 전 리뷰를 해 주세요.
확인 항목:
- CDK diff가 과한 IAM wildcard를 추가하는지
- Lambda가 secret을 Secrets Manager에서만 읽는지
- API Gateway에 throttling, usage plan, logging 설정이 있는지
- CloudWatch Logs에 개인정보나 전체 요청 본문이 노출될 수 있는지
- rollback 절차가 문서화되어 있는지
- npm test, cdk synth, cdk diff가 통과했는지

severity, file, line, fix를 표로 반환해 주세요.

Claude Code는 최종 production 승인자가 아닙니다. 계정 경계, 비용 영향, 보안 예외는 사람이 책임져야 합니다. 다만 리뷰 루틴을 반복 가능하게 만들어 같은 실수를 줄일 수 있습니다.

정리

AWS 배포에서 Claude Code의 가치는 CDK 코드를 빨리 쓰는 데만 있지 않습니다. 서비스 선택, IaC, CI/CD, IAM, secret, 로그, rollback, 비용 가드레일을 하나의 운영 습관으로 묶는 데 있습니다.

작은 Web API는 Lambda + API Gateway + CDK로 시작해도 충분한 경우가 많습니다. 항상 켜져 있는 컨테이너가 필요해지면 ECS/Fargate로, 정적 프런트엔드 중심이면 S3 + CloudFront나 Amplify로 확장합니다. 처음부터 크게 만드는 것보다 작고 검토 가능하며 되돌릴 수 있는 경계를 만드는 것이 중요합니다.

기존 저장소에 맞춰 Claude Code 규칙, AWS CDK 배포, GitHub Actions OIDC, IAM 최소 권한, 운영 체크를 설계하고 싶다면 Claude Code consulting을 예약하세요. Masa가 저장소 구조, AWS 계정 상태, 배포 실패 기록을 함께 보고 실제로 적용 가능한 개선안으로 정리합니다.

실제로 해보면 AWS 계정 준비가 되어 있다는 전제에서 이런 작은 lead API는 반나절에서 하루 안에 배포, 로그, 비용 알림까지 만들 수 있습니다. 가장 조심해야 할 부분은 Lambda 코드가 아니라 IAM, secret, rollback, 비용 가시성입니다. Claude Code는 구현 속도를 올리고, 사람은 production risk를 책임지는 구도가 안전합니다.

#Claude Code #AWS 배포 #CDK #Lambda #GitHub Actions #IAM
무료

무료 PDF: Claude Code 치트시트

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

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

Masa

작성자 소개

Masa

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