AWS ECS Fargate Provider

Deploy preview environments to AWS ECS Fargate with an Application Load Balancer (ALB).

How it works

Preview URLs follow the pattern: https://pr-{prNumber}.{baseDomain}

Prerequisites

  1. An AWS account with a VPC, subnets, and security groups.
  2. An Application Load Balancer (ALB) with an HTTPS listener (port 443).
  3. An ECS cluster.
  4. An ECR repository prefix (created automatically by first deploy).
  5. An IAM role for ECS task execution.
  6. 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:

  1. Ensure the ALB has an HTTPS listener on port 443 with a TLS certificate covering *.{baseDomain}.
  2. 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