AWS Integration Guide

Technology: aws · Category: hosting · Last reviewed: 2026-08-23

Source: https://tech-stack.codeamanilabs.org/guide/aws

Insight:

AWS is the Enterprise-tier host — reserved for $50K+ client builds (incl. the HIPAA-compliant healthcare platform) where Neon/R2/Resend/Vercel can't satisfy a client's compliance, residency, or scale requirement. It complements, never replaces, the house defaults. Before reaching for AWS, consult the codeAmani-tech-stack MCP and confirm a default won't do; when you do use it, sign a BAA via AWS Artifact and stay on HIPAA-eligible services only.

 █████╗ ██╗    ██╗███████╗
██╔══██╗██║    ██║██╔════╝
███████║██║ █╗ ██║███████╗
██╔══██║██║███╗██║╚════██║
██║  ██║╚███╔███╔╝███████║
╚═╝  ╚═╝ ╚══╝╚══╝ ╚══════╝

AWS Integration Guide

Focus: When and how codeAmani uses Amazon Web Services for Enterprise-tier client builds — IAM least-privilege, S3/presigned URLs, RDS & Aurora Serverless v2, Lambda/API Gateway, ECS Fargate, CloudFront, SES, IaC with the AWS CDK (TypeScript), and a HIPAA baseline for the Florida healthcare niche.

Overview

AWS is advertised as infrastructure on motionstackstudios.com and is the host for the agency's Enterprise segment — $50K+ engagements, including a HIPAA-compliant healthcare platform for an APD/AHCA/DCF-licensed provider. It is the heavyweight option in a roster of house hosting guides (vercel, netlify, cloudflare, render, google-cloud), and it complements rather than replaces them.

The house defaults stay the same for almost everything: Neon for Postgres, Cloudflare R2 for object storage, Resend for email, Vercel for app hosting. AWS earns a place only when an enterprise client's contract demands something a default can't give — a signed BAA, data residency in a specific region, a private VPC, VPC-peered databases, or scale/SLA guarantees. The decision flow:

flowchart TD
    A["New enterprise build<br/>$50K+ engagement"] --> B{"Compliance / residency /<br/>scale requirement?"}
    B -->|No| C["House defaults<br/>Neon · R2 · Resend · Vercel"]
    B -->|Yes — e.g. HIPAA BAA| D["AWS account<br/>(per-client, isolated)"]
    D --> E["IAM least-privilege<br/>roles · no root keys"]
    E --> F["S3 + RDS/Aurora<br/>Lambda · ECS · CloudFront · SES"]
    F --> G["BAA via AWS Artifact<br/>HIPAA-eligible services only"]
    G --> H["Provisioned via CDK / Terraform<br/>reviewed by [CA] Cloud Architect"]

Check first. Before adding any AWS service, query the codeAmani-tech-stack MCP (search_guides / get_guide) and confirm a house default won't satisfy the requirement. AWS adds operational surface, cost, and compliance obligations — prefer the default unless the client specifically requires AWS.

Official Documentation

Resource URL
AWS Documentation (root) https://docs.aws.amazon.com
AWS SDK for JavaScript v3 https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/
AWS CDK v2 Developer Guide https://docs.aws.amazon.com/cdk/v2/guide/home.html
AWS CLI v2 User Guide https://docs.aws.amazon.com/cli/latest/userguide/
Lambda Node.js runtimes https://docs.aws.amazon.com/lambda/latest/dg/lambda-nodejs.html
Official AWS MCP servers (awslabs/mcp) https://github.com/awslabs/mcp
HIPAA-Eligible Services Reference https://aws.amazon.com/compliance/hipaa-eligible-services-reference/
Architecting HIPAA on AWS (whitepaper) https://docs.aws.amazon.com/whitepapers/latest/architecting-hipaa-security-and-compliance-on-aws/

