Answers you can trust, from Codeables
Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.
Explore Codeablesplatform for preview apps per PR that can automatically sleep to save money
Most teams want per-PR preview apps, but not the surprise bill that comes from leaving every branch running 24/7. The good news: you can get ephemeral preview environments that feel like “real prod,” then automatically scale them to zero (or tear them down) when they’re idle so you only pay for real usage.
Quick Answer: Fly.io gives you per‑PR preview apps that run on Fly Machines—hardware‑virtualized containers that launch in under a second—so you can spin environments up on every pull request, let them sit idle at near‑zero cost, and bring them back on demand without bolting on Kubernetes or custom sleepers.
The Quick Overview
- What It Is: A way to run full preview environments per PR on Fly.io using Machines and GitHub Actions, with automatic scale‑to‑zero and cheap restarts so you’re not paying for idle branches.
- Who It Is For: Product teams, platform engineers, and solo devs who want “real” app previews for every pull request, without owning a cluster or over‑provisioning resources.
- Core Problem Solved: You can give reviewers a live URL for every branch and still keep your cloud bill sane by automatically pausing or destroying unused preview apps.
How It Works
You wire your repo to Fly.io via GitHub Actions (or your CI of choice). On each pull request, CI builds an image, creates a Fly app named after the PR (for example, myapp-pr-123), deploys it, and posts the URL back to the PR. When the PR closes or goes stale, CI tears the app down or scales its Machines to zero. Because Machines start quickly and bill by the second, “sleeping” apps cost basically nothing until someone hits them again.
Here’s the flow at a high level:
-
PR opened → preview app created:
A GitHub Actions workflow triggers onpull_request. It runsflyctl deploywith a PR‑specific app name and environment variables. Fly Proxy routes a unique URL to that app. -
Preview runs close to users, then idles:
Machines for that preview app run in your chosen region(s) (e.g.,iad,lhr). You can pin them to cheap shared CPUs and minimal RAM. When idle, you can either scale to zero or let them autoscale down to a single tiny Machine. -
PR merged/closed → preview app sleeps or dies:
Another workflow reacts topull_request.closed. It either callsfly machines stoporfly apps destroy. You can enforce retention rules—e.g., kill apps after 7 days of no deploys—to keep costs predictable.
A concrete workflow shape
A typical GitHub Actions file looks like this (trimmed to essentials):
name: Review App
on:
pull_request:
types: [opened, synchronize, reopened]
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_REGION: iad
FLY_ORG: personal
jobs:
review_app:
runs-on: ubuntu-latest
concurrency:
group: pr-${{ github.event.number }}
steps:
- uses: actions/checkout@v4
- name: Install flyctl
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Build & deploy PR app
run: |
APP_NAME="myapp-pr-${{ github.event.number }}"
flyctl deploy \
--app "$APP_NAME" \
--remote-only \
--build-arg GIT_SHA=${{ github.sha }} \
--env GIT_SHA=${{ github.sha }} \
--auto-confirm
- name: Output URL
id: deploy
run: |
APP_NAME="myapp-pr-${{ github.event.number }}"
echo "url=https://$APP_NAME.fly.dev" >> "$GITHUB_OUTPUT"
You then add a second workflow on pull_request.closed that either calls fly apps destroy "$APP_NAME" or scales Machines down, depending on how aggressive you want to be about cost.
Features & Benefits Breakdown
| Core Feature | What It Does | Primary Benefit |
|---|---|---|
| Per‑PR Fly apps via CI | Creates a unique Fly app per pull request (myapp-pr-123) with its own Machines, config, and URL. | Every branch gets a real, isolated environment that mirrors production—no shared “staging soup.” |
| Scale‑to‑zero with fast start | Uses Fly Machines that can be stopped when idle and restarted on demand in under a second. | You pay only for CPU/RAM while requests are actually served, not while a branch quietly ages. |
| Automatic cleanup via GitHub Actions | Ties preview lifecycle to PR events (open, sync, close) and optional retention cron jobs. | Old preview apps disappear automatically, keeping your bill and your app list tidy. |
Feature details
Per‑PR Fly apps via CI
- Each PR gets:
- Its own app name, e.g.,
myservice-pr-42. - Its own
fly.tomloverrides (env vars, secret overrides). - Clean logs and metrics, separated from main prod.
- Its own app name, e.g.,
- Tie the app to the PR number so cleanup is deterministic:
APP_NAME="myservice-pr-${GITHUB_PR_NUMBER}" flyctl apps create "$APP_NAME" --org "$FLY_ORG"
Scale‑to‑zero with fast start
There are two main “sleep” patterns:
-
Hard sleep (stop Machines):
Usefly machines stopwhen a PR closes or after N hours of inactivity. This stops billing for CPU and RAM; you still pay for any attached storage.flyctl machines list --app "$APP_NAME" -j | jq -r '.[].id' | \ xargs -n1 flyctl machines stop --app "$APP_NAME" -
Soft sleep (minimal footprint):
Run a single tiny Machine (e.g.,shared-cpu-1xwith 256–512MB RAM) and let everything else scale down. This is “it works, okay?” mode if you don’t want cold starts at all.
Because Machines start quickly, a stopped preview app can wake up to serve HTTP without anyone noticing much, especially for internal reviewers.
Automatic cleanup via GitHub Actions
Hook into pull_request.closed:
name: Cleanup Review App
on:
pull_request:
types: [closed]
env:
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
FLY_ORG: personal
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Install flyctl
uses: superfly/flyctl-actions/setup-flyctl@master
- name: Destroy PR app
run: |
APP_NAME="myapp-pr-${{ github.event.number }}"
flyctl apps destroy "$APP_NAME" --yes || echo "App already gone"
If you want “sleep but keep around,” swap apps destroy for machines stop as above.
Ideal Use Cases
-
Best for trunk‑based teams with lots of PRs:
Because each branch gets a full, isolated environment, you can preview complicated changes (migrations, feature flags, risky config) without trashing a shared staging cluster. -
Best for cost‑sensitive startups running global apps:
Because Fly Machines bill by the second and can be scaled to zero, you can afford dozens of preview environments without building a homegrown quota system or Kubernetes rental marketplace.
Limitations & Considerations
-
Cold starts exist when you fully sleep Machines:
If you stop all Machines for a preview app, the first hit after sleep incurs a startup delay (typically under a second, but it’s not literally zero). If that’s unacceptable, keep one tiny Machine running per app and accept a small baseline cost. -
Stateful dependencies need planning:
Preview apps that depend on Postgres, Redis, or object storage need isolated state. You can:- Use a shared Postgres with per‑PR schemas (most robust).
- Spin up per‑PR databases or Tigris buckets (more isolation, more cost).
- Reuse a shared dev DB (the “it works, okay?” tier, with some data bleed risk).
Pricing & Plans
Fly.io bills by the second for the CPU/RAM your Machines use, plus any storage (NVMe, Postgres, Tigris) you attach. There’s no separate line‑item for “preview apps”—they’re just more Fly apps running on the same primitives.
In practice:
- Tiny shared‑CPU Machines for preview apps are cheap enough that you can run many concurrently and still spend less than you would on an always‑on staging cluster.
- Stopped Machines cost nothing in compute; you only pay for any persistent disks or Postgres volume you keep around.
How you might think about “plans” for preview apps:
-
Aggressive Sleep Mode: Best for teams with many PRs who don’t mind a small cold start.
- Stop all Machines when PRs close or after N idle hours.
- Use per‑PR apps and a shared Postgres with per‑schema separation.
- Net effect: you pay pennies for the occasional wake‑up, not for idle branches.
-
Always‑Warm Reviewer Mode: Best for teams doing lots of live demos or frequent re‑reviews.
- Keep one small Machine running for the most active PRs; sleep or destroy the rest.
- Optional: use per‑PR databases for stricter isolation.
- Net effect: prompt responses for “click‑through” reviews, with some constant baseline cost.
Frequently Asked Questions
Can Fly.io preview apps really scale to zero and wake back up?
Short Answer: Yes. You can stop Machines for a preview app and restart them when needed; they bill only while running.
Details:
Preview apps on Fly.io are just Fly apps running on Machines. Machines have lifecycle commands:
flyctl machines stop <id> # stops and stops billing for compute
flyctl machines start <id> # restarts a stopped Machine
You can script that from CI or a small scheduler:
- On PR closure: stop or destroy all Machines for
myapp-pr-123. - On demand (e.g., a “wake up preview” job or webhook): start them again.
- Billing is per‑second for CPU/RAM while running, plus storage. That’s why sleeping idle PRs works well.
If you want “automatic” sleep based on inactivity, you can build a simple job that:
- Lists preview apps (
flyctl apps listwith a naming convention like*-pr-*). - Checks last activity via logs or your own telemetry.
- Stops Machines for apps past your idle threshold.
What’s the simplest way to get started with per‑PR preview apps on Fly.io?
Short Answer: Use GitHub Actions, name apps by PR number, and add one deploy workflow plus one cleanup workflow.
Details:
You don’t need Helm, Operators, or Terraform to get going:
- Install
flyctllocally. - Run
flyctl launchto scaffoldfly.tomlfor your main app. - Add
FLY_API_TOKENas a GitHub secret. - Add a
review_app.ymlworkflow usingsuperfly/flyctl-actions/setup-flyctl. - Use
${{ github.event.number }}to name the app per PR. - Add a matching
cleanup_review_app.ymlworkflow that destroys or sleeps the app when the PR closes.
From there, you can layer in niceties: PR URLs in GitHub’s “Environments” panel, per‑PR configs, database schemas, region selection (e.g., keep previews close to your reviewers).
Summary
If you’re looking for a platform for preview apps per PR that can automatically sleep to save money, Fly.io gives you the sharp‑tool version:
- Real, isolated Fly apps per PR with prod‑like behavior.
- Fast‑starting Machines that you can scale to zero and restart on demand, paying only for actual CPU and memory usage.
- Lifecycle wired to your Git host so preview environments appear when PRs open and vanish (or sleep) when they’re done.
You get the benefits of “serverless‑like” elasticity and global routing without inheriting Kubernetes, and you keep review‑time infrastructure costs tightly coupled to actual usage instead of idle branches.