AWS ECS Fargate Provider
Deploy preview environments to AWS ECS Fargate. Each preview gets a dedicated Application Load Balancer — no shared ALB, no custom domain, no DNS changes required.
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 a per-service ALB (HTTP:80) with a target group, then creates the ECS Fargate service.
- Delete: scales the service to zero, deletes it, then deletes the ALB listener, target group, and ALB.
- List: lists ECS services in the cluster tagged with Previewops metadata.
Preview URLs are the ALB's own DNS name: http://prev-{owner}-{repo}-pr{N}.{region}.elb.amazonaws.com
Note: Preview URLs are HTTP (not HTTPS). This is intentional for ephemeral test environments — no TLS certificate or DNS record is needed.
Prerequisites
- An AWS account with a VPC, subnets, and security groups (port 80 open inbound).
- An ECS cluster.
- An ECR repository prefix (created automatically by first deploy).
- An IAM role for ECS task execution.
- IAM credentials for Previewops with the required permissions.
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 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 3 — 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:CreateLoadBalancer", "elasticloadbalancing:DeleteLoadBalancer",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:CreateTargetGroup", "elasticloadbalancing:DeleteTargetGroup",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:CreateListener", "elasticloadbalancing:DeleteListener",
"elasticloadbalancing:DescribeListeners",
"iam:PassRole"
],
"Resource": "*"
}
]
}
Store the credentials:
| Secret | Value |
|---|---|
AWS_ACCESS_KEY_ID |
Access key ID |
AWS_SECRET_ACCESS_KEY |
Secret access key |
Infrastructure credential keys — required unless the same field is set in .previewops.yaml (YAML takes precedence):
| Key | Description |
|---|---|
ECS_CLUSTER |
ECS cluster name (step 1) |
ECS_VPC |
VPC ID |
ECS_SUBNETS |
Comma-separated subnet IDs (e.g. subnet-a,subnet-b) |
ECS_SECURITY_GROUPS |
Comma-separated security group IDs (port 80 must be open inbound) |
ECS_EXECUTION_ROLE_ARN |
ECS task execution role ARN (step 2) |
ECS_ECR_REPO_PREFIX |
ECR repo name prefix (default: previewops) |
AWS_REGION |
AWS region (default: us-east-1) |
If all required infrastructure keys are stored as credentials, you can skip
.previewops.yamlentirely — no file is needed in your repo. Values from.previewops.yamlproviderConfigalways take precedence over credentials when the same key is set in both.
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 4 — Configure the repo
provider: aws-ecs
providerConfig:
cluster: previewops-cluster # required — ECS cluster name (step 1)
vpc: vpc-0abc1234def56789a # required — VPC ID
subnets: # required — at least one subnet ID
- subnet-0abc1234def56789a
- subnet-0abc1234def56789b
securityGroups: # required — at least one security group ID (port 80 open inbound)
- sg-0abc1234def56789a
executionRoleArn: arn:aws:iam::123456789012:role/ecsTaskExecutionRole # required — task execution role ARN (step 2)
ecrRepoPrefix: previewops # optional — prefix for ECR repo names (default: previewops)
region: us-east-1 # optional (default: us-east-1)
Complete example with all options:
# .previewops.yaml
provider: aws-ecs
providerConfig:
cluster: previewops-cluster
vpc: vpc-0abc1234def56789a
subnets:
- subnet-0abc1234def56789a
- subnet-0abc1234def56789b
securityGroups:
- sg-0abc1234def56789a
executionRoleArn: arn:aws:iam::123456789012:role/ecsTaskExecutionRole
ecrRepoPrefix: previewops # optional (default: previewops)
region: us-east-1 # optional (default: us-east-1)
# Common options — all optional
concurrency: 5 # max simultaneous active previews per repo
ttlHours: 48 # auto-delete after N hours (default: 24)
port: 3000 # optional — auto-detected from your Dockerfile EXPOSE; falls back to 8080
memory: 512Mi # Fargate task memory (default: 512Mi)
cpu: 1 # Fargate task vCPU (default: 1)
dockerfile: Dockerfile # Dockerfile path relative to repo root (default: Dockerfile)
buildContext: . # Docker build context (default: .)
env:
NODE_ENV: preview # inject env vars into the preview container
Step 5 — 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 per preview | ~$0.008/hour per ALB + LCU charges (deleted when PR closes) |
| 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 |
Per-service ALBs are deleted on PR close, so you only pay for active previews.
Troubleshooting
| Error | Fix |
|---|---|
aws-ecs providerConfig.cluster is required |
Add cluster to providerConfig |
AccessDeniedException |
IAM policy is missing one of the listed permissions |
[ecs] ALB did not become active |
ALB provisioning timed out — check AWS Console for ALB status |
| Preview unreachable | Ensure security group allows port 80 inbound from 0.0.0.0/0 |