mirror of
https://github.com/Sea-Haven-Industries/engineering-handbook.git
synced 2026-09-30 09:13:14 +00:00
Exact pins remain (reproducibility) but the pinned version is kept current by Dependabot version updates gated by CI + dependency review, not by a number frozen in the handbook. Blanket dependabot ignore entries are banned; version-specific ignores only, commented and temporary. Bundled-dep vulnerabilities are a prompt to advance the pin, never to dismiss the alert.
219 lines
8.1 KiB
Markdown
219 lines
8.1 KiB
Markdown
# CDK Project Layout
|
|
|
|
## When to Use CDK
|
|
|
|
SAM is the default for new serverless stacks (Lambda + API Gateway + DynamoDB). Use CDK when the infrastructure goes beyond what SAM handles cleanly:
|
|
|
|
- ECS Fargate tasks
|
|
- VPCs, subnets, security groups
|
|
- Multi-service compositions (e.g., SES + Lambda + DynamoDB + Bedrock Agent)
|
|
- L2/L3 constructs not available in SAM (Bedrock agents, Knowledge Bases)
|
|
- Docker-based Lambda or container workloads
|
|
|
|
If the project is a handful of Lambdas behind API Gateway, use SAM. See [sam-project-layout.md](sam-project-layout.md).
|
|
|
|
## Standard Directory Structure
|
|
|
|
```
|
|
project-name/
|
|
├── bin/
|
|
│ └── app.ts # CDK app entry point
|
|
├── lib/
|
|
│ └── project-name-stack.ts # Stack definition
|
|
├── lambdas/
|
|
│ ├── function-name/
|
|
│ │ ├── index.ts # Handler (Node) or app.py (Python)
|
|
│ │ └── package.json # Per-function deps (Node) or requirements.txt (Python)
|
|
│ └── shared/ # Shared utilities across functions (if needed)
|
|
├── cdk.json
|
|
├── package.json
|
|
├── package-lock.json
|
|
├── tsconfig.json
|
|
├── .gitignore
|
|
└── .github/
|
|
└── workflows/
|
|
├── ci.yaml
|
|
└── deploy.yaml
|
|
```
|
|
|
|
## File Purposes
|
|
|
|
### `bin/app.ts`
|
|
|
|
The CDK app entry point. Instantiates the stack with an explicit `stackName` to enforce kebab-case (see Naming below).
|
|
|
|
```typescript
|
|
import * as cdk from 'aws-cdk-lib';
|
|
import { ProjectNameStack } from '../lib/project-name-stack';
|
|
|
|
const app = new cdk.App();
|
|
new ProjectNameStack(app, 'ProjectNameStack', {
|
|
stackName: 'project-name',
|
|
env: { account: '328440206208', region: 'us-east-1' },
|
|
});
|
|
```
|
|
|
|
### `lib/project-name-stack.ts`
|
|
|
|
All resource definitions. For larger projects, split into multiple constructs under `lib/` and compose them in the stack file. Keep the stack class thin -- it wires constructs together, not defines low-level resources.
|
|
|
|
### `lambdas/`
|
|
|
|
Lambda handler source code. One directory per function, same convention as SAM's `src/` directory. Each function has its own dependency file. CDK references these via `Code.fromAsset('lambdas/function-name')` or `NodejsFunction`'s `entry` property.
|
|
|
|
### `cdk.json`
|
|
|
|
CDK context and feature flags. Committed to the repo. Generated by `cdk init` -- keep the default `app` command pointing at `bin/app.ts`.
|
|
|
|
## Naming
|
|
|
|
CDK generates PascalCase stack names by default. Always set an explicit `stackName` to enforce kebab-case:
|
|
|
|
```typescript
|
|
new MyStack(app, 'MyStack', {
|
|
stackName: 'my-stack',
|
|
});
|
|
```
|
|
|
|
All resource names within the stack follow the same kebab-case convention: `my-stack-process-orders` for Lambdas, `my-stack-orders` for tables, `my-stack/api-key` for secrets. See [naming-conventions.md](naming-conventions.md).
|
|
|
|
### Legacy stacks
|
|
|
|
A handful of stacks predate this convention and remain PascalCase (e.g., `SeaHavenDoorUnlockStack`). Do not rename these -- changing the stack name requires replacement of all resources. New stacks must use kebab-case from day one.
|
|
|
|
## Version Pinning
|
|
|
|
Pin `aws-cdk-lib` to an exact version (no `^`/`~`/`>=`) and let Dependabot keep it current — no blanket ignore entries. See [aws-infrastructure.md](aws-infrastructure.md#cdk-version-policy) for the full policy.
|
|
|
|
`aws-cdk-lib` bundles transitive dependencies (`inBundle: true`). Certain versions break `npm ci` with phantom missing-package errors that cannot be fixed via npm `overrides`. CI gates Dependabot bumps automatically; when bumping manually, test first:
|
|
|
|
1. Update `package.json` to the new version
|
|
2. Run `rm -rf node_modules package-lock.json && npm install`
|
|
3. Run `npm ci` -- if it fails, the version is not safe
|
|
4. Run `npx cdk synth` -- if it fails, the version is not safe
|
|
|
|
Dependabot will flag vulnerabilities in bundled transitive deps. npm `overrides` cannot fix these — the remedy is advancing to the release that bundles the patched version. Merge the next `aws-cdk-lib` bump rather than dismissing the alert.
|
|
|
|
See [aws-infrastructure.md](aws-infrastructure.md#cdk-version-policy) for more detail.
|
|
|
|
## overrideLogicalId
|
|
|
|
Never remove `overrideLogicalId` calls from existing resources.
|
|
|
|
CDK auto-generates CloudFormation logical IDs with hash suffixes (e.g., `OrdersTable4A3B2C1D`). When you override the logical ID to something stable (e.g., `OrdersTable`), CloudFormation tracks the resource under that name. Removing the override changes the logical ID back to the hashed version, which CloudFormation interprets as:
|
|
|
|
1. **Delete** the resource with the old logical ID
|
|
2. **Create** a new resource with the new logical ID
|
|
|
|
For named resources (Secrets Manager secrets, S3 buckets, IAM roles), the create fails because the physical name already exists. For unnamed resources (DynamoDB tables without explicit names), the delete succeeds -- and takes your data with it.
|
|
|
|
If you need to refactor construct tree paths, add new `overrideLogicalId` calls to preserve the existing logical IDs. Never remove existing ones without understanding the CloudFormation diff (`cdk diff`).
|
|
|
|
## ARM64 Docker Builds
|
|
|
|
When using `ContainerImage.fromAsset()` for ARM64 Fargate tasks (or any ARM64 container), two things are required:
|
|
|
|
### 1. Platform flag in CDK
|
|
|
|
```typescript
|
|
import * as ecr_assets from 'aws-cdk-lib/aws-ecr-assets';
|
|
|
|
ContainerImage.fromAsset('path/to/docker', {
|
|
platform: ecr_assets.Platform.LINUX_ARM64,
|
|
});
|
|
```
|
|
|
|
Without the `platform` flag, CDK builds an x86 image regardless of what the Dockerfile or CI runner does. The container will crash on an ARM64 Fargate task with `exec format error`.
|
|
|
|
### 2. QEMU in GitHub Actions
|
|
|
|
The CI runner is x86. To build ARM64 images, the workflow needs QEMU:
|
|
|
|
```yaml
|
|
- uses: docker/setup-qemu-action@v3
|
|
```
|
|
|
|
Add this step before `cdk deploy` or `cdk synth` in the deploy workflow. Both the platform flag and QEMU are required -- one without the other produces a broken image.
|
|
|
|
## Lambda Defaults
|
|
|
|
Same defaults as SAM projects. Verify these on every Lambda in every CDK stack:
|
|
|
|
| Setting | Value |
|
|
|---|---|
|
|
| Runtime | Python 3.12 or Node 24.x |
|
|
| Architecture | arm64 |
|
|
| Log retention | 60 days (explicit `RetentionInDays`) |
|
|
| Naming | kebab-case, prefixed with stack name |
|
|
|
|
CDK's `NodejsFunction` and `PythonFunction` constructs auto-create log groups, but the default retention is **never expire**. Always set it explicitly:
|
|
|
|
```typescript
|
|
new logs.LogGroup(this, 'HandlerLogs', {
|
|
logGroupName: `/aws/lambda/${fn.functionName}`,
|
|
retention: logs.RetentionDays.TWO_MONTHS,
|
|
});
|
|
```
|
|
|
|
See [aws-infrastructure.md](aws-infrastructure.md#lambda-defaults).
|
|
|
|
## CI/CD
|
|
|
|
Same OIDC deploy role pattern as SAM. Each repo gets its own `githubdeploy-<repo-name>` IAM role -- never share deploy roles across repos.
|
|
|
|
CDK projects use the TypeScript CDK reusable workflows:
|
|
|
|
```yaml
|
|
# .github/workflows/ci.yaml
|
|
name: CI
|
|
on:
|
|
pull_request:
|
|
branches: [main]
|
|
jobs:
|
|
ci:
|
|
uses: Sea-Haven-Industries/.github/.github/workflows/ci-typescript-cdk.yaml@main
|
|
with:
|
|
node-version: "24"
|
|
|
|
# .github/workflows/deploy.yaml
|
|
name: Deploy
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
jobs:
|
|
deploy:
|
|
uses: Sea-Haven-Industries/.github/.github/workflows/cd-cdk.yaml@main
|
|
with:
|
|
node-version: "24"
|
|
secrets:
|
|
deploy-role-arn: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
|
|
```
|
|
|
|
Always pass `node-version: "24"` explicitly. See [cicd.md](cicd.md) for the full pipeline convention.
|
|
|
|
## Bedrock Agents
|
|
|
|
CDK is the standard IaC for Bedrock Agents and Knowledge Bases (SAM lacks L2 constructs for these). Key gotchas:
|
|
|
|
- Use cross-region inference profile IDs for `CfnAgent.foundationModel`, not direct model IDs
|
|
- Bump the alias `description` to force version rotation after model or instruction changes
|
|
- `VectorKnowledgeBase` requires Docker Desktop on the build machine
|
|
|
|
See [bedrock.md](bedrock.md) for inference profile formats, IAM permissions, alias version pinning, and action group conventions.
|
|
|
|
## Standard .gitignore
|
|
|
|
```
|
|
cdk.out/
|
|
node_modules/
|
|
*.js
|
|
*.d.ts
|
|
*.js.map
|
|
.env
|
|
```
|
|
|
|
The `*.js` / `*.d.ts` / `*.js.map` entries assume TypeScript source with compiled output excluded from version control. If you have JavaScript Lambda handlers under `lambdas/`, add a negation:
|
|
|
|
```
|
|
!lambdas/**/*.js
|
|
```
|