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

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

  1. An AWS account with a VPC, subnets, and security groups (port 80 open inbound).
  2. An ECS cluster.
  3. An ECR repository prefix (created automatically by first deploy).
  4. An IAM role for ECS task execution.
  5. 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.yaml entirely — no file is needed in your repo. Values from .previewops.yaml providerConfig always 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