GitLab CI + AWS ALB
Two TypeScript source directories produce two CloudFormation stacks and two GitLab CI pipelines. One shared ALB fans out to two Fargate services based on path rules. The split is by deploy unit: the infrastructure everything else depends on is its own stack, and the two services that ride on it are declared together in one project because they deploy together.
What you’ll build
Section titled “What you’ll build”┌─────────────────────────────────────────────────────────────────┐│ shared-alb stack (gitlab-aws-alb-infra) ││ VPC + ALB + ECS cluster + ECR repos │└──────────────────────────────┬──────────────────────────────────┘ │ ClusterArn, ListenerArn, ECR URIs ▼┌─────────────────────────────────────────────────────────────────┐│ shared-alb-services stack (gitlab-aws-alb-services) ││ Fargate at /api/* (priority 100) ││ Fargate at /* (priority 200, catch-all) │└─────────────────────────────────────────────────────────────────┘The two projects:
| Project | Stack | What it declares |
|---|---|---|
examples/gitlab-aws-alb-infra/ | shared-alb | VPC, ALB, ECS cluster, ECR repos, the stack outputs the services consume, the deploy pipeline, and the alb-deploy Op in ops/ |
examples/gitlab-aws-alb-services/ | shared-alb-services | Both FargateService composites, their image parameters, and the pipeline that builds both images and deploys the stack |
lexicons/aws/examples/shared-alb/, shared-alb-api/ and shared-alb-ui/ hold the AWS-only halves of the same declarations, without the GitLab pipeline; lexicons/aws/examples/multi-service-alb/ puts all of it in one stack.
What you’ll learn
Section titled “What you’ll learn”- How
FargateServicecomposite wires a Fargate task to an ALB listener rule — path pattern, priority, health check path, and security group all in one call - Why infra must deploy before services: the ECR repos live in the infra stack; the service pipeline pushes images to them
- How two services share one stack: two composite calls in one
src/, two image parameters, oneaws cloudformation deploy - Two pitfalls already solved in the examples:
amazon/aws-clientrypoint override and$CI_COMMIT_REF_SLUGin jq expressions
Get started
Section titled “Get started”Build the CloudFormation templates and the GitLab CI pipelines for thegitlab-aws-alb-infra and gitlab-aws-alb-services examples.My AWS credentials are in ~/.aws/credentials, region is us-east-1,account ID is 123456789012.See the example READMEs for full instructions:
Key patterns
Section titled “Key patterns”FargateService: one composite, seven resources
Section titled “FargateService: one composite, seven resources”Each service is a single composite call. It expands to seven CloudFormation resources: a Fargate task definition, ECS service, ALB listener rule, target group, task role, security group, and CloudWatch log group. Two calls in one src/ is why the merged stack has 14:
export const api = FargateService({ clusterArn: Ref(clusterArn), // from infra stack parameter listenerArn: Ref(listenerArn), // from infra stack parameter image: Ref(apiImage), // injected by CI: ECR_URI:$CI_COMMIT_REF_SLUG containerPort: 8080, pathPatterns: ["/api", "/api/*"], healthCheckPath: "/api/get", priority: 100, // UI is 200 (evaluated later, catch-all)});Both services sit in one src/, so one chant build produces one template with both. They share the ALB parameters and differ only where they must: the path pattern and priority that route traffic to them, and which image parameter each one reads (apiImage or uiImage).
The stack receives infra stack outputs as CloudFormation parameters — clusterArn, listenerArn, vpcId, subnet IDs, execution role ARN. The pipeline fetches them with aws cloudformation describe-stacks and passes them as --parameter-overrides.
amazon/aws-cli entrypoint override
Section titled “amazon/aws-cli entrypoint override”The amazon/aws-cli Docker image sets aws as its entrypoint. GitLab CI prepends the image entrypoint to every script command, turning aws cloudformation deploy into aws aws cloudformation deploy. Fix: entrypoint: [""].
const awsImage = new Image({ name: "amazon/aws-cli:latest", entrypoint: [""], // ← critical — overrides the image's aws entrypoint});$CI_COMMIT_REF_SLUG in jq
Section titled “$CI_COMMIT_REF_SLUG in jq”Shell variables inside single-quoted jq expressions are not expanded. The examples build the ECR image URI by appending the tag outside jq:
# Right — expand outside jqIMAGE_URI=$(echo "$OUTPUTS" | jq -r '.Stacks[0].Outputs[] | select(.OutputKey=="ApiRepoUri") | .OutputValue'):$CI_COMMIT_REF_SLUGDeploy order
Section titled “Deploy order”1. Push infra repo → infra pipeline → shared-alb stack created (ECR repos exist)2. Push services repo → services pipeline → two parallel docker builds → ECR push → one CF deploy of shared-alb-servicesIf the services pipeline runs before infra, docker push fails with “repository does not exist.”
examples/gitlab-aws-alb-infra/ops/alb-deploy.op.ts declares that order as a single named workflow: a parallel Build phase over both projects, a sequential Deploy phase that applies shared-alb and then shared-alb-services, and a Verify phase that snapshots. chant run alb-deploy from the infra directory runs it without GitLab at all.
Further reading
Section titled “Further reading”- AWS CloudFormation lexicon — FargateService, AlbShared, and other composites
- GitLab CI/CD lexicon — job types, pipeline composites, environments
- Multi-Stack Projects guide — patterns for splitting infrastructure across stacks