Pull request previews on AWS

Run each pull request's stagelet on its own EC2 instance in your AWS account. See what stagely setup creates, the access key it needs and how ECR sign-in works.

Stagely gives each GitHub pull request a stagelet: a private, running copy of your app. On AWS, each stagelet runs on its own EC2 instance in your account. You run stagely setup aws once. After that, the customer agent launches an instance when a pull request deploys, and terminates it when the stagelet ends.

What setup creates

Setup applies a Terraform module with OpenTofu or Terraform. It creates these resources in the region that you choose:

Resource Details
VPC 10.42.0.0/16, with an internet gateway and one subnet, 10.42.0.0/20. Instances in the subnet get public addresses, so they need no NAT gateway.
Agent host EC2 instance of type t3.small, from the newest Ubuntu 24.04 AMI of Canonical
Gateway host EC2 instance of type t3.micro, from the same AMI, with an Elastic IP
Security groups One for each host, and one for stagelet machines and build workers
EC2 key pair The SSH key that setup uses to install the agent and the gateway
ECR repository creation template Lets build workers create repositories under the prefix in -registry-prefix when they first push. Setup creates no repository and no other registry.

Both hosts require IMDSv2. A later run of setup keeps the hosts when Canonical publishes a newer AMI.

Credentials

Setup needs an access key of an IAM user, in AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. The agent keeps the same key on the agent host, in /etc/stagely/stagely-agent.env, which only root can read.

Use a long-lived access key. Do not use temporary credentials with AWS_SESSION_TOKEN:

  • Only the long-lived key of an IAM user can call STS GetFederationToken. The agent needs this call to sign machines in to ECR, so with temporary credentials the first build fails.
  • Temporary credentials also expire. After that, the agent stops working.

The agent uses these permissions: ec2:DescribeInstances, ec2:RunInstances, ec2:CreateTags, ec2:TerminateInstances, ec2:DescribeKeyPairs, ec2:ImportKeyPair, sts:GetFederationToken and ecr:GetAuthorizationToken. It also needs the ECR push and pull actions and ecr:CreateRepository on the repositories under the prefix.

Stagelet machines and build workers

  • The agent launches one instance for each stagelet. Its type is t3.medium, with a 30 GiB root volume for images, the build cache and stagelet data. Setup has no flag to change this type.
  • The agent keeps two build workers, also of type t3.medium. They stay when stagelets come and go.
  • Each instance starts from the Ubuntu 24.04 AMI, and cloud-init installs Docker at the first boot. With -golden-image, instances start from an AMI that already has Docker. The stagely repository has the Packer template for this AMI.
  • Each instance launches into the environment’s subnet. The agent connects to it on its private address over SSH, and accepts only the host key that it gave the instance.

Registry

Build workers push images to Amazon ECR in your account, under <account>.dkr.ecr.<region>.amazonaws.com/<prefix>. The prefix is the environment’s name, unless you set -registry-prefix.

Before each build and each deploy, the agent gets a fresh ECR token for the machine in two steps:

  1. It calls STS GetFederationToken for temporary credentials. Their policy allows only the repositories under the prefix. A build worker’s credentials can push and pull. A stagelet machine’s credentials can only pull.
  2. It calls ECR GetAuthorizationToken with these temporary credentials. The token works only with the registry, and it expires after 12 hours.

Neither the agent’s access key nor the temporary credentials go to a machine.

Isolation

  • The security group of stagelet machines and build workers accepts connections only from the agent’s and the gateway’s security groups. It has no rule for its own members, so a stagelet does not accept connections from another stagelet.
  • The agent host accepts only SSH, and only from your -operator-cidr ranges.
  • The gateway host accepts ports 80 and 443 from anywhere, and SSH from your ranges.
  • On each instance, containers cannot open new connections to the instance metadata service at 169.254.169.254. Each instance also requires IMDSv2 with a hop limit of 1, so a container gets no metadata token.

Run setup

export AWS_ACCESS_KEY_ID=<access key id>
export AWS_SECRET_ACCESS_KEY=<secret access key>
stagely login
stagely setup aws \
  -environment production \
  -region eu-central-1 \
  -stagelet-domain stagelets.example.com \
  -operator-cidr 203.0.113.7/32 \
  -binaries bin/linux-amd64 \
  -acme-email ops@example.com
Flag Meaning
-region AWS region, required
-availability-zone Availability zone of the environment’s subnet. By default, AWS chooses.
-registry-prefix ECR repository name prefix for built images. The default is the environment’s name.
-golden-image AMI id of a golden image for stagelet machines and build workers

Setup also takes the flags that every cloud takes. At the end, it prints the gateway’s Elastic IP. Point DNS at the gateway with an A record.

After setup

  1. Add .stagely.yml to your repository.
  2. Add config values and secrets.
  3. Read the Security model before you add real secrets.