AWS Infrastructure with CDK

Production-ready AWS infrastructure using CDK — App Runner for HTTP services, Aurora Serverless for database, Cognito for auth, Lambda for serverless functions, and S3 for storage

Free
Free with a ShipSwift account.

Copies a short instruction with your private download link. Paste it into your agent: it downloads the whole skill (the source code, the setup steps and the mistakes we already made for you) and builds the screen.

Works with

+70

What it does

Manually configuring AWS resources through the Console is error-prone, unrepeatable, and impossible to version control. As your app grows from a side project to a real product, you will inevitably need to reproduce your infrastructure for staging environments, disaster recovery, or a second project.

AWS CDK (Cloud Development Kit) solves this by letting you define your entire AWS infrastructure in TypeScript. You get:

  • Reproducibility — One cdk deploy stands up your entire stack from scratch
  • Version control — Infrastructure changes go through the same PR review as application code
  • Type safety — TypeScript catches misconfigurations at compile time, not at 3am in production
  • Least-privilege by default — CDK's grant* methods generate minimal IAM policies automatically
  • Cost visibility — All resources are declared in code, making it easy to audit what you are paying for

This skill provides a complete, production-tested CDK stack for indie iOS apps: App Runner for your HTTP backend, Aurora Serverless v2 for PostgreSQL, Cognito for authentication, Lambda for serverless tasks, and S3 for file storage.

What you get

  • ✓1,068 lines of source code, ready to paste
  • ✓Architecture
  • ✓Dependencies
  • ✓Implementation
  • ✓Integration Checklist
  • ✓Common Customizations
  • ✓Known Pitfalls
AWSCDKinfrastructureApp RunnerAuroraLambdaCognitodeployment

Architecture

┌──────────────────────────────────────────────────────────────────────────┐
│                              iOS Client                                │
└──────────────────────────────┬───────────────────────────────────────────┘
                               │
                ┌──────────────┼──────────────┐
                │              │              │
                ▼              │              ▼
        ┌──────────────┐       │    ┌────────────────────┐
        │   Cognito    │       │    │   API Gateway       │
        │  User Pool   │◄──────┼────│  (JWT Authorizer)   │
        │ + Hosted UI  │       │    └─────────┬──────────┘
        └──────┬───────┘       │              │
               │               │              ▼
               ▼               │    ┌────────────────────┐
        ┌──────────────┐       │    │    App Runner       │
        │Identity Pool │◄──────┘    │   (Hono backend)    │
        │(anon + auth) │            └─────────┬──────────┘
        └──────────────┘                      │
                                ┌─────────────┴─────────────┐
                                │                           │
                                ▼                           ▼
                      ┌──────────────┐            ┌────────────────┐
                      │  RDS Proxy   │            │   S3 Bucket    │
                      │ (conn pool)  │            │  (file store)  │
                      └──────┬───────┘            └────────────────┘
                             │
                             ▼
                      ┌──────────────┐
                      │   Aurora     │
                      │Serverless v2 │
                      └──────────────┘

Service roles:

ServicePurposeWhy this one
Cognito User PoolUser authenticationNative Apple/Google sign-in, JWT auto-refresh
Cognito Identity PoolAnonymous guests + credential managementUnique ID for every visitor, temporary AWS credentials
API Gateway HTTP APIAPI gatewayJWT Authorizer for unified auth, low latency
App RunnerBackend runtimeAuto-deploy from GitHub, auto-scale, zero server management
Aurora Serverless v2DatabaseAuto-scale, pay-per-use, PostgreSQL compatible
RDS ProxyConnection poolingPrevents connection storms, improves stability
Secrets ManagerSecret storageAuto-rotation, secure database credential storage
S3File storageUnlimited capacity, low cost
LambdaServerless functionsDB migrations, scheduled tasks, event processing
VPCNetwork isolationPrivate subnets protect your database

Integration Checklist

1. AWS Account Setup

# Install AWS CLI
brew install awscli

# Configure a named profile
aws configure --profile wei
# Enter: Access Key ID, Secret Access Key, region (us-east-1), output (json)

# Verify access
aws sts get-caller-identity --profile wei

2. CDK Bootstrap (one-time per account/region)

# Bootstrap CDK in your account
npx cdk bootstrap aws://ACCOUNT_ID/us-east-1 --profile wei

3. GitHub Connection (for App Runner auto-deploy)

  1. Open AWS Console > App Runner > GitHub connections
  2. Click "Add new" and authorize your GitHub account
  3. Copy the Connection ARN into cdk.json

4. First Deploy (two-step for social login)

# Step 1: Deploy without social login to create Secrets
# Set in cdk.json: "enableSocialLogin": "false"
npm run cdk:deploy

# Step 2: Update secrets with real values
aws secretsmanager put-secret-value \
  --secret-id my-app/secrets \
  --secret-string '{"AUTH_APPLE_PRIVATE_KEY":"...","AUTH_GOOGLE_CLIENT_SECRET":"..."}' \
  --profile wei --region us-east-1

