DEV Community

Sohana Akbar
Sohana Akbar

Posted on

Deploying Preview Environments on AWS with PR Labels

Preview environments—temporary, isolated deployments of your code—have become essential for modern development workflows. They let you test changes in a production-like setting before merging, catch issues early, and give stakeholders a tangible way to review work in progress.

One of the most elegant ways to trigger these environments? PR labels. Slap a label on a pull request, and boom—a full environment spins up on AWS. Remove the label, and it tears down. Simple, powerful, and cost-effective. Let me walk you through how to set this up.

Why PR Labels?
Managing preview environments manually is a nightmare. You don't want to:

Spin up environments for every PR ($$$)

Manually track which PRs need environments

Remember to clean up after merge

PR-based labels solve this beautifully. You're in control—only PRs flagged with your chosen label (like preview or deploy) get environments. Remove the label or close the PR? Environment destroyed.

The Architecture at a Glance
There are several solid approaches depending on your stack and complexity needs. Here are the main patterns:

  1. The Simple Path: PullPreview + AWS Lightsail If you want the simplest route and your app runs on Docker Compose, PullPreview is hard to beat . It's a GitHub Action that provisions a cheap AWS Lightsail instance (around $10/month, prorated) whenever you label a PR . The workflow stays entirely within GitHub Actions—your code never touches third-party servers .

Here's a minimal workflow file:

yaml

.github/workflows/pullpreview.yml

name: PullPreview
on:
pull_request:
types: [labeled, unlabeled, synchronize, closed, reopened]
push:
branches:
- main

jobs:
deploy:
if: github.event_name == 'push' || github.event.label.name == 'pullpreview' || contains(github.event.pull_request.labels.*.name, 'pullpreview')
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pullpreview/action@v5
with:
admins: your-github-username
always_on: main # Keeps a staging environment for main branch
compose_files: docker-compose.yml
default_port: 80
env:
AWS_ACCESS_KEY_ID: "${{ secrets.AWS_ACCESS_KEY_ID }}"
AWS_SECRET_ACCESS_KEY: "${{ secrets.AWS_SECRET_ACCESS_KEY }}"
AWS_REGION: "us-east-1"
What makes this great:

No Kubernetes complexity—each preview is a single VM

State persistence across deploys via Docker volumes

SSH access for troubleshooting (GitHub keys auto-installed)

Privacy-first—code never leaves your infrastructure

  1. The CDK Approach: Full Infrastructure as Code If you want total control and your team is comfortable with TypeScript, AWS CDK gives you infrastructure-as-code superpowers . You define your entire preview environment in code, and CDK handles the heavy lifting.

The basic flow:

Label a PR → triggers GitHub Action

Action derives a unique stack name (AwesomeStack-pr-42-feat-new-feature)

CDK deploys a full stack (CloudFront + S3, ECS, or whatever you need)

Deployment URL shows up in GitHub's UI via Deployments API

Remove label or close PR → cdk destroy cleans everything up

Key CDK snippets:

typescript
// lib/awesome-stack.ts - Infrastructure defined as code
const bucket = new s3.Bucket(this, "Bucket", {
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY, // Critical for cleanup!
});

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

// Export the URL for GitHub integration
new cdk.CfnOutput(this, "DeploymentUrl", {
value: "https://" + distribution.distributionDomainName
});
Pro tip: The removalPolicy: cdk.RemovalPolicy.DESTROY is essential—without it, your S3 buckets and databases stick around even after PRs close, costing you money .

  1. The Kubernetes Route: ArgoCD + EKS For teams already running Kubernetes on AWS (EKS), ArgoCD's ApplicationSet with PullRequest generator offers a GitOps-native approach .

The workflow:

Developer creates PR with preview label

GitHub Actions builds Docker image, pushes to ECR

ArgoCD detects the PR via ApplicationSet, provisions resources in a dedicated namespace

Preview environment gets its own Ingress URL

Remove label or close PR → ArgoCD deletes everything

This approach works especially well for microservices with dependencies. One neat trick from production implementations: use a hybrid approach where databases are provisioned per-PR but dependent services are shared from staging, saving costs while keeping data isolated .

Two Big Considerations
Cost Control
Preview environments can get expensive if you're not careful. Here are strategies to keep costs in check:

Spot instances for non-critical workloads (perfect for previews)

Auto-cleanup—remove the preview label if a PR goes stale for a certain period

Lightsail nano instances (512MB RAM) for lighter workloads

Scheduled cleanup jobs to catch dangling resources

Security
IAM policies should be least-privilege—give actions only what they need

CIDR restrictions to limit access to specific IP ranges

Never hardcode credentials—use GitHub Secrets for AWS access keys

The Bottom Line
PR-based preview environments on AWS transform how you ship code. Whether you go with:

PullPreview + Lightsail for dead-simple setup

AWS CDK for full infrastructure-as-code flexibility

ArgoCD + EKS for Kubernetes-native GitOps

…the core principle is the same: label-driven, ephemeral environments that give your team confidence to ship faster.

My recommendation: Start with PullPreview if you're using Docker Compose and want to be live in minutes. Graduate to CDK if you need custom infrastructure. And if you're already deep in Kubernetes, ArgoCD is your friend.

The key is to make preview environments so easy that your team actually uses them. Because when you combine speed with safety, that's when development gets truly enjoyable.

Happy deploying! 🚀

Top comments (0)