GitLab CI + AWS ALB
This tutorial takes you from zero to a fully deployed multi-service ALB on AWS, using the working examples in the chant repo. You’ll use Claude Code to build the templates, set up GitLab, and deploy — all by telling your agent what to do.
The examples are already written. Your job is to build them, configure your credentials, and push.
What you’ll deploy
Section titled “What you’ll deploy”Three CloudFormation stacks, deployed by three GitLab CI pipelines:
┌─────────────────────────────────────────────────────────────────┐│ GitLab ││ ││ gitlab-aws-alb-infra ──► shared-alb stack ││ (VPC, ALB, ECS cluster, ECR repos) ││ ││ gitlab-aws-alb-api ──► shared-alb-api stack ││ (docker build → ECR push → Fargate service at /api/*) ││ ││ gitlab-aws-alb-ui ──► shared-alb-ui stack ││ (docker build → ECR push → Fargate service at /*) ││ │└─────────────────────────────────────────────────────────────────┘
┌──────────────┐ │ ALB │ │ (port 80) │ └──────┬───────┘ │ ┌─────────┴─────────┐ │ │ /api/* /* (default) │ │ ┌─────┴─────┐ ┌─────┴─────┐ │ API svc │ │ UI svc │ │ go-httpbin│ │ nginx │ └───────────┘ └───────────┘The source lives in two places:
| Example | Location | What it defines |
|---|---|---|
| Infra stack (AWS) | lexicons/aws/examples/shared-alb/ | VPC, ALB, ECS cluster, ECR repos, CF outputs |
| API service stack (AWS) | lexicons/aws/examples/shared-alb-api/ | Fargate service at /api/* with go-httpbin |
| UI service stack (AWS) | lexicons/aws/examples/shared-alb-ui/ | Fargate service at /* with nginx |
| Infra pipeline (GitLab) | examples/gitlab-aws-alb-infra/ | CF deploy job |
| API pipeline (GitLab) | examples/gitlab-aws-alb-api/ | Docker build → ECR push → CF deploy |
| UI pipeline (GitLab) | examples/gitlab-aws-alb-ui/ | Docker build → ECR push → CF deploy |
Prerequisites
Section titled “Prerequisites”- AWS account with IAM credentials (access key + secret key). You need broad permissions — CloudFormation, ECS, EC2, ECR, IAM, ELB, and CloudWatch Logs. Admin or power-user access works best.
- GitLab account — gitlab.com (free tier) or self-hosted.
glabCLI installed and authenticated:For self-hosted GitLab:Terminal window brew install glabglab auth loginglab auth login --hostname gitlab.mycompany.comchantandbuninstalled (Installation guide)- Claude Code with chant MCP configured (Agent Integration guide)
Step 1: Build the templates
Section titled “Step 1: Build the templates”The AWS and GitLab examples are already in the repo. You just need to build them.
“Build the CloudFormation templates for the shared-alb, shared-alb-api, and shared-alb-ui examples. Then build the GitLab CI pipelines for all three gitlab-aws-alb examples.”
The agent runs chant build in each example directory:
# AWS stacks → template.jsoncd lexicons/aws/examples/shared-alb && chant build src --lexicon aws -o template.jsoncd lexicons/aws/examples/shared-alb-api && chant build src --lexicon aws -o template.jsoncd lexicons/aws/examples/shared-alb-ui && chant build src --lexicon aws -o template.json
# GitLab pipelines → .gitlab-ci.ymlcd examples/gitlab-aws-alb-infra && chant build src --lexicon gitlab -o .gitlab-ci.ymlcd examples/gitlab-aws-alb-api && chant build src --lexicon gitlab -o .gitlab-ci.ymlcd examples/gitlab-aws-alb-ui && chant build src --lexicon gitlab -o .gitlab-ci.ymlThis gives you three CF templates and three .gitlab-ci.yml files, all generated from the TypeScript source.
What’s in the examples
Section titled “What’s in the examples”Infra stack — three files do all the work:
// shared-alb/src/network.ts — entire VPC in one lineexport const network = VpcDefault({});
// shared-alb/src/alb.ts — ALB + ECS cluster + execution roleexport const shared = AlbShared({ vpcId: network.vpc.VpcId, publicSubnetIds: [network.publicSubnet1.SubnetId, network.publicSubnet2.SubnetId],});
// shared-alb/src/ecr.ts — container registries for each serviceexport const apiRepo = new ECRRepository({ RepositoryName: "alb-api", ... });export const uiRepo = new ECRRepository({ RepositoryName: "alb-ui", ... });The infra stack exports everything service stacks need — cluster ARN, listener ARN, VPC ID, subnet IDs, ECR repo URIs — via CloudFormation outputs in outputs.ts.
Service stacks — each receives shared infra as parameters and creates a single Fargate service:
export const api = FargateService({ clusterArn: Ref(clusterArn), listenerArn: Ref(listenerArn), image: Ref(image), // injected by CI/CD pipeline containerPort: 8080, pathPatterns: ["/api", "/api/*"], healthCheckPath: "/api/get",});GitLab pipelines — the infra pipeline runs a single CF deploy job. The service pipelines have two stages: build a Docker image and push it to ECR, then deploy the CF stack with cross-stack parameter passing:
const awsImage = new Image({ name: "amazon/aws-cli:latest", entrypoint: [""] });
export const deployInfra = new Job({ stage: "deploy", image: awsImage, script: [ "aws cloudformation deploy --template-file templates/template.json " + "--stack-name shared-alb --capabilities CAPABILITY_IAM --no-fail-on-empty-changeset", ], rules: [new Rule({ if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH" })],});Step 2: Create GitLab projects and set credentials
Section titled “Step 2: Create GitLab projects and set credentials”“Create three private GitLab projects: gitlab-aws-alb-infra, gitlab-aws-alb-api, gitlab-aws-alb-ui. My AWS credentials are in ~/.aws/credentials, region is us-east-1, account ID is 123456789012.”
Tell the agent where your credentials are — ~/.aws/credentials, a specific profile, a file, or just paste them. It reads the values and sets everything up.
The agent:
-
Creates three projects:
Terminal window glab project create gitlab-aws-alb-infra --privateglab project create gitlab-aws-alb-api --privateglab project create gitlab-aws-alb-ui --private -
Sets CI/CD variables on each project:
| Variable | Description | Set on |
|---|---|---|
AWS_ACCESS_KEY_ID | IAM access key | All 3 projects |
AWS_SECRET_ACCESS_KEY | IAM secret key (masked) | All 3 projects |
AWS_DEFAULT_REGION | e.g. us-east-1 | All 3 projects |
AWS_ACCOUNT_ID | e.g. 123456789012 | API + UI only |
Step 3: Deploy
Section titled “Step 3: Deploy”“Push to GitLab and deploy — infra first, then the services.”
The agent handles the ordering:
-
Assembles each GitLab repo — copies the built CF template and
.gitlab-ci.ymlinto a fresh git repo for each project. The service repos also get aDockerfile(the API uses go-httpbin, the UI uses nginx). -
Pushes the infra repo first and monitors the pipeline:
Terminal window glab ci list --project user/gitlab-aws-alb-infraglab ci trace --project user/gitlab-aws-alb-infra -
Waits for the infra pipeline to succeed — VPC, ALB, ECS cluster, and ECR repos are now live.
-
Pushes API and UI repos — both can deploy in parallel now that ECR repos exist.
-
Monitors service pipelines — watches Docker build + CF deploy until both succeed.
Step 4: Verify
Section titled “Step 4: Verify”“Check that everything deployed correctly.”
The agent verifies all three stacks and tests the endpoints:
# Check stack statusaws cloudformation describe-stacks --stack-name shared-alb --query 'Stacks[0].StackStatus'aws cloudformation describe-stacks --stack-name shared-alb-api --query 'Stacks[0].StackStatus'aws cloudformation describe-stacks --stack-name shared-alb-ui --query 'Stacks[0].StackStatus'
# Get the ALB DNS nameALB_DNS=$(aws cloudformation describe-stacks --stack-name shared-alb \ --query 'Stacks[0].Outputs[?OutputKey==`AlbDnsName`].OutputValue' --output text)
# Test the servicescurl http://$ALB_DNS/api/get # → go-httpbin JSON responsecurl http://$ALB_DNS/ # → nginx welcome pageGotchas
Section titled “Gotchas”These are already handled in the examples, but knowing them helps if you customize the pipelines.
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 entrypoint to every script command, so aws cloudformation deploy ... becomes aws aws cloudformation deploy .... The examples fix this with entrypoint: [""]:
const awsImage = new Image({ name: "amazon/aws-cli:latest", entrypoint: [""], // ← critical});$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 image URI by appending the tag outside jq:
# Wrong — variable inside single quotes, not expandedIMAGE_URI=$(echo "$OUTPUTS" | jq -r '... + ":$CI_COMMIT_REF_SLUG"')
# Right — what the examples do: expand outside jqIMAGE_URI=$(echo "$OUTPUTS" | jq -r '...'):$CI_COMMIT_REF_SLUGECR repos must exist before push
Section titled “ECR repos must exist before push”Service pipelines push Docker images to ECR. If the infra stack hasn’t created the repos yet, docker push fails with “repository does not exist.” That’s why infra deploys first.
Alpine AWS CLI installation
Section titled “Alpine AWS CLI installation”The docker:27-cli image is Alpine-based. The examples install AWS CLI in before_script to authenticate with ECR:
apk add --no-cache aws-cliSubsequent deployments
Section titled “Subsequent deployments”After the initial deployment:
- Code changes to API or UI — push to the respective GitLab repo. The pipeline rebuilds the Docker image, pushes to ECR, and redeploys the Fargate service. No need to touch the infra stack.
- Infrastructure changes — push to the infra repo. Only re-run when you need to modify VPC, ALB, or ECS cluster settings.
- Adding a new service — add an ECR repo to the infra stack, create a new service stack and pipeline following the same pattern, redeploy infra first, then push the new service.
Clean up
Section titled “Clean up”“Tear down everything.”
The agent handles the ordering:
# Delete service stacks firstaws cloudformation delete-stack --stack-name shared-alb-apiaws cloudformation delete-stack --stack-name shared-alb-uiaws cloudformation wait stack-delete-complete --stack-name shared-alb-apiaws cloudformation wait stack-delete-complete --stack-name shared-alb-ui
# Then delete infraaws cloudformation delete-stack --stack-name shared-albaws cloudformation wait stack-delete-complete --stack-name shared-alb
# Optionally delete GitLab projectsglab project delete user/gitlab-aws-alb-infraglab project delete user/gitlab-aws-alb-apiglab project delete user/gitlab-aws-alb-uiFurther reading
Section titled “Further reading”- AWS CloudFormation lexicon — resource reference, composites, examples
- GitLab CI/CD lexicon — job types, composites, pipeline examples
- Composite Resources guide — how
FargateServiceandAlbSharedwork under the hood - Multi-Stack Projects guide — patterns for splitting infrastructure across stacks