Use Cases (Updated: 6/2/2026)

Claude Code AWS Deployment Guide: CDK, GitHub Actions, IAM, Logs, and Rollback

Deploy a small AWS API with Claude Code, CDK, OIDC, least-privilege IAM, logs, rollback, and cost guardrails.

Claude Code AWS Deployment Guide: CDK, GitHub Actions, IAM, Logs, and Rollback

The risky way to use Claude Code for AWS deployment is to ask for “an AWS architecture” and accept whatever appears. Real deployment quality is not a diagram. It is working infrastructure code, scoped IAM, secrets handling, logs, rollback, and cost controls that a small team can actually operate.

This guide implements one safe path for a small web app or API: API Gateway + Lambda + AWS CDK + GitHub Actions. CDK is Infrastructure as Code: a repeatable recipe for the AWS resources you would otherwise create manually. IAM is the permission system that decides who or what can perform each action. OIDC lets GitHub Actions assume an AWS role with temporary credentials instead of storing long-lived AWS keys.

Claude Code is useful here because it can read the repository, edit several files, run checks, explain cdk diff, and turn deployment errors into concrete fixes. Use the Claude Code documentation for product behavior, and keep AWS claims grounded in the AWS CDK NodejsFunction docs, API Gateway Lambda proxy integration guide, Lambda environment variable guidance, Lambda CloudWatch Logs docs, and IAM best practices.

Choose the AWS target before coding

Do not let the deployment target stay vague. Claude Code will make better decisions when you name the workload shape.

OptionBest forStrengthWatch out for
Lambda + API GatewaySmall APIs, contact forms, webhooks, lightweight admin backendsLow server management and a good starting point for low trafficNot ideal for long-running jobs, persistent connections, or large container needs
ECS/Fargate + ALBDockerized APIs, always-on services, mixed API and worker workloadsFlexible containers and easier migration for existing appsMore moving parts: VPC, ALB, task definitions, scaling, image builds
Amplify or S3 + CloudFrontStatic sites, SPAs, frontend-heavy appsFast CDN-backed deliveryBackend API, auth, and jobs need separate design

For this article, we choose Lambda + API Gateway because it fits the most common consulting request: “I have a small product or internal API and want a safe AWS deployment without overbuilding.” For deeper adjacent reading, see Claude Code AWS Lambda guide, Claude Code AWS IAM guide, Claude Code API Gateway guide, and Claude Code security best practices.

flowchart LR
  User[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

Four practical use cases

The first use case is a contact or lead form behind an existing website. Keep the frontend where it is and deploy only the submission endpoint. API Gateway throttling, an API key, and CloudWatch Logs make spam and incidents easier to investigate.

The second use case is an MVP API for a new SaaS product. Start with small routes such as /v1/health, /v1/contact, and /v1/webhook. Tell Claude Code not to create VPC, RDS, queues, or containers until there is a real requirement.

The third use case is an internal automation webhook: Slack alerts, CMS publishing hooks, approval callbacks, or lightweight admin operations. Lambda is a good fit when each request finishes quickly. If you need retries, long-running work, or fan-out, add SQS or Step Functions intentionally.

The fourth use case is CI/CD migration. Many teams already deploy from a laptop or click through the AWS console. Claude Code can help move that process into CDK and GitHub Actions, but the human decision is where the production boundary, approval flow, and rollback policy live.

Give Claude Code deployment rules

Before implementation, create a CLAUDE.md file so Claude Code has guardrails. This prevents a small API from turning into an expensive platform rewrite.

# AWS deployment rules

- Use AWS CDK v2 and TypeScript.
- Target region: us-east-1 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.

Then prompt Claude Code with clear outputs and constraints:

Add a CDK v2 SmallApiStack to this repository.
Create an API Gateway REST API and a Node.js 20 Lambda with /v1/health and /v1/contact.
Read the runtime config from Secrets Manager secret prod/claude-code-demo/api.
Keep the Lambda execution role least-privilege, add CloudWatch log retention, reserved concurrency, and API Gateway throttling.
Create a GitHub Actions deployment workflow that uses OIDC only. Do not use long-lived AWS keys.
After editing, run npm test, npx cdk synth, and npx cdk diff, then explain the result.

Create the CDK project

You need Node.js, AWS CLI v2, AWS CDK CLI, and an authenticated AWS profile. The commands below assume Bash. On Windows, use WSL or Git Bash for the smoothest copy-paste experience.

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

Create the runtime configuration once. Real API keys and database passwords should not be committed or stored as plaintext Lambda environment variables.

aws secretsmanager create-secret \
  --name prod/claude-code-demo/api \
  --secret-string '{"supportQueue":"aws-consulting"}' \
  --region us-east-1

Add the Lambda handler

Create lambda/handler.ts. Notice that the code logs only operational metadata. It does not print the full request body or secret values.

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" });
  }
}

