Session 004 — CDK v2: setup, bootstrap and project structure
Estimated duration: 60 minutes
Prerequisites: session-003 — CloudFormation: changesets, drift detection and stack policies
Objective
By the end, you will be able to initialize a CDK v2 project in TypeScript or Python, run cdk bootstrap on an account/region, understand what the bootstrap provisions (S3 bucket, ECR, IAM roles), and run cdk synth, cdk diff and cdk deploy on a simple stack.
Context
[FACT] AWS CDK (Cloud Development Kit) is an open-source framework released in 2019 that allows you to define infrastructure using general-purpose programming languages (TypeScript, Python, Java, Go, C#). CDK does not replace CloudFormation — it generates CloudFormation as an output artifact. Every cdk deploy ends with a CloudFormation changeset being executed.
[CONSENSUS] The main advantage of CDK over raw CloudFormation is not just syntax — it's abstraction with escape hatch. You write less for common cases (L2 constructs handle permissions, logs, and secure defaults automatically), but you can always drop down to the CloudFormation level when you need full control. This is the core proposition of the L1/L2/L3 construct model, which session 005 covers in depth.
[FACT] CDK v2 was released in December 2021. The main difference from v1 is that all AWS service constructs were consolidated into a single package (aws-cdk-lib) instead of separate per-service packages. CDK v1 reached end-of-support in June 2023. Always use v2.
Key concepts
1. Mental model: CDK is an infrastructure compiler
CDK transforms code into CloudFormation. The complete flow is:
Seu código (TypeScript/Python)
│
▼ cdk synth
Cloud Assembly (cdk.out/)
├── MeuStack.template.json ← CloudFormation template gerado
├── asset.XXXX/ ← arquivos locais a fazer upload (Lambda code, etc.)
└── manifest.json ← metadados do assembly
│
▼ cdk deploy
Upload assets → S3/ECR (bucket do bootstrap)
Cria/atualiza stack CloudFormation via changeset
│
▼
Recursos AWS criados
When a deploy fails, the actual error is in CloudFormation — not in CDK. That's why knowing CloudFormation (sessions 002-003) is an essential prerequisite.
2. Installation and prerequisites
[FACT] The CDK CLI is an npm package. The minimum runtime is Node.js 18.x (even if you write in Python or Go — the CLI is always Node.js).
# Verificar versão do Node
node --version # precisa ser >= 18.x
# Instalar o CDK CLI globalmente
npm install -g aws-cdk
# Verificar versão instalada
cdk --version # ex: 2.176.0 (build xxxxxxx)
# Para Python: instalar a lib no projeto (não no sistema)
pip install aws-cdk-lib constructs
[CONSENSUS] Installing the CDK CLI globally (-g) is convenient for local use, but in CI/CD pipelines prefer installing it locally in the project (npm install aws-cdk) to pin the version. The CLI version and the aws-cdk-lib version in package.json must be compatible — version differences between CLI and lib can cause warnings or synth errors.
3. Structure of a CDK v2 project
Initializing a new project:
mkdir meu-projeto && cd meu-projeto
# TypeScript (mais comum em exemplos oficiais)
cdk init app --language typescript
# Python
cdk init app --language python
Generated structure for TypeScript:
meu-projeto/
├── bin/
│ └── meu-projeto.ts ← Entry point: instancia o App e os Stacks
├── lib/
│ └── meu-projeto-stack.ts ← Definição dos constructs (onde você trabalha)
├── test/
│ └── meu-projeto.test.ts ← Testes com assertions (sessão 008)
├── cdk.json ← Configuração do CDK CLI
├── package.json
├── tsconfig.json
└── node_modules/
Generated structure for Python:
meu-projeto/
├── app.py ← Entry point
├── meu_projeto/
│ ├── __init__.py
│ └── meu_projeto_stack.py ← Definição dos constructs
├── tests/
├── cdk.json
├── requirements.txt
└── .venv/ ← virtualenv (criar manualmente: python -m venv .venv)
The entry point (bin/meu-projeto.ts):
import * as cdk from 'aws-cdk-lib';
import { MeuProjetoStack } from '../lib/meu-projeto-stack';
const app = new cdk.App();
new MeuProjetoStack(app, 'MeuProjetoStack', {
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
[FACT] CDK_DEFAULT_ACCOUNT and CDK_DEFAULT_REGION are environment variables automatically populated by the CDK CLI based on the active AWS profile. Hardcoding account/region is possible but not recommended — it prevents reusing the same code across multiple accounts.
The stack (lib/meu-projeto-stack.ts):
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { Construct } from 'constructs';
export class MeuProjetoStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// Construct L2 — nível de abstração alto
new s3.Bucket(this, 'MeuBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
removalPolicy: cdk.RemovalPolicy.DESTROY, // equivalente a DeletionPolicy
autoDeleteObjects: true,
});
}
}
Construct hierarchy:
App ← raiz de tudo
└── Stack (MeuProjetoStack)
└── Bucket (MeuBucket)
└── BucketPolicy (gerado automaticamente pelo L2)
Each node in the tree has a unique ID within its parent. CDK uses this path to generate logical IDs in CloudFormation: MeuProjetoStackMeuBucketXXXXXXXX.
4. Bootstrap — what it is and what it provisions
Why bootstrap is necessary:
When CDK deploys assets (Lambda code, Docker images, local files), it needs a place to store them before passing them to CloudFormation. The bootstrap creates this supporting infrastructure.
[FACT] cdk bootstrap deploys a CloudFormation stack called CDKToolkit in the account/region. This stack provisions:
CDKToolkit stack
├── S3 Bucket (cdk-xxxxxxxx-assets-ACCOUNT-REGION)
│ └── Armazena templates sintetizados e assets locais
│ Versionado e com lifecycle policy de 365 dias
│
├── ECR Repository (cdk-xxxxxxxx-container-assets-ACCOUNT-REGION)
│ └── Armazena imagens Docker para Lambda/ECS
│
└── IAM Roles (6 roles criadas por padrão)
├── cdk-xxxxxxxx-lookup-role-ACCOUNT-REGION
│ └── Permite ao CDK CLI fazer lookups (VPCs, AMIs, etc.)
├── cdk-xxxxxxxx-file-publishing-role-ACCOUNT-REGION
│ └── Upload de assets para o S3
├── cdk-xxxxxxxx-image-publishing-role-ACCOUNT-REGION
│ └── Push de imagens para o ECR
├── cdk-xxxxxxxx-deploy-role-ACCOUNT-REGION
│ └── Criação/atualização de stacks CloudFormation
├── cdk-xxxxxxxx-cfn-exec-role-ACCOUNT-REGION
│ └── Role que o CloudFormation assume para criar recursos
└── cdk-xxxxxxxx-readonly-role-ACCOUNT-REGION (v2 recente)
└── Permissões de leitura para diff e synth
Running the bootstrap:
# Bootstrap na conta/região atual (usa o perfil padrão)
cdk bootstrap
# Bootstrap explicitando conta e região
cdk bootstrap aws://123456789012/us-east-1
# Bootstrap com perfil específico
cdk bootstrap --profile prod aws://123456789012/us-east-1
# Bootstrap multi-região (executar uma vez por região)
cdk bootstrap aws://123456789012/us-east-1
cdk bootstrap aws://123456789012/sa-east-1
# Ver o que seria criado sem executar
cdk bootstrap --show-template
Bootstrap qualifier:
[FACT] The qualifier is a 9-character identifier (default: hnb659fds) that is part of the bootstrap resource names. It allows having multiple bootstraps in the same account/region — useful when different teams need asset isolation.
# Bootstrap com qualifier customizado
cdk bootstrap --qualifier meutime
# No cdk.json, referenciar o qualifier
{
"@aws-cdk/core:bootstrapQualifier": "meutime"
}
When to re-run the bootstrap:
The bootstrap has a version number. When updating the CDK CLI to a major version, you may need to update the bootstrap. CDK warns during cdk deploy if the bootstrap version is outdated:
❌ This CDK deployment requires bootstrap stack version '6', found '4'.
Please run 'cdk bootstrap'.
Running cdk bootstrap on an already-bootstrapped account/region is safe — it updates existing resources without recreating them.
5. The main CDK CLI commands
cdk synth — compile to CloudFormation:
# Sintetizar todos os stacks
cdk synth
# Sintetizar um stack específico
cdk synth MeuProjetoStack
# Ver o template gerado no terminal (sem salvar em cdk.out/)
cdk synth --quiet
# Output em cdk.out/ por padrão
ls cdk.out/
# MeuProjetoStack.template.json
# manifest.json
# tree.json
cdk synth is the equivalent of "compiling" — it transforms code into CloudFormation. It detects configuration errors before any AWS calls are made.
cdk diff — see what will change:
# Diff entre o código local e a stack deployada
cdk diff
# Diff de um stack específico
cdk diff MeuProjetoStack
# Diff contra um template salvo (sem stack deployada)
cdk diff --template cdk.out/MeuProjetoStack.template.json
cdk diff is the equivalent of the changeset from the previous session — but calculated locally, without creating anything in AWS. It's faster than a real changeset, but less precise for resources with values resolved at runtime.
cdk deploy — deploy:
# Deploy com aprovação interativa de IAM changes
cdk deploy
# Deploy sem aprovação (útil em pipelines)
cdk deploy --require-approval never
# Deploy de múltiplos stacks
cdk deploy Stack1 Stack2
# Deploy de todos os stacks
cdk deploy "*"
# Deploy com hotswap (para desenvolvimento — evita CloudFormation para mudanças em Lambda)
cdk deploy --hotswap
# Deploy e mostrar os outputs da stack
cdk deploy --outputs-file outputs.json
[FACT] cdk deploy --hotswap detects changes that are only in Lambda code or ECS definitions and applies them directly (without CloudFormation), reducing feedback time from ~2 minutes to ~10 seconds. Never use hotswap in production — it can leave the CloudFormation state out of sync with the actual state.
cdk destroy — destroy the stack:
cdk destroy MeuProjetoStack
# Pede confirmação interativa
cdk destroy --force MeuProjetoStack
# Sem confirmação
Command flow diagram:
Código CDK
│
├─ cdk synth ──► cdk.out/ (CloudFormation templates + assets)
│
├─ cdk diff ──► Comparação local vs. stack deployada (sem chamadas AWS)
│
├─ cdk deploy
│ ├── cdk synth (implícito)
│ ├── Upload assets → S3/ECR (via roles do bootstrap)
│ ├── Cria changeset CloudFormation
│ ├── Mostra IAM changes para aprovação (se houver)
│ └── Executa changeset → aguarda conclusão
│
└─ cdk destroy
├── Cria changeset de deleção
└── Executa → deleta todos os recursos (respeitando DeletionPolicy)
6. cdk.json — project configuration
cdk.json is the CDK CLI configuration file for the project. Every cdk command run in the folder reads this file.
{
"app": "npx ts-node --prefer-ts-exts bin/meu-projeto.ts",
"watch": {
"include": ["**"],
"exclude": ["README.md", "cdk*.json", "**/*.d.ts", "**/*.js", "tsconfig.json",
"package*.json", ".git/", "node_modules/", "cdk.out/"]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true
}
}
Key fields:
app → comando para executar o entry point (compilador/runtime)
watch → arquivos a monitorar para cdk watch (hot reload em dev)
context → feature flags e valores de contexto persistidos
[FACT] The values in context that start with @aws-cdk/ are feature flags. They control behaviors that changed between CDK versions and allow gradual migration. New projects created with cdk init automatically receive the set of feature flags from the current version.
What goes in gitignore:
cdk.out/ ← sempre ignorar (artefato de build)
cdk.context.json ← DEPENDE (veja sessão 012 para a discussão completa)
node_modules/ ← sempre ignorar
.venv/ ← sempre ignorar (Python)
Hands-on example
Create a CDK project from scratch and do the first deploy:
# 1. Criar e inicializar o projeto
mkdir demo-cdk && cd demo-cdk
cdk init app --language typescript
npm install
# 2. Bootstrap (uma vez por conta/região)
cdk bootstrap --profile dev
# 3. Editar lib/demo-cdk-stack.ts
cat > lib/demo-cdk-stack.ts << 'EOF'
import * as cdk from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';
export class DemoCdkStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const bucket = new s3.Bucket(this, 'DemoBucket', {
versioned: true,
encryption: s3.BucketEncryption.S3_MANAGED,
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
removalPolicy: cdk.RemovalPolicy.DESTROY,
autoDeleteObjects: true,
});
const appRole = new iam.Role(this, 'AppRole', {
assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'),
],
});
// L2 cuida das permissões — gera a policy correta automaticamente
bucket.grantReadWrite(appRole);
// Outputs
new cdk.CfnOutput(this, 'BucketName', { value: bucket.bucketName });
new cdk.CfnOutput(this, 'AppRoleArn', { value: appRole.roleArn });
}
}
EOF
# 4. Sintetizar — ver o CloudFormation gerado
cdk synth
# 5. Ver o que vai ser criado (diff contra stack inexistente)
cdk diff --profile dev
# 6. Deploy
cdk deploy --profile dev
# 7. Ver o CloudFormation template gerado (para comparar com o que você escreveria)
cat cdk.out/DemoCdkStack.template.json | jq '.Resources | keys'
# 8. Comparar o que o L2 gerou vs o que você escreveria manualmente
# O bucket.grantReadWrite(appRole) gera automaticamente uma IAM Policy
# com as permissões corretas — sem precisar escrever o ARN do bucket
cat cdk.out/DemoCdkStack.template.json | jq '.Resources | to_entries[] | select(.value.Type == "AWS::IAM::Policy")'
Common pitfalls
1. Forgetting to re-run bootstrap after a CDK update
After npm install -g aws-cdk@latest, if the new version requires a more recent bootstrap version, the next cdk deploy fails with the incompatible version message. The fix is simple (cdk bootstrap), but it confuses those who don't know why it's failing.
2. RemovalPolicy.DESTROY in production
CDK uses RemovalPolicy.RETAIN as the default for most stateful resources (S3, RDS, DynamoDB). Changing to DESTROY makes cdk destroy delete the resource and its data. In projects created from tutorials, it's common to forget this value copied from development examples when promoting to production.
3. Unstable logical ID — the rename problem
CDK derives the CloudFormation logical ID from the construct path in the tree (Stack > Construct > Resource). If you rename a construct or move a resource in the hierarchy, the logical ID changes — and CloudFormation interprets this as "delete the old one and create a new one". For stateful resources (RDS, S3 with data), this is destructive. The solution is to use overrideLogicalId() to pin the ID when necessary.
// Fixar o logical ID para evitar substituição acidental ao refatorar
const cfnBucket = bucket.node.defaultChild as s3.CfnBucket;
cfnBucket.overrideLogicalId('MeuBucketEstavel');
Reflection exercise
You are migrating an existing CloudFormation stack to CDK. The original template has an S3 bucket with logical ID AppBucket. When writing the CDK code, you create the bucket like this:
new s3.Bucket(this, 'AppBucket', { versioned: true });
CDK will generate a different logical ID from the original (something like AppBucketXXXXXXXX with a hash). If you run cdk deploy on the existing stack, CloudFormation will try to create a new bucket and delete the original one.
What are the two approaches to solve this problem? For each one, describe the concrete steps and the trade-offs. Also consider: why does CDK add this hash to the logical ID and what problem does it solve in the normal lifecycle of a CDK project.
Resources for further reading
Setup and first project:
- Getting started with the AWS CDK — prerequisites, installation, and the hello-world tutorial with TypeScript and Python.
Bootstrap in depth:
- AWS CDK bootstrapping — lists all created resources, bootstrap versions, and when to re-run.
CLI reference:
- AWS CDK CLI reference — all commands with flags, including --hotswap, --watch, --outputs-file and approval options.
CDK Workshop (hands-on):
- CDK Workshop — hands-on tutorial with TypeScript or Python covering App → Stack → Constructs → Pipeline in ~2 hours. Recommended to consolidate this session in practice.