AWS now ships official MCP servers, but none is wired into the house .mcp.json by default. AWS publishes the open-source awslabs/mcp collection plus AWS-managed remote endpoints — the read-only AWS Knowledge MCP Server (https://knowledge-mcp.global.api.aws, no auth) for live AWS docs and API references, and the AWS API MCP Server (https://aws-mcp.us-east-1.api.aws/mcp, preview) alongside Amazon EKS/ECS servers that can actually drive an account. The Knowledge server is a safe read-only companion worth enabling; for anything that mutates a client account, codeAmani still goes through the AWS CLI v2 + the v3 SDK + CDK under the [CA] Cloud Architect gate. If you do enable a write-capable AWS-managed server, note it auto-injects the aws:ViaAWSMCPService / aws:CalledViaAWSMCP IAM context keys, so least-privilege policies can distinguish agent-driven calls from human-initiated ones. The [CA] Cloud Architect agent owns IaC and account topology; loop it in for any new account or VPC design.


Accounts & IAM (least-privilege)

Every enterprise client gets an isolated AWS account (ideally under an AWS Organizations management account) so blast radius, billing, and a HIPAA BAA stay scoped per engagement. The non-negotiables:

A minimal scoped policy for a Vercel app that only needs to put/get objects in one bucket:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ScopedBucketAccess",
      "Effect": "Allow",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::acme-health-intake/*"
    }
  ]
}

Secrets Manager

App secrets (DB credentials, third-party API keys) live in AWS Secrets Manager, not in plaintext env files on the instance. This is the AWS-native analogue of the house infisical guide — when a build is on AWS end-to-end, prefer Secrets Manager (it integrates with RDS rotation and IAM); when the app is Vercel-hosted, Infisical/Vercel env vars remain the default and AWS holds only the infrastructure secrets. Encryption is handled by KMS automatically; see the encryption guide for the at-rest/in-transit baseline.

// lib/aws/secrets.ts
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";

const client = new SecretsManagerClient({ region: process.env.AWS_REGION });

export async function getSecret<T = Record<string, string>>(secretId: string): Promise<T> {
  const res = await client.send(new GetSecretValueCommand({ SecretId: secretId }));
  if (!res.SecretString) throw new Error(`Secret ${secretId} has no string value`);
  return JSON.parse(res.SecretString) as T;
}

S3 & Presigned URLs

S3 is the AWS object store. Cloudflare R2 is the house default (zero egress fees, S3-compatible API), and most builds never leave R2 — but an enterprise/HIPAA client may require S3 specifically (BAA coverage, s3:ObjectLockConfiguration for WORM retention, SSE-KMS with a customer-managed key, or VPC-gated access). R2 speaks the S3 API, so the v3 @aws-sdk/client-s3 code below works against both; only the endpoint and credentials change.

The pattern for client uploads is the presigned URL: the browser uploads directly to S3, the server never proxies the bytes, and the URL expires. For PHI, the bucket is private with SSE-KMS and the presigned PUT carries the encryption header.

// lib/aws/s3-presign.ts
import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";

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

/** Presigned PUT for a direct browser upload (PHI: SSE-KMS enforced). */
export async function presignUpload(key: string, contentType: string): Promise<string> {
  const cmd = new PutObjectCommand({
    Bucket: BUCKET,
    Key: key,
    ContentType: contentType,
    ServerSideEncryption: "aws:kms",
    SSEKMSKeyId: process.env.S3_KMS_KEY_ID, // customer-managed key for healthcare
  });
  return getSignedUrl(s3, cmd, { expiresIn: 300 }); // 5 minutes
}

/** Presigned GET for a time-boxed download (e.g. an intake PDF). */
export async function presignDownload(key: string): Promise<string> {
  const cmd = new GetObjectCommand({ Bucket: BUCKET, Key: key });
  return getSignedUrl(s3, cmd, { expiresIn: 60 });
}

Gotcha: A presigned PUT only succeeds if the request matches the signed parameters exactly. If you sign ServerSideEncryption: "aws:kms", the browser's PUT must send the x-amz-server-side-encryption: aws:kms header — otherwise S3 returns 403 SignatureDoesNotMatch. Sign exactly the headers the client will send, no more.


Databases — RDS & Aurora Serverless v2

Neon is the house default Postgres — serverless, branch-per-PR, generous free tier, and it's already wired into the neon guide and the app's Drizzle setup. Stay on Neon unless an enterprise client needs something Neon can't offer: a signed BAA, a private VPC with no public endpoint, VPC peering to other AWS resources, or a specific data-residency region.

When AWS is required, the recommendation is Aurora Serverless v2 (PostgreSQL-compatible) over plain RDS for most workloads — it autoscales ACUs with load (including scale-to-near-zero on idle), which mirrors Neon's serverless economics while living inside the client's VPC. Reach for provisioned RDS only when a steady, predictable instance class is cheaper or a feature requires it.

Concern House default (Neon) AWS (Aurora Serverless v2 / RDS)
Provisioning Instant, in-console/MCP CDK/Terraform into a VPC
Scaling Serverless autoscale Aurora SLv2 ACUs / RDS instance class
Branching Per-PR DB branches Snapshots/clones (heavier)
Connection Pooled HTTP/WebSocket driver Standard pg over VPC; use RDS Proxy for Lambda
HIPAA BAA Via Neon's terms Covered under AWS BAA (Aurora/RDS are HIPAA-eligible)
Encryption TLS + at-rest TLS + KMS at-rest (required for PHI)

Drizzle still drives it — point the connection string at the Aurora writer endpoint (sourced from Secrets Manager). For Lambda, put RDS Proxy in front to avoid exhausting connections on cold-start storms.


Compute — Lambda + API Gateway & ECS Fargate

Two compute shapes cover the enterprise cases:

Runtimes: target a current managed runtime. Lambda's Node.js line on Amazon Linux 2023 is nodejs22.x, nodejs24.x (the console default), and nodejs26.x; the AL2-based nodejs18.x and earlier are deprecated. In the CDK, prefer Runtime.NODEJS_LATEST (or pin NODEJS_24_X) so functions stay on a supported line. On Fargate, pin the base image to a supported Node.js LTS (22 or 24).

Both run with a scoped task/execution role (no embedded keys) and read secrets from Secrets Manager at start. A typical healthcare platform is: CloudFront → ALB → Fargate service (in private subnets) → Aurora Serverless v2 (in isolated subnets) → S3 for documents.


CloudFront & SES


Infrastructure as Code (AWS CDK in TypeScript)

All AWS infrastructure is defined as code and reviewed by the [CA] Cloud Architect agent before apply — no click-ops in the console for anything that touches client data. The house preference is the AWS CDK v2 in TypeScript (same language as the app, type-safe constructs); Terraform is the alternative when a client standardizes on it or needs multi-cloud.

npm install -g aws-cdk        # CDK v2 CLI (the `cdk` command)
npm install aws-cdk-lib constructs   # in-project library
cdk bootstrap aws://ACCOUNT_ID/us-east-1

A minimal, HIPAA-leaning stack — a private, encrypted, versioned bucket plus a scoped secret:

// lib/intake-stack.ts
import { Stack, StackProps, Duration, RemovalPolicy } from "aws-cdk-lib";
import { Construct } from "constructs";
import {
  Bucket,
  BucketEncryption,
  BlockPublicAccess,
} from "aws-cdk-lib/aws-s3";
import { Key } from "aws-cdk-lib/aws-kms";
import { Secret } from "aws-cdk-lib/aws-secretsmanager";

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

    // Customer-managed KMS key with rotation (required posture for PHI).
    const key = new Key(this, "IntakeKey", {
      enableKeyRotation: true,
      removalPolicy: RemovalPolicy.RETAIN,
    });

    // Private, encrypted, versioned bucket — no public access.
    new Bucket(this, "IntakeBucket", {
      bucketName: "acme-health-intake",
      encryption: BucketEncryption.KMS,
      encryptionKey: key,
      enforceSSL: true,
      versioned: true,
      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
      lifecycleRules: [{ expiration: Duration.days(2555) }], // ~7yr retention
    });

    // DB credentials in Secrets Manager (consumed by Aurora + the app).
    new Secret(this, "DbCredentials", {
      secretName: "acme/aurora/credentials",
      generateSecretString: {
        secretStringTemplate: JSON.stringify({ username: "app" }),
        generateStringKey: "password",
        excludePunctuation: true,
      },
    });
  }
}
cdk synth      # emit the CloudFormation template (review this in the [CA] gate)
cdk diff       # show the delta against the deployed stack
cdk deploy     # apply (only after Cloud Architect review)

