Fly.io Provider
Deploy preview environments to Fly.io using the Machines REST API.
How it works
- Build: Previewops starts a Docker builder machine inside your own Fly org, clones your repository, runs
docker build, and pushes the image to your Fly registry. The builder machine is removed after each build. - Deploy: creates a Fly app and Fly Machine running your image. The machine's
.fly.devhostname is posted as the preview URL. - Delete: stops and deletes the Fly Machine and app when the PR is closed.
- List: identifies active preview apps in your org by metadata tags written at deploy time.
Everything runs in your own Fly account — Previewops does not use its own infrastructure for builds or storage.
Prerequisites
- A Fly.io account and organisation.
- Your Fly organisation must support privileged machines — required for the Docker builder. Most Fly orgs have this by default. If you see a
permission deniederror on the first build, contact Fly support to enable it.
Step 1 — Create a Fly API token
fly tokens create deploy --name previewops
Copy the token — you will need it in Step 3.
Step 2 — Find your org slug
fly orgs list
The slug is in the Slug column (e.g. personal or my-company).
Step 3 — Store credentials in Previewops
Click Credentials in the left sidebar and add the following keys under the Fly.io provider:
| Key | Required | Value |
|---|---|---|
FLY_API_TOKEN |
✅ | Token from Step 1 |
FLY_ORG_SLUG |
✅ | Org slug from Step 2 (e.g. my-fly-org) |
FLY_REGION |
Fly region code (default: iad). Run fly platform regions to list options. |
Once stored, you can comment /deploy-previewops --provider=fly on any PR — no .previewops.yaml file is required.
Step 4 — Configure the repo (optional)
If you need per-repo overrides, add .previewops.yaml to the repo root:
provider: fly
providerConfig:
orgSlug: my-fly-org # overrides FLY_ORG_SLUG credential
region: iad # overrides FLY_REGION credential
Values in .previewops.yaml always take precedence over the credentials stored in Step 3.
Complete example with all options:
# .previewops.yaml
provider: fly
providerConfig:
orgSlug: my-fly-org # Fly org slug (optional if FLY_ORG_SLUG stored as credential)
region: iad # Fly region (default: iad); run `fly platform regions` to list options
# Common options — all optional
concurrency: 3 # max simultaneous active previews per repo
ttlHours: 24 # auto-delete after N hours (default: 24)
port: 3000 # optional — auto-detected from your Dockerfile EXPOSE; falls back to 8080
memory: 512Mi # container memory — e.g. 256Mi, 512Mi, 1Gi (default: 512Mi)
cpu: 1 # container vCPU (default: 1)
dockerfile: Dockerfile # Dockerfile path relative to repo root (default: Dockerfile)
buildContext: . # Docker build context (default: .)
env:
NODE_ENV: preview # inject env vars into the preview container
Step 5 — Verify
Comment /validate-previewops on any open PR. The bot will confirm your token is valid and your org is reachable.
Build times
| Scenario | Typical duration |
|---|---|
| First build (no cached layers) | 5–15 minutes |
| Subsequent builds (dependencies unchanged) | 2–5 minutes |
The main variable is your Dockerfile — specifically how long docker build takes for your dependencies.
Tip: structure your
Dockerfileso dependency installation (COPY package.json && npm install) comes before copying your source files (COPY . .). Docker caches each layer, so unchanged dependencies won't be reinstalled on subsequent builds.
Build progress updates
Because Fly builds are slower than other providers, Previewops posts intermediate progress updates to the PR comment so you can track where the build is:
| Message | What's happening |
|---|---|
🏗️ Provisioning Fly builder machine… |
Creating the remote Docker builder VM in your Fly org |
⚙️ Builder machine ready — waiting for exec agent… |
Builder VM is up, waiting for the exec agent inside the machine to become ready |
🐳 Transferring build script… |
Exec agent ready, uploading the build script to the builder VM |
🔨 Docker build in progress… (Ns) |
Build is running — elapsed time shown, updated every 15 seconds |
🚀 Image ready — deploying preview machine… |
Build complete, creating your preview machine |
⏳ Machine created — waiting for service to become reachable… |
Machine is booting and starting your app |
Dockerfile requirements
- Your app must listen on the port configured in
.previewops.yaml(default:8080). Fly routes HTTPS traffic to this port automatically. - Your
Dockerfilemust produce a runnable image —CMDorENTRYPOINTmust start the server process. - Standard
docker buildsyntax is fully supported.
Notes
- Each PR gets its own Fly app (
prev-{owner}-{repo}-pr{number}, max 30 characters). - Images are stored in your Fly registry (
registry.fly.io) — no external registry needed. - Preview machines use
auto_start_config— they wake on the first inbound HTTPS request and sleep after 5 minutes of inactivity, keeping costs near zero. - Fly's free allowance covers 3 shared-cpu 256 MB machines and 160 GB outbound transfer/month.
Troubleshooting
| Error | Cause | Fix |
|---|---|---|
FLY_API_TOKEN environment variable is required |
Token not stored | Add FLY_API_TOKEN in Credentials (left sidebar) |
fly orgSlug is required |
Org slug missing | Add FLY_ORG_SLUG credential or orgSlug in .previewops.yaml |
401 Unauthorized from Fly API |
Token expired or invalid | Regenerate: fly tokens create deploy --name previewops |
permission denied on first build |
Privileged machines not enabled for your org | Contact Fly support |
IP allocation failed — no shared_v4 found |
Transient Fly API timing issue | Retry the deploy — this resolves on its own |
Your application crashed immediately after startup |
App process exited at boot | See App crashes on startup |
Build timed out after 3600s |
Build exceeded 1 hour | Check your Dockerfile for unusually slow steps; ensure the base image is accessible |
Preview machine did not reach 'started' state |
Machine start timed out (60s) | Retry; if persistent, check your Fly org's machine quotas |
Build failed with empty package directories |
Build environment compatibility issue | See Build fails with empty package directories |
| Build marked ❌ Failed on first attempt with no clear error | Transient setup failure on first deploy or after extended inactivity | See Build fails on first attempt |
| Deploy timed out with no clear error in the PR comment | App crashed on startup or isn't listening on the expected port | Confirm your app listens on port 8080 (or the port: value in .previewops.yaml) and doesn't exit at boot. Run docker build && docker run -p 8080:8080 locally to reproduce. |
Build fails on first attempt
Occasionally a build will fail on the first /deploy-previewops --provider=fly attempt even when your Dockerfile and credentials are correct. This can happen on initial setup or after extended inactivity.
Simply retry the command. The second attempt should complete successfully.
If the retry also fails, check the build output for a specific error message and consult the table above.
Build fails with empty package directories
If your build fails and your package manager appears to have run but produced no output files, ensure your Dockerfile uses standard copy operations without requiring special filesystem capabilities. Previewops automatically selects the most compatible build mode for Fly — no configuration change is needed on your side.
If the issue persists, try adding a RUN echo "build ok" after your install step to confirm which layer is failing, then share the build output with Previewops support.
App crashes on startup
When the bot posts "Your application crashed immediately after startup", the preview machine started successfully but your app process exited before it could accept connections.
Common causes by exit code:
| Exit code | Likely cause |
|---|---|
1 |
Unhandled exception or startup error — check your application logs |
127 |
Command not found — a dependency or binary is missing from the image |
137 |
Out of memory — increase memory in .previewops.yaml |
? |
Very fast crash before the exit code was recorded |
Debugging steps:
- Confirm your app starts locally with Docker:
docker build -t test . && docker run -e PORT=8080 -p 8080:8080 test - Confirm your app reads
PORTfrom the environment and listens on that port. Previewops setsPORTautomatically. - If exit code 137 (OOM), increase memory in
.previewops.yaml:memory: 1Gi # default is 256Mi - Check your
DockerfileCMD/ENTRYPOINT— ensure it starts the server and does not exit immediately.
Token permissions
The deploy token scope (created via fly tokens create deploy) is sufficient for all Previewops operations. You do not need an admin or org-owner token.
If you rotate your Fly API token, update it in Credentials (left sidebar) and re-run /validate-previewops to confirm the new token works before triggering a deploy.