Skip to content

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


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:

  1. Watches your GitHub repo for pushes to main
  2. Synthesizes CDK (generates CloudFormation templates)
  3. Updates itself if the pipeline definition changed
  4. Deploys your runtime infrastructure (ECS Fargate + ALB + Gateway registration)
  5. Runs E2E tests against the deployed environment
  6. 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.


Diagram

Pulls code from GitHub via a CodeStar connection. Triggered on every push to the configured branch.

This is where most of the work happens. In a single CodeBuild step:

Terminal window
npm run co:login # Authenticate with CodeArtifact
npm ci # Install CDK dependencies
(cd src && npm ci) # Install app dependencies
(cd src && npm run sync:translations) # Sync i18n translations
npm run audit # Security audit (high severity)
npm run lint # ESLint
npm run test # Vitest unit tests
npm run build # TypeScript compilation (CDK)
npm run cdk synth # Generate CloudFormation templates

If any step fails, the pipeline stops. No broken code reaches deployment.

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.

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.

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

Every MFE produces exactly two CloudFormation stacks:

StackName PatternWhat It ContainsWho Deploys It
CI{project}-ciCodePipeline, CodeBuild projects, IAM rolesYou (once, manually)
Runtime{project}-rtECS Fargate, ALB, Gateway registration, alarmsThe 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.


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:

Terminal window
project="omni-web-catalog-pdp" \
repo="omni-web-catalog-pdp" \
branch="main" \
environmentName="preprod" \
npx cdk deploy

After this, every push to main triggers the full pipeline automatically.


All CodeBuild steps share these defaults:

SettingValue
Node.js24
ArchitectureARM64 (Graviton)
ComputeSMALL
ImageAmazon Linux 2 ARM 3
PrivilegedYes (for Docker builds)
Cachenode_modules/**/*, src/node_modules/**/*
Pipeline typeV2 (faster, event-driven)

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.

Graviton instances are ~20% cheaper and ~20% faster for Node.js workloads. All builds run on ARM.

V2 pipelines are event-driven (no polling), support parallel stages, and have better pricing for low-frequency pipelines.

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.

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.


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.


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)

To create a pipeline for a new MFE:

  1. Copy the lib/pipeline/ folder from an existing MFE (e.g., PDP)
  2. Update bin/solution.ts with your service/component names
  3. Add @hema/common-types, @hema/monitoring-constructs, and @hema/omni-web-gateway-management-library-constructs to your CDK dependencies
  4. Run the one-time deploy:
    Terminal window
    project="your-service-name" \
    repo="your-repo-name" \
    branch="main" \
    environmentName="preprod" \
    npx cdk deploy
  5. Push to main — the pipeline takes over from here