HIPAA on AWS (Florida healthcare niche)

This is the reason AWS exists in the stack. For any build handling PHI for an APD/AHCA/DCF provider, the baseline is non-negotiable:

  1. Sign the BAA. Accept the AWS Business Associate Addendum via AWS Artifact (Console → Artifact → Agreements) before any PHI lands in the account. No BAA, no PHI — full stop.
  2. HIPAA-eligible services only. PHI may only flow through services on the HIPAA-eligible services reference. S3, RDS/Aurora, Lambda, ECS/Fargate, API Gateway, CloudFront, SES, Secrets Manager, KMS are all eligible — but verify each one before use; not every AWS service is covered.
  3. Encryption at rest — KMS on S3 (SSE-KMS), Aurora/RDS storage encryption, EBS volumes. Use customer-managed keys with rotation for the strongest posture.
  4. Encryption in transit — TLS everywhere; enforceSSL on buckets, rds.force_ssl, HTTPS-only CloudFront. See the encryption guide.
  5. Audit + access — CloudTrail on (immutable log bucket), least-privilege IAM, no public endpoints for PHI stores, VPC isolation for databases.
  6. Retention — lifecycle rules / Object Lock to meet record-retention rules (Florida healthcare retention can run ~7 years).
flowchart LR
    A["Provider uploads PHI"] --> B["CloudFront + OAC<br/>HTTPS only"]
    B --> C["Private S3 bucket<br/>SSE-KMS · versioned"]
    A --> D["Fargate service<br/>(private subnet)"]
    D --> E[("Aurora Serverless v2<br/>KMS at-rest · TLS")]
    D --> F["Secrets Manager<br/>DB creds via IAM role"]
    G["AWS Artifact BAA"] -.signed before any PHI.-> C
    H["CloudTrail"] -.audit log.-> C

