# Cdk Infrastructure

> AWS CDK infrastructure development with TypeScript. Use when creating or modifying CDK constructs, DynamoDB tables, ECS/Fargate services, Lambda functions, S3 buckets, networking, IAM roles, or any CloudFormation resources. Covers configuration patterns, single-stack architecture, naming conventions, and Bedrock AgentCore integration.

- Skill: `boise-state-development/cdk-infrastructure` (Agent Skill, multi-file: 9 files)
- Install (CLI): `npx skillmds@latest add boise-state-development/cdk-infrastructure`
- Raw SKILL.md: https://api.skillmd.com/api/skills/boise-state-development/cdk-infrastructure/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: boise-state-development (https://skillmd.com/u/boise-state-development)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/boise-state-development/cdk-infrastructure

---


# AWS CDK Infrastructure Best Practices

## TypeScript

- Use strict type checking
- Import from `aws-cdk-lib` and `constructs`
- Use L2 constructs when available, L1 (Cfn*) when necessary

## Architecture — Single Stack

The entire application is provisioned by **one CDK stack** (`PlatformStack`). Application code is shipped out-of-band via AWS APIs (ECR push → ECS service update / Lambda code update / AgentCore Runtime update).

```
infrastructure/
├── bin/infrastructure.ts          # App entrypoint (instantiates PlatformStack)
├── lib/
│   ├── platform-stack.ts          # The one stack — all infrastructure
│   ├── config.ts                  # Configuration loader & validator
│   └── constructs/                # 39 reusable CDK constructs
│       ├── network/               # VPC, ALB, ECS cluster
│       ├── identity/              # Cognito, secrets, KMS, OAuth
│       ├── data/                  # DynamoDB tables, file uploads
│       ├── rag/                   # RAG documents, vectors
│       ├── rag-ingestion/         # RAG ingestion Lambda
│       ├── artifacts/             # Artifact rendering pipeline
│       ├── mcp-sandbox/           # MCP Apps sandbox proxy
│       ├── agentcore/             # Memory, Code Interpreter, Browser, Gateway
│       ├── inference-api/         # AgentCore Runtime
│       ├── app-api/               # Fargate service
│       ├── fine-tuning/           # SageMaker IAM
│       ├── spa/                   # SPA CloudFront distribution
│       └── zones/                 # Route53, ALB DNS
└── cdk.context.json               # Configuration defaults
```

**Key principle:** CDK deploys are rare (infrastructure changes only). Day-to-day code changes deploy via `backend.yml` (AWS API calls, no CDK).

## Configuration

Use the centralized config system:

```typescript
import { loadConfig, getResourceName, getStackEnv, applyStandardTags } from './config';
```

PlatformStack receives config via props:
```typescript
const config = loadConfig(app);
new PlatformStack(app, `${config.projectPrefix}-PlatformStack`, { config, env });
```

For configuration patterns, see [references/configuration.md](references/configuration.md).

## Naming Conventions

**Resource Names:** Use `getResourceName()`:
```typescript
getResourceName(config, 'user-quotas')  // "bsu-agentcore-user-quotas"
```

**SSM Parameters:** Hierarchical naming for runtime consumption:
```
/{projectPrefix}/{category}/{resource-type}
```

Categories: `/network/`, `/quota/`, `/cost-tracking/`, `/auth/`, `/frontend/`, `/gateway/`, `/rag/`, `/artifacts/`

## Cross-Construct References

Since everything is in one stack, use **typed props** — not SSM:

```typescript
// In PlatformStack:
const network = new NetworkConstruct(this, 'Network', { config });
new AlbConstruct(this, 'Alb', { config, vpc: network.vpc });
```

SSM parameters are published **only for runtime consumption** by ECS tasks and Lambdas — never for CDK-to-CDK references within the same stack.

### Wire a resource's name to every compute that reads it

When a construct exposes a table/bucket that backend code reads via
`os.environ.get("X_NAME", "default")`, you must set `X_NAME` in the container
environment of **every** compute that runs that code — thread the typed ref
through that compute's env builder (e.g. `buildAppApiEnvironment` for app-api,
the inference-agentcore construct's `environment` for inference-api). Wiring one
does **not** wire the other.

**Why this bites (silent 502):** the backend's default fallback hides the
omission. If the env var is missing, the code queries the *default* name
(e.g. `"memory-spaces"` instead of `{prefix}-memory-spaces`), the resource
isn't found, boto3 raises `ResourceNotFoundException`, and the centralized
handler (`apis/shared/security/error_handler.py`) maps it to a generic
**502 `{"detail":"Upstream service error."}`**. Nothing in `cdk synth` or CI
catches it — the stack is valid, the IAM grant may even exist; only a runtime
read fails. (Real instance: PR #588 — memory-spaces names were wired to
inference-api but not app-api, which owns the CRUD routes.)

**Guard it:** add an env-map unit test asserting the key is emitted (see
`test/app-api-environment.test.ts`). **Mind the boundary:** app-api owns
user-facing CRUD; granting IAM or wiring inference-api does not cover it.

## DynamoDB Tables

- Always use PK + SK for flexibility
- Use `PAY_PER_REQUEST` billing
- Enable point-in-time recovery
- Environment-based removal policy

For table patterns, see [references/dynamodb.md](references/dynamodb.md).

## ECS/Fargate

- Cluster created by NetworkConstruct, referenced via typed prop
- Health checks mandatory
- Auto-scaling with CPU/memory targets
- Circuit breaker for rollback
- Bootstrap container pattern: CDK creates the service with a placeholder image; the backend workflow pushes the real image via `update-service`

For service patterns, see [references/ecs-fargate.md](references/ecs-fargate.md).

## Lambda

- Use ARM64 architecture (cost optimization)
- Role with least privilege
- Secrets Manager access requires wildcard suffix
- Bootstrap pattern: CDK creates the function with placeholder code; the backend workflow pushes real code via `update-function-code`

For Lambda patterns, see [references/lambda.md](references/lambda.md).

## S3 Buckets

- Block public access
- Enable versioning
- Lifecycle rules for cost optimization
- Include account ID for global uniqueness

For bucket patterns, see [references/s3.md](references/s3.md).

## Security

- Separate security groups for ALB and ECS
- Private subnets for services
- IAM roles with SIDs for clarity
- Never hardcode secrets

For IAM patterns, see [references/iam.md](references/iam.md).

## Important Constraints

**AgentCore Names:** Use underscores, not hyphens:
```typescript
name: getResourceName(config, 'memory').replace(/-/g, '_')
```

**Secrets Manager ARN:** Include wildcard for random suffix:
```typescript
resources: [`${secret.secretArn}*`]
```

**Removal Policy:**
```typescript
removalPolicy: getRemovalPolicy(config)  // RETAIN in prod, DESTROY in dev
```

## CDK Commands

```bash
cd infrastructure
npm ci                # Install dependencies
npx cdk synth         # Synthesize CloudFormation
npx cdk deploy {prefix}-PlatformStack  # Deploy
npx cdk diff          # Preview changes
```

