Use Cases

Claude Code × AWS Lambda 완전 가이드 | 함수 생성부터 배포 자동화까지

Claude Code로 AWS Lambda 함수를 초고속 개발. 핸들러 생성, IAM 정책 설계, SAM 배포 자동화까지 API Gateway/S3/DynamoDB 연동 실전 코드로 완전 해설.

AWS Lambda 개발에서 이런 경험을 해본 적 있으신가요? 핸들러의 타입 정의가 번거롭고, IAM 정책을 매번 찾아보며 작성하고, SAM 템플릿 작성법을 잊어버린 경험… Claude Code는 이 모든 것을 한 번에 해결합니다.

Lambda 함수 구현부터 IAM 정책 생성, 로컬 테스트, 프로덕션 배포까지 — Claude Code를 활용한 초고속 AWS Lambda 개발의 전 과정을 실전 코드로 해설합니다.

왜 Claude Code × AWS Lambda인가

Lambda 개발의 “번거로운 부분”은 사실 대부분 보일러플레이트입니다.

  • 핸들러 함수의 타입 정의 (APIGatewayProxyHandler, S3Handler …)
  • 에러 핸들링과 응답 형식
  • IAM 정책의 최소 권한 설계
  • SAM / CloudFormation 템플릿 작성
  • 로컬 테스트 환경 설정

Claude Code는 이것들을 “이런 Lambda가 필요해”라는 한 마디로 생성해줍니다. 게다가 AWS CLI나 SAM CLI와 조합하면 코드 생성부터 배포까지 한 번에 실행할 수 있습니다.

환경 세팅

# 필요한 도구 확인
aws --version        # AWS CLI v2
sam --version        # SAM CLI 1.100+
node --version       # Node.js 20+

# AWS 자격증명 설정 (미설정인 경우)
aws configure
# → Access Key ID, Secret Access Key, Region (ap-northeast-2), Output (json)
<!-- CLAUDE.md에 추가 -->
## AWS 설정
- 기본 리전: ap-northeast-2 (서울)
- Lambda 런타임: nodejs20.x
- 배포 도구: AWS SAM
- 소스 디렉터리: src/functions/
- SAM 템플릿: template.yaml
- 스택 이름: my-app-{dev|staging|prod}

## IAM 정책 방침
- 최소 권한 원칙 엄수
- 와일드카드 (*) 사용 금지
- 리소스 ARN은 구체적으로 지정

구현 예시 1: API Gateway + Lambda (REST API)

가장 기본적인 패턴. HTTP 요청을 받아 처리하는 Lambda입니다.

Claude Code에 대한 지시

claude -p "
/users/{userId}에 GET 요청이 오면
DynamoDB의 Users 테이블에서 사용자 정보를 가져와 반환하는
Lambda 함수를 TypeScript로 구현해줘.

- 런타임: Node.js 20
- 테이블 이름: UsersTable (환경 변수로 받음)
- 에러 핸들링: 404/500을 적절히 반환
- 타입 정의: @types/aws-lambda 사용
- src/functions/getUser/index.ts에 출력
"

생성되는 구현 예시

// src/functions/getUser/index.ts
import {
  APIGatewayProxyHandler,
  APIGatewayProxyResult,
} from "aws-lambda";
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({ region: process.env.AWS_REGION });
const ddb = DynamoDBDocumentClient.from(client);

const response = (statusCode: number, body: unknown): APIGatewayProxyResult => ({
  statusCode,
  headers: {
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "*",
  },
  body: JSON.stringify(body),
});

export const handler: APIGatewayProxyHandler = async (event) => {
  const userId = event.pathParameters?.userId;

  if (!userId) {
    return response(400, { error: "userId is required" });
  }

  try {
    const result = await ddb.send(
      new GetCommand({
        TableName: process.env.USERS_TABLE!,
        Key: { userId },
      })
    );

    if (!result.Item) {
      return response(404, { error: `User '${userId}' not found` });
    }

    return response(200, result.Item);
  } catch (err) {
    console.error("DynamoDB error:", err);
    return response(500, { error: "Internal server error" });
  }
};

SAM 템플릿도 자동 생성

# template.yaml (발췌)
GetUserFunction:
  Type: AWS::Serverless::Function
  Properties:
    Handler: src/functions/getUser/index.handler
    Runtime: nodejs20.x
    Timeout: 10
    Environment:
      Variables:
        USERS_TABLE: !Ref UsersTable
    Policies:
      - DynamoDBReadPolicy:
          TableName: !Ref UsersTable
    Events:
      GetUserApi:
        Type: Api
        Properties:
          Path: /users/{userId}
          Method: GET

UsersTable:
  Type: AWS::DynamoDB::Table
  Properties:
    TableName: !Sub "${AWS::StackName}-users"
    BillingMode: PAY_PER_REQUEST
    AttributeDefinitions:
      - AttributeName: userId
        AttributeType: S
    KeySchema:
      - AttributeName: userId
        KeyType: HASH

구현 예시 2: S3 이벤트 트리거 Lambda

파일 업로드를 트리거로 처리를 실행하는 Lambda.

// src/functions/generateThumbnail/index.ts
import { S3Handler } from "aws-lambda";
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import sharp from "sharp";

const s3 = new S3Client({ region: process.env.AWS_REGION });

