CI/CD Pipeline Overview
Source: omni-web-catalog-pdp/lib/pipeline/pipeline-stack.ts Runtime: Node.js 24 | CDK 2.251 | CodePipeline V2 | ARM64 builds
The Ideal Minimal Pipeline
Section titled “The Ideal Minimal Pipeline”Every MFE in hema100 follows the same pipeline pattern. The key insight: you deploy the pipeline once, and it takes care of everything after that — including updating itself.
The pipeline is a self-mutating CodePipeline that:
- Watches your GitHub repo for pushes to
main - Synthesizes CDK (generates CloudFormation templates)
- Updates itself if the pipeline definition changed
- Deploys your runtime infrastructure (ECS Fargate + ALB + Gateway registration)
- Runs E2E tests against the deployed environment
- Cleans up stale CDK assets
This means after the initial cdk deploy, you never manually deploy again. Every push to main triggers the full pipeline automatically.
Pipeline Architecture (PDP Example)
Section titled “Pipeline Architecture (PDP Example)”What Each Stage Does
Section titled “What Each Stage Does”1. Source
Section titled “1. Source”Pulls code from GitHub via a CodeStar connection. Triggered on every push to the configured branch.
2. Synth (the heavy lifter)
Section titled “2. Synth (the heavy lifter)”This is where most of the work happens. In a single CodeBuild step:
npm run co:login # Authenticate with CodeArtifactnpm ci # Install CDK dependencies(cd src && npm ci) # Install app dependencies(cd src && npm run sync:translations) # Sync i18n translationsnpm run audit # Security audit (high severity)npm run lint # ESLintnpm run test # Vitest unit testsnpm run build # TypeScript compilation (CDK)npm run cdk synth # Generate CloudFormation templatesIf any step fails, the pipeline stops. No broken code reaches deployment.
3. Self-Mutate
Section titled “3. Self-Mutate”CDK Pipelines magic: if you changed the pipeline definition itself (e.g., added a new stage), the pipeline updates itself before proceeding. You never need to manually redeploy the pipeline.
4. Deploy Runtime
Section titled “4. Deploy Runtime”Deploys the runtime stack via CloudFormation:
- ECS Fargate service running your Next.js Docker container
- Application Load Balancer with health checks
- Gateway Registration — registers your routes with the CloudFront gateway
- Monitoring alarms (CPU, memory, 5xx errors)
- VPC Origin for secure CloudFront → ALB communication
For production environments, a Manual Approval step is added before deploy.
5. Post-Deploy
Section titled “5. Post-Deploy”Two parallel steps run after deployment:
- Playwright E2E — runs end-to-end tests against the deployed preview URLs
- Garbage Collect — cleans up stale CDK assets (old Docker images, S3 objects) older than 7 days
Two Stacks, One Repo
Section titled “Two Stacks, One Repo”Every MFE produces exactly two CloudFormation stacks:
| Stack | Name Pattern | What It Contains | Who Deploys It |
|---|---|---|---|
| CI | {project}-ci | CodePipeline, CodeBuild projects, IAM roles | You (once, manually) |
| Runtime | {project}-rt | ECS Fargate, ALB, Gateway registration, alarms | The pipeline (on every push) |
The CI stack is the “factory” that produces the Runtime stack. You bootstrap the factory once, and it builds everything else automatically.
Real Example: PDP Pipeline
Section titled “Real Example: PDP Pipeline”From omni-web-catalog-pdp/bin/solution.ts:
const environment = createEnvironment( process.env.environmentName, // e.g., "preprod" 'omni-web-catalog', // service name 'pdp', // component name);
const servicePipelineStack = new ServicePipelineStack(app, 'CI', { branch: process.env.branch, // "main" repo: process.env.repo, // "omni-web-catalog-pdp" project: process.env.project, // "omni-web-catalog-pdp" environment: environment, stackName: `${process.env.project}-ci`, intTestEnabled: false, butlerStackTag,});Deploy it once:
project="omni-web-catalog-pdp" \repo="omni-web-catalog-pdp" \branch="main" \environmentName="preprod" \npx cdk deployAfter this, every push to main triggers the full pipeline automatically.
Build Environment
Section titled “Build Environment”All CodeBuild steps share these defaults:
| Setting | Value |
|---|---|
| Node.js | 24 |
| Architecture | ARM64 (Graviton) |
| Compute | SMALL |
| Image | Amazon Linux 2 ARM 3 |
| Privileged | Yes (for Docker builds) |
| Cache | node_modules/**/*, src/node_modules/**/* |
| Pipeline type | V2 (faster, event-driven) |
Key Design Decisions
Section titled “Key Design Decisions”Why self-mutating?
Section titled “Why self-mutating?”You define the pipeline in the same repo as the code. Change the pipeline → push → it updates itself. No separate “infra repo” or manual CloudFormation updates.
Why ARM64?
Section titled “Why ARM64?”Graviton instances are ~20% cheaper and ~20% faster for Node.js workloads. All builds run on ARM.
Why CodePipeline V2?
Section titled “Why CodePipeline V2?”V2 pipelines are event-driven (no polling), support parallel stages, and have better pricing for low-frequency pipelines.
Why Playwright in the pipeline?
Section titled “Why Playwright in the pipeline?”E2E tests run against the actual deployed environment (not localhost). The pipeline gets the preview URLs from CloudFormation outputs and passes them to Playwright. If E2E fails, you know immediately.
Why Garbage Collection?
Section titled “Why Garbage Collection?”CDK assets (Docker images, S3 objects) accumulate over time. The GC step runs cdk gc with a 7-day rollback buffer, keeping costs down without risking rollback capability.
Pipeline Permissions
Section titled “Pipeline Permissions”The pipeline needs specific IAM permissions:
- CodeArtifact — read
@hema/*packages from the private registry - Secrets Manager — read Sanity credentials, basic auth for E2E
- EC2 Describe — CDK context lookups (VPC, subnets)
- SSM Parameters — read infrastructure config
- CloudFormation + ECR + S3 — garbage collection of stale assets
These are defined in ServicePipelineStack.createBuildPolicy() and applied to all CodeBuild steps.
Feature Branch Pipeline (Butler)
Section titled “Feature Branch Pipeline (Butler)”The main pipeline handles main branch deployments. For feature branches, Butler provides ephemeral sandbox environments using buildspec-ci.yaml.
The key difference:
- Main pipeline: Full pipeline with synth → self-mutate → deploy → E2E → GC
- Butler: Single CodeBuild run that does install → build →
cdk deploy(no self-mutation, no E2E)
Adding Your Own Pipeline
Section titled “Adding Your Own Pipeline”To create a pipeline for a new MFE:
- Copy the
lib/pipeline/folder from an existing MFE (e.g., PDP) - Update
bin/solution.tswith your service/component names - Add
@hema/common-types,@hema/monitoring-constructs, and@hema/omni-web-gateway-management-library-constructsto your CDK dependencies - Run the one-time deploy:
Terminal window project="your-service-name" \repo="your-repo-name" \branch="main" \environmentName="preprod" \npx cdk deploy - Push to
main— the pipeline takes over from here
Related
Section titled “Related”- Butler & Feature Sandboxes — ephemeral environments for feature branches
- Testing Strategy — unit, integration, and E2E testing patterns
- CDK Infrastructure — the runtime stack in detail