DEV Community

Sohana Akbar
Sohana Akbar

Posted on

Deploying Preview Environments on AWS with PR Labels

Preview environments have become an essential part of modern development workflows. They allow teams to review changes in a live environment before merging code, significantly improving collaboration and reducing bugs.

In this article, I'll walk you through setting up preview environments on AWS that are triggered by PR labels. This approach gives you complete control over your infrastructure while keeping costs low and workflows simple.

Why Preview Environments?
Preview environments (also called review apps) are temporary deployments of your application from pull requests. They let teammates, product managers, and QA teams visually review changes before they hit production .

The benefits are clear:

Faster feedback loops – reviewers see exactly what changed

Reduced risk – issues are caught before merging

Better collaboration – non-technical stakeholders can participate in reviews

Confidence in changes – you can run E2E tests against real infrastructure

The Label-Based Approach
Using PR labels to trigger preview deployments is a pattern that's gained significant traction. Here's why it works so well:

Intent-based activation – you choose when to spin up an environment

Automatic cleanup – removing the label or closing the PR tears everything down

Team-friendly – no complex commands required, just add a label

As one popular implementation describes it: "You can manage preview environments by adding or removing the pullpreview label on your Pull Requests" .

Architecture Overview
The architecture depends on your specific needs, but a typical setup includes:

Option 1: Simple VM-based (using PullPreview)

This approach provisions AWS Lightsail instances for each preview environment . Each instance runs Docker Compose to spin up your application and its dependencies. It's simple, cheap, and works with any app that can be containerized.

Option 2: Infrastructure as Code (AWS CDK)

For more complex applications, you can use AWS CDK to define your infrastructure in TypeScript. This gives you more control and enables sophisticated setups with:

CloudFront CDN in front of S3 buckets

RDS databases with proper lifecycle management

VPC configurations and security groups

Option 3: Kubernetes with GitOps

Larger teams might use Kubernetes with ArgoCD's ApplicationSet and PullRequest generator. This approach creates isolated namespaces per PR, with full service mesh capabilities .

Implementation: PullPreview Approach
Let me show you the simplest way to get started, using the PullPreview GitHub Action.

  1. Set Up AWS Credentials First, create an IAM user with Lightsail permissions. AWS doesn't provide a default Lightsail policy, so you'll need to create one :

json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "lightsail:",
"Resource": "
"
}
]
}
Then store the access keys as repository secrets:

AWS_ACCESS_KEY_ID

AWS_SECRET_ACCESS_KEY

AWS_REGION (e.g., us-east-1)

  1. Create the Workflow File Add .github/workflows/pullpreview.yml to your repository:

yaml
name: PullPreview
on:
pull_request:
types: [labeled, unlabeled, synchronize, closed, reopened]

jobs:
deploy:
permissions:
contents: read
deployments: write
pull-requests: write
statuses: write
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pullpreview/action@v5
with:
admins: your-github-username
compose_files: docker-compose.pullpreview.yml
ports: 80,443
default_port: 443
env:
AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
AWS_REGION: "us-east-1"

  1. Create the Docker Compose File Your docker-compose.pullpreview.yml defines what runs in the preview environment:

yaml
services:
proxy:
image: caddy:2
command: "caddy reverse-proxy --from '${PULLPREVIEW_URL}' --to web:4567"
ports:
- "80:80"
- "443:443"

web:
build: .
environment:
DATABASE_URL: "postgres://user:pass@db/postgres"

db:
image: postgres:13
environment:
POSTGRES_PASSWORD: p4ssw0rd

  1. Create the Label In your GitHub repository, create a label called pullpreview. When you add this label to a PR, the preview environment will spin up automatically.

Implementation: CDK Approach
For teams needing more infrastructure control, the AWS CDK approach offers greater flexibility .

Define Your Stack
typescript
// lib/awesome-stack.ts
import * as cloudfront from "@aws-cdk/aws-cloudfront";
import * as s3 from "@aws-cdk/aws-s3";
import * as cdk from "@aws-cdk/core";

export default class AwesomeStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const bucket = new s3.Bucket(this, "Bucket", {
  autoDeleteObjects: true,
  removalPolicy: cdk.RemovalPolicy.DESTROY, // Clean up when PR closes
});

const distribution = new cloudfront.Distribution(this, "Distribution", {
  defaultBehavior: {
    origin: new cloudfrontOrigins.S3Origin(bucket),
    viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
  },
});

// Export the URL for GitHub Deployments API
new cdk.CfnOutput(this, "DeploymentUrl", {
  value: "https://" + distribution.distributionDomainName
});
Enter fullscreen mode Exit fullscreen mode

}
}
Trigger Based on PR Number
Use the PR number and branch name to create unique stack names:

yaml

  • name: set STAGE variable
    run: echo "STAGE=pr-${{ github.event.number }}-${{ env.GITHUB_HEAD_REF_SLUG }}" >> $GITHUB_ENV

  • name: deploy the stack
    run: yarn deploy
    This ensures each PR gets its own isolated infrastructure .

Cleanup: Don't Leave Resources Running
Preview environments should be ephemeral. Here's how to ensure cleanup:

With PullPreview: Removing the pullpreview label or closing the PR automatically destroys the Lightsail instance.

With CDK: Create a cleanup workflow that triggers on PR closure:

yaml
name: "Pull Request clean-up"

on:
pull_request:
types: [unlabeled, closed]

jobs:
clean-up:
if: |
(github.event.action == 'unlabeled' && github.event.label.name == '🚀 deploy') ||
(github.event.action == 'closed' && contains(github.event.pull_request.labels.*.name, '🚀 deploy'))
runs-on: ubuntu-latest
steps:
- name: destroy the stack
run: cdk destroy "AwesomeStack-${{ env.STAGE }}" --force
Cost Considerations
One of the advantages of this approach is cost control:

Lightsail instances start at ~$10/month, prorated to actual usage

CDK resources can be set to auto-delete with RemovalPolicy.DESTROY

Spot instances in Kubernetes can further reduce costs for non-critical workloads

Advanced Features to Consider
Multi-service dependencies: When Service A depends on Service B, developers should be able to point to either staging or another preview environment .

Persistent state: Keep database volumes across deploys so reviewers don't lose data when new commits arrive .

SSH access: Grant trusted team members SSH access to preview instances for troubleshooting .

Conclusion
Preview environments triggered by PR labels provide a powerful way to review changes in a production-like setting. Whether you choose the simplicity of PullPreview with Lightsail, the control of AWS CDK, or the scalability of Kubernetes, the core pattern remains the same: code → label → preview → review → cleanup.

The key benefits are clear:

Low cost – pay only for what you use

Simple activation – just add a label

Automatic cleanup – no manual resource management

Privacy – your code stays in your AWS account

Start small, get the workflow working for a simple app, then expand to your full microservices architecture. Your team's confidence in shipping changes will increase dramatically when everyone can see and test the real thing before it merges.

Top comments (0)