Compliance gate: PHI work intersects the [COMPLIANCE] and [PRIVACY] agents. The BAA, encryption posture, and the HIPAA-eligible-services check are human approval gates — surface them, don't assume.


Environment Variables

# Region (required by every v3 SDK client)
AWS_REGION=us-east-1

# Static credentials — ONLY where a role can't be attached (e.g. a Vercel app
# calling S3). Scope them to one bucket/action. Prefer roles everywhere else.
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...           # server-side only — NEVER ship to the client bundle

# Local dev / CLI: assume a role from a named profile instead of static keys
AWS_PROFILE=acme-enterprise

# App-level config (sourced from Secrets Manager in production)
S3_BUCKET=acme-health-intake
S3_KMS_KEY_ID=arn:aws:kms:us-east-1:123456789012:key/...

Add these to ENV_MASTER.md and each project's .env.example. In production prefer IAM roles (Lambda/ECS/EC2) and Secrets Manager over static keys; static keys are a last resort and must be rotated. Never expose AWS_SECRET_ACCESS_KEY to the browser — presign on the server.


CLI Integration (AWS CLI v2)

Install the AWS CLI v2 (the v1 line is end-of-life-track; always v2). Authenticate with a named profile or SSO — never paste root keys.

# Configure a named profile (or `aws configure sso` for IAM Identity Center)
aws configure --profile acme-enterprise

# Verify which identity you're operating as before anything destructive
aws sts get-caller-identity --profile acme-enterprise

# Create a private, versioned bucket with default SSE-KMS
aws s3api create-bucket --bucket acme-health-intake --region us-east-1 \
  --profile acme-enterprise