# Step 3: Enable social login and redeploy
# Set in cdk.json: "enableSocialLogin": "true"
npm run cdk:deploy

5. Subsequent Deploys

# Preview changes
npm run cdk:diff

# Deploy
npm run cdk:deploy

Known Pitfalls

1. App Runner Cannot Reach RDS Proxy

Symptom: Connection timeout or refused.

Cause: Security group rules are missing or misconfigured.

Fix: Ensure the RDS Proxy security group allows inbound traffic from the App Runner security group on port 5432.

proxySecurityGroup.addIngressRule(
  appRunnerSecurityGroup,
  ec2.Port.tcp(5432),
  "Allow App Runner to connect to RDS Proxy"
);

2. Lambda Cannot Access Secrets Manager

Symptom: getaddrinfo ENOTFOUND secretsmanager.*.amazonaws.com

Cause: Lambda is in a private subnet with no route to AWS service endpoints.

Fix: Add a VPC endpoint for Secrets Manager, or ensure your VPC has a NAT Gateway.

vpc.addInterfaceEndpoint("SecretsManagerEndpoint", {
  service: ec2.InterfaceVpcEndpointAwsService.SECRETS_MANAGER,
});

3. CDK Deploy Stuck on Custom Resource

Symptom: Deployment hangs at CREATE_IN_PROGRESS for 60+ minutes.

Cause: The Lambda backing the Custom Resource timed out (network issues) or threw an unhandled error (no CloudFormation response sent).

Fix:

  1. Check Lambda logs in CloudWatch > Log groups
  2. Verify the Lambda has correct VPC/subnet/security-group configuration
  3. Increase Lambda timeout
  4. Ensure the migration code properly handles CloudFormation callback responses

4. Cognito signInAliases Cannot Be Modified

Symptom: Updates are not allowed for property - UsernameAttributes

Cause: Cognito User Pool signInAliases and passwordPolicy are immutable after creation.

Fix: Delete and recreate the User Pool, or plan your initial configuration carefully to include all login methods you might need.

5. App Runner Build Fails

Symptom: Build failed in App Runner deployment logs.

Common causes:

  • Missing --include=dev in build command (TypeScript compiler is a devDependency)
  • Wrong Node.js runtime version
  • Missing environment variables during build

Fix:

codeConfigurationValues: {
  runtime: apprunner.Runtime.NODEJS_22,
  buildCommand: "npm ci --include=dev && npm run build",
  startCommand: "node dist/src/server.js",
}

6. TypeScript Path Aliases Fail in Production

Symptom: Works locally, but Cannot find module '@/xxx' after deployment.

Cause: TypeScript paths are only for type checking. esbuild and the Node.js runtime do not resolve them.

Fix: Use relative imports instead of path aliases.

// Avoid
import { db } from "@/db/client";

// Use relative paths
import { db } from "../db/client";

7. CDK Overwrites Your Real Secrets

Symptom: After cdk deploy, your API keys in Secrets Manager are replaced with "PLACEHOLDER".

Cause: You changed the placeholder text in your CDK code, so CDK detected a diff and applied it.

Fix: Never modify the placeholder values in secretObjectValue after the initial deploy. The placeholders exist only so CDK can create the secret — you update real values manually via the AWS CLI.

8. API Gateway Returns 401

Symptom: Authenticated requests are rejected.

Checklist:

  1. Is the JWT token expired? (default: 1 hour)
  2. Does the token come from the correct User Pool?
  3. Does jwtAudience match the App Client ID?
  4. Is the header format correct? Authorization: Bearer <token>

9. Cost Awareness

ResourceDev ConfigEstimated Monthly Cost
NAT Gateway1 instance~$32
Aurora Serverless v20.5-2 ACU~$43
App Runner0.25 vCPU / 0.5 GB~$5
Secrets Manager1 secret~$0.40
Total~$80/mo

To minimize cost during development:

natGateways: 1,                              // Single NAT Gateway
serverlessV2MinCapacity: 0.5,                // Minimum Aurora capacity
cpu: apprunner.Cpu.QUARTER_VCPU,             // Smallest App Runner
memory: apprunner.Memory.HALF_GB,
removalPolicy: cdk.RemovalPolicy.DESTROY,    // Easy cleanup
deletionProtection: false,

Production Readiness Checklist

  • deletionProtection: true on Aurora and Cognito
  • removalPolicy: cdk.RemovalPolicy.RETAIN on database and secrets
  • Multi-AZ deployment: natGateways: 2
  • Aurora backup: retention: cdk.Duration.days(30)
  • CloudWatch alarms for error rates and latency
  • VPC Flow Logs enabled
  • Secrets rotation configured
  • enableDataApi: false on Aurora cluster

More in Backend