AWS ECS Fargate Provider
Deploy preview environments to AWS ECS Fargate with an Application Load Balancer (ALB).
How it works
- Build: uses AWS CodeBuild to build the Docker image and push it to Amazon ECR. CodeBuild clones the repo directly — no local Docker required.
- Deploy: registers an ECS task definition, creates or updates an ECS service, and configures an ALB listener rule to route
pr-{N}.{baseDomain}traffic to the service. - Delete: scales the service to zero, deletes it, and removes the ALB target group and listener rule.
- List: lists ECS services in the cluster tagged with Previewops metadata.
Preview URLs follow the pattern: https://pr-{prNumber}.{baseDomain}
Prerequisites
- An AWS account with a VPC, subnets, and security groups.
- An Application Load Balancer (ALB) with an HTTPS listener (port 443).
- An ECS cluster.
- An ECR repository prefix (created automatically by first deploy).
- An IAM role for ECS task execution.
- A wildcard DNS record pointing
*.{baseDomain}at the ALB.
Step 1 — Create an ECS cluster
aws ecs create-cluster --cluster-name previewops-cluster
Or use an existing cluster — just note its name.
Step 2 — Create or reuse an ALB with HTTPS listener
You likely already have an ALB for your staging/production environment. To add Previewops routing to an existing ALB:
- Ensure the ALB has an HTTPS listener on port 443 with a TLS certificate covering
*.{baseDomain}. - Note the ARN of the HTTPS listener:
aws elbv2 describe-listeners \ --load-balancer-arn <your-alb-arn> \ --query 'Listeners[?Port==`443`].ListenerArn' \ --output text
If creating a new ALB, point a new *.previews.example.com wildcard cert at it.
Step 3 — Create the ECS task execution role
Previewops uses this role for both ECS task execution and as the CodeBuild service role (to build and push Docker images). It therefore needs trust relationships for both services, plus ECR push and CloudWatch Logs permissions.
If you don't already have one:
aws iam create-role \
--role-name ecsTaskExecutionRole \
--assume-role-policy-document '{
"Version":"2012-10-17",
"Statement":[
{"Effect":"Allow","Principal":{"Service":"ecs-tasks.amazonaws.com"},"Action":"sts:AssumeRole"},
{"Effect":"Allow","Principal":{"Service":"codebuild.amazonaws.com"},"Action":"sts:AssumeRole"}
]
}'
aws iam attach-role-policy \
--role-name ecsTaskExecutionRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
Then add an inline policy for ECR image push and CloudWatch Logs (used by CodeBuild and ECS task logging):
aws iam put-role-policy \
--role-name ecsTaskExecutionRole \
--policy-name previewops-ecr-push-and-logs \
--policy-document '{
"Version":"2012-10-17",
"Statement":[{
"Effect":"Allow",
"Action":[
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource":"*"
}]
}'
Note the role ARN:
aws iam get-role --role-name ecsTaskExecutionRole --query 'Role.Arn' --output text
Step 4 — Create IAM credentials for Previewops
Create an IAM user or role with the following permissions (save as an inline policy):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"codebuild:CreateProject", "codebuild:UpdateProject",
"codebuild:StartBuild", "codebuild:BatchGetBuilds",
"ecr:CreateRepository", "ecr:DescribeRepositories",
"ecs:RegisterTaskDefinition", "ecs:CreateService", "ecs:UpdateService",
"ecs:DeleteService", "ecs:DescribeServices", "ecs:ListServices",
"elasticloadbalancing:CreateTargetGroup", "elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTags",
"elasticloadbalancing:CreateRule", "elasticloadbalancing:DeleteRule",
"elasticloadbalancing:DescribeRules",
"iam:PassRole"
],
"Resource": "*"
}
]
}
Store the credentials:
| Secret | Value |
|---|---|
AWS_ACCESS_KEY_ID |
Access key ID |
AWS_SECRET_ACCESS_KEY |
Secret access key |
Step 5 — Point DNS at the ALB
Create a wildcard DNS record:
*.previews.example.com CNAME my-alb-1234567890.us-east-1.elb.amazonaws.com
Use Route 53, Cloudflare, or whichever DNS provider you use.
Step 6 — Configure the repo
provider: aws-ecs
providerConfig:
cluster: previewops-cluster # required
vpc: vpc-0abc1234def56789a # required
subnets: # required — at least one
- subnet-0abc1234def56789a
- subnet-0abc1234def56789b
securityGroups: # required — at least one
- sg-0abc1234def56789a
albListenerArn: arn:aws:elasticloadbalancing:us-east-1:123456789012:listener/app/my-alb/xxx/yyy # required
executionRoleArn: arn:aws:iam::123456789012:role/ecsTaskExecutionRole # required
baseDomain: previews.example.com # required
ecrRepoPrefix: previewops # optional (default: previewops)
region: us-east-1 # optional (default: us-east-1)
Step 7 — Verify
Comment /validate-previewops on any open PR. The bot calls ListServices against the specified ECS cluster and reports whether credentials and cluster access are valid.
Cost notes
| Resource | Cost |
|---|---|
| ALB | ~$16/month base (already paid if sharing with prod) |
| Fargate tasks | ~$0.01–$0.05/hour per preview (service runs for the lifetime of the PR; deleted on close) |
| ECR | ~$0.10/GB/month |
| CodeBuild | 100 free build-minutes/month; ~$0.005/min after |
If you already have an ALB, the marginal cost of adding Previewops is near-zero.
Troubleshooting
| Error | Fix |
|---|---|
aws-ecs providerConfig.cluster is required |
Add cluster to providerConfig |
AccessDeniedException |
IAM policy is missing one of the listed permissions |
TargetGroup not found |
Ensure albListenerArn is the listener ARN, not the load balancer ARN |
| DNS not resolving | Check the *.{baseDomain} wildcard record is pointing at the correct ALB |