export const handler: S3Handler = async (event) => {
  const record = event.Records[0];
  const bucket = record.s3.bucket.name;
  const key = decodeURIComponent(record.s3.object.key.replace(/\+/g, " "));

  // thumbnails/ 접두사는 무시 (무한 루프 방지)
  if (key.startsWith("thumbnails/")) return;

  const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
  const buffer = Buffer.from(await Body!.transformToByteArray());

  const thumbnail = await sharp(buffer)
    .resize(200, 200, { fit: "cover" })
    .webp({ quality: 85 })
    .toBuffer();

  const thumbnailKey = `thumbnails/${key.replace(/\.[^.]+$/, ".webp")}`;
  await s3.send(
    new PutObjectCommand({
      Bucket: bucket,
      Key: thumbnailKey,
      Body: thumbnail,
      ContentType: "image/webp",
    })
  );

  console.log(`썸네일 생성 완료: s3://${bucket}/${thumbnailKey}`);
};

구현 예시 3: 스케줄 실행 Lambda (EventBridge)

# template.yaml의 EventBridge 설정
SendReminderFunction:
  Type: AWS::Serverless::Function
  Properties:
    Handler: src/functions/sendReminder/index.handler
    Runtime: nodejs20.x
    Timeout: 300
    Environment:
      Variables:
        DATABASE_URL: !Sub "{{resolve:secretsmanager:${AWS::StackName}/database-url}}"
        RESEND_API_KEY: !Sub "{{resolve:secretsmanager:${AWS::StackName}/resend-api-key}}"
    Events:
      DailyReminder:
        Type: Schedule
        Properties:
          Schedule: cron(0 0 * * ? *)  # UTC 0:00 = KST 9:00

IAM 정책을 Claude Code에 설계시키기

claude -p "
다음 Lambda에 필요한 최소 권한 IAM 정책을 JSON으로 생성:
- S3 my-uploads에서 GetObject
- DynamoDB ProcessingJobs에 PutItem/UpdateItem
- SQS ProcessingQueue에 SendMessage
- CloudWatch Logs에 쓰기

ARN은 리소스 이름으로부터 추론하여 구체적으로 작성할 것.
"
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::my-uploads/*"
    },
    {
      "Effect": "Allow",
      "Action": ["dynamodb:PutItem", "dynamodb:UpdateItem"],
      "Resource": "arn:aws:dynamodb:ap-northeast-2:*:table/ProcessingJobs"
    },
    {
      "Effect": "Allow",
      "Action": ["sqs:SendMessage"],
      "Resource": "arn:aws:sqs:ap-northeast-2:*:ProcessingQueue"
    },
    {
      "Effect": "Allow",
      "Action": ["logs:CreateLogGroup", "logs:CreateLogStream", "logs:PutLogEvents"],
      "Resource": "arn:aws:logs:*:*:log-group:/aws/lambda/*"
    }
  ]
}

로컬 테스트와 프로덕션 배포

# 빌드
sam build

# 로컬에서 API 실행
sam local start-api --port 3001

# 단발 테스트
sam local invoke GetUserFunction --event events/get-user.json

# Claude Code로 일괄 실행
claude -p "
sam build를 실행하고,
sam local invoke GetUserFunction --event events/test-get-user.json으로 결과 확인.
문제없으면 sam deploy --config-env dev를 실행.
"

주의해야 할 함정 5선

1. 콜드 스타트를 고려하지 않은 초기화

// ❌ 핸들러 내에서 매번 클라이언트 생성
export const handler = async () => {
  const ddb = new DynamoDBClient({});  // ← 매번 인스턴스화
};

// ✅ 모듈 스코프에서 1회만 초기화
const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler = async () => { /* ddb 재사용 */ };

2. 타임아웃을 기본값(3초)으로 방치 DynamoDB + 외부 API라면 최소 10-30초. 처리 내용에 맞춰 반드시 설정해야 합니다.

3. Secrets를 환경 변수에 직접 작성

# ❌ 템플릿에 직접 작성
Environment:
  Variables:
    DB_PASSWORD: "my-secret"

# ✅ Secrets Manager 경유
Environment:
  Variables:
    DB_PASSWORD: !Sub "{{resolve:secretsmanager:myapp/db-password}}"

4. VPC Lambda의 콜드 스타트 RDS 연결 목적으로 VPC에 넣으면 콜드 스타트가 수 초 늘어납니다. Provisioned Concurrency 또는 RDS Proxy로 대처하세요.

5. 거대한 배포 패키지 node_modules를 통째로 패키징하면 250MB 상한에 걸립니다. 공통 라이브러리는 Lambda Layer로 분리하세요.

정리

작업Claude Code의 기여
핸들러 구현타입 정의·에러 처리·로직을 한 번에 생성
SAM 템플릿이벤트·IAM·환경 변수를 자동 출력
IAM 정책최소 권한 설계를 정확하게 생성
로컬 테스트sam invoke 실행과 결과 판단을 자동화
배포build + deploy를 일련의 흐름으로 실행

Lambda 개발에서 가장 시간을 잡아먹는 “타입 정의와 템플릿 작성”을 Claude Code가 통째로 처리합니다. 비즈니스 로직에 집중할 수 있는 환경이 구현 속도를 3-5배 끌어올립니다.

관련 기사

참고 자료

#claude-code #aws #lambda #serverless #api-gateway #typescript

Claude Code 워크플로우를 한 단계 업그레이드하세요

지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.

무료 제공

무료 PDF: 5분 완성 Claude Code 치트시트

이메일 주소만 등록하시면 A4 한 장짜리 치트시트 PDF를 즉시 보내드립니다.

개인정보는 엄격하게 관리하며 스팸은 보내지 않습니다.

Masa

이 글을 작성한 사람

Masa

Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.