Add the CDK stack

Replace lib/small-api-stack.ts with the stack below. The important details are appSecret.grantRead(handler), one-month log retention, reserved concurrency, API Gateway throttling, and dataTraceEnabled: false so request bodies are not written to execution logs.

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 });
  }
}

Update the CDK entry file, usually under bin/.

#!/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 ?? "us-east-1",
  },
});

Review the diff before deploy

CDK bootstrap is required the first time CDK deploys into an account and region.

export AWS_REGION=us-east-1
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

Ask Claude Code to explain the diff, especially IAM changes. If it cannot explain why a permission is needed, that permission should not go to production without review.

Test the API and read logs

After deployment, get the CloudFormation output and call both routes.

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"}'

Tail Lambda logs from CloudWatch. AWS notes that logs can take several minutes to appear, so do not treat a short delay as a failed deployment.

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

Deploy from GitHub Actions with OIDC

Do not store AWS access keys in GitHub Secrets. Configure an AWS IAM OIDC provider and a deployment role, then restrict the trust policy to the exact repository and branch or environment.

{
  "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"
        }
      }
    }
  ]
}

Create .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: us-east-1

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

The deployment role permission policy depends on your CDK bootstrap model, CloudFormation execution role, and organizational guardrails. Avoid AdministratorAccess for production. A practical route is to start from the CDK bootstrap roles, restrict OIDC trust conditions, use permission boundaries where your organization requires them, and reduce permissions with IAM Access Analyzer once the workload stabilizes.

Add cost guardrails

Small serverless APIs can still create bills if they loop, get spammed, or log too much. This CDK stack includes API throttling, a usage plan, and Lambda reserved concurrency. Add AWS Budgets before production.

budget.json:

{
  "BudgetName": "small-api-monthly-guardrail",
  "BudgetLimit": {
    "Amount": "20",
    "Unit": "USD"
  },
  "TimeUnit": "MONTHLY",
  "BudgetType": "COST"
}

notifications-with-subscribers.json:

[
  {
    "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

AWS Budgets helps track cost and usage, but budget graphs and notifications may not appear instantly after creation. Set them up before launch, not after a spike.

Roll back without drama

For a first Lambda + API Gateway service, the most practical rollback is often “revert the bad commit and redeploy.” Document that path before production.

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

Then verify the stack events and logs.

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

Three failure modes are common. First, someone edits the AWS console after CDK deployment, and future diffs become confusing. Second, secrets are put into Lambda environment variables or logs. Third, API Gateway request-body tracing is enabled in production, leaking sensitive payloads into logs. The stack above avoids the third issue with dataTraceEnabled: false, but the team still needs a review habit.

Ask Claude Code for a pre-deploy review

Use Claude Code as a reviewer, but require evidence.

Run an AWS pre-deployment review.
Check:
- Whether the CDK diff adds excessive IAM wildcards
- Whether Lambda reads secrets only from Secrets Manager
- Whether API Gateway has throttling, usage plan, and logging settings
- Whether CloudWatch Logs can expose personal data or full request bodies
- Whether rollback steps are documented
- Whether npm test, cdk synth, and cdk diff passed

Return findings as a table with severity, file, line, and fix.

Claude Code is not the final production approver. Account boundaries, cost exposure, and security exceptions belong to humans. But Claude Code can make the review repeatable and catch the same mistakes before they reach AWS.

Summary

The value of Claude Code for AWS deployment is not only faster CDK generation. The value is turning architecture, IaC, CI/CD, IAM, secrets, logs, rollback, and cost guardrails into one repeatable deployment habit.

For a small web API, Lambda + API Gateway + CDK is usually enough to start. Move to ECS/Fargate when the app truly needs long-running containers. Use S3 + CloudFront or Amplify when the workload is mostly static frontend delivery. Keep the first boundary small, documented, and reversible.

If your team wants help designing Claude Code rules, AWS CDK deployment, GitHub Actions OIDC, least-privilege IAM, and operational checks around an existing repository, book a Claude Code consulting session. Masa can review your current repo and AWS deployment history, then turn it into an implementation plan your team can actually ship.

In practice, a small lead-capture API like this can be made deployable in half a day to one day once the AWS account is ready. The part that deserves the most care is not the Lambda code. It is IAM, secrets, rollback, and cost visibility. Let Claude Code speed up implementation, while humans keep ownership of production risk.

#Claude Code #AWS deployment #CDK #Lambda #GitHub Actions #IAM
Free

Free PDF: Claude Code Cheatsheet

Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.

We handle your data with care and never send spam.

Level up your Claude Code workflow

Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.