aws s3api put-bucket-versioning --bucket acme-health-intake \
  --versioning-configuration Status=Enabled --profile acme-enterprise

# Read a secret (JSON) from Secrets Manager
aws secretsmanager get-secret-value --secret-id acme/aurora/credentials \
  --query SecretString --output text --profile acme-enterprise

# Tail a Lambda's logs live
aws logs tail /aws/lambda/intake-handler --follow --profile acme-enterprise

Automation Workflows

Claude Code slash command: AWS resource audit

.claude/commands/aws-audit.md:

Audit the AWS account for the profile: $ARGUMENTS

1. Run `aws sts get-caller-identity --profile $ARGUMENTS` and confirm the account.
2. List S3 buckets and check each for: public-access block ON, default encryption,
   and versioning (`aws s3api get-bucket-encryption|get-public-access-block|get-bucket-versioning`).
3. List IAM users and flag any with active access keys older than 90 days.
4. Confirm CloudTrail is enabled in all regions.
5. Cross-check every service touching PHI against the HIPAA-eligible services list.
6. Output a findings table: resource, issue, severity, remediation. Flag anything
   that breaks the HIPAA baseline for the [COMPLIANCE] / [PRIVACY] gate.

GitHub Actions: CDK deploy via OIDC (no static keys)

# .github/workflows/cdk-deploy.yml
name: CDK Deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write   # required for OIDC role assumption
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with: { node-version: '24' }
      - uses: aws-actions/configure-aws-credentials@v6
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-cdk-deploy
          aws-region: us-east-1
      - run: npm ci
      - run: npx cdk diff
      - run: npx cdk deploy --require-approval never

Use GitHub OIDC to assume a deploy role — no long-lived AWS keys in CI secrets. The role is scoped to the CDK CloudFormation stack only.


Common Use Cases

Use Case Approach
Default object storage Cloudflare R2 (house default) — use S3 only if a client requires it
Default Postgres Neon (house default) — use Aurora Serverless v2 only for VPC/BAA/residency needs
Default transactional email Resend (house default) — use SES only for in-VPC/high-volume/BAA email
Default app hosting Vercel (house default) — use ECS Fargate/Lambda for VPC-resident services
Direct browser upload S3 presigned PUT (@aws-sdk/s3-request-presigner), 5-min expiry
Time-boxed file download S3 presigned GET, short expiry
Infrastructure provisioning AWS CDK v2 (TypeScript), reviewed by [CA]
App/infra secrets AWS Secrets Manager + IAM role (ties to infisical/encryption)
HIPAA PHI store Private S3 + SSE-KMS + Aurora-in-VPC, BAA via AWS Artifact
Bursty / event-driven compute Lambda + API Gateway (+ RDS Proxy for DB)
Long-running / containerized ECS Fargate (ties to docker guide)

Troubleshooting

Issue Fix
SignatureDoesNotMatch on presigned PUT Client must send exactly the signed headers (e.g. x-amz-server-side-encryption); sign only what the client will send
403 AccessDenied on S3 GET Check the IAM policy ARN matches the bucket, and that bucket policy / OAC isn't blocking; confirm AWS_REGION is correct
ExpiredToken from the SDK SSO/role session expired — re-run aws sso login --profile ... or refresh the assumed role
CLI uses the wrong account Pass --profile (or set AWS_PROFILE); verify with aws sts get-caller-identity
Lambda DB connection exhaustion Front the database with RDS Proxy; don't open a raw pool per invocation
cdk deploy fails on bootstrap Run cdk bootstrap aws://ACCOUNT/REGION once per account+region
SES emails go to spam / sandbox Verify domain DKIM/SPF and request production access to leave the SES sandbox
PHI on a non-eligible service Stop — move PHI only onto services on the HIPAA-eligible list, and confirm the BAA is signed in AWS Artifact
KMS AccessDeniedException The IAM role needs kms:GenerateDataKey/kms:Decrypt on the key, and the key policy must allow the role

Official docs: