FLY.IO SECURITY CHECKLIST

Fly.io gives you machines, private networks (6PN), and a CLI that makes it easy to do the right thing — and just as easy to do the wrong thing fast. Two patterns produce most Fly security failures: secrets baked into Docker images instead of using fly secrets, and deploy tokens scoped at the org level so a leak gives an attacker every app the org runs. The checklist below is what we look for first when we audit a Fly-deployed app.

Treat Critical as launch-blocking. High is week-one. Medium is the cleanup once you have users.

How to use this checklist

Walk it once via flyctl against your production org, ticking items as you go. Audit each app separately. After the whole list passes, run a black-box scan against the production domain.

Critical (fix before launch)

1. Use fly secrets, never bake secrets into Dockerfiles

Why it matters. ENV API_KEY=sk-... in a Dockerfile bakes the key into the image. The image ends up in your Fly registry and possibly in any other registry you mirror to. Anyone with image-pull access reads every secret.

How to check. Read every Dockerfile and docker-compose.yaml in the repo. Search for ENV lines containing secret patterns. Pull the deployed image with flyctl image show and inspect.

How to fix. Move secrets to fly secrets set KEY=value. Reference at runtime via process.env.KEY. Remove the ENV lines and rebuild.

2. Restrict deploy tokens by org scope

Why it matters. Default flyctl auth produces an org-level token that can deploy to every app, change every secret, and create new apps in every org you belong to. CI tokens often inherit this scope.

How to check. flyctl tokens list. Audit each token’s scope and where it’s used.

How to fix. Issue per-app deploy tokens (flyctl tokens create deploy --app <name>). Use those in CI; rotate the org-level token; restrict who has it.

3. Use 6PN private networking for inter-app traffic

Why it matters. Fly’s 6PN gives every app a private IPv6 address reachable only by other apps in the same org. Apps that talk to each other over public IPv4 expose internal API surface to the internet.

How to check. Audit how each app references other apps. Internal calls should use <app-name>.internal or <app-name>.flycast, not the public hostname.

How to fix. Switch inter-app references to .internal / .flycast. Block public access on internal APIs entirely if they don’t need it.

4. Audit allow_concurrent_tunnels and SSH access

Why it matters. flyctl ssh console gives interactive shell access to running machines. If too many people in the org can SSH, every machine is a foothold for the next compromised laptop.

How to check. Audit org membership. Anyone with org access can SSH to any machine.

How to fix. Restrict org membership; for production-only access, use a separate org or restrict via deploy-token-only access.

5. Enable IP allowlists on databases

Why it matters. Fly Postgres on the public 5432 port is reachable from anywhere with the connection string. Even with strong passwords, that’s a credential bearer-token model with no second factor.

How to check. For Fly Postgres clusters, audit which networks can connect. Confirm 6PN is preferred over public connections.

How to fix. Restrict connections to 6PN-only where possible. For external clients, use IP allowlists or stable WireGuard endpoints.

6. Verify Dockerfiles don’t leak build secrets via cache

Why it matters. Build secrets passed via --build-arg end up in the image’s layer history, even if not in the final filesystem. Anyone who pulls the image can read them.

How to check. Pull the image and run docker history --no-trunc <image>. Search for secret patterns in the output.

How to fix. Use BuildKit’s --secret mount instead of --build-arg for build-time secrets. Rotate any value found.

High (fix in the first week)

7. Pin runtime/Docker base image versions

In your Dockerfile, pin base images to specific digests, not :latest. Auto-upgrades are how you ship vulnerable base images.

8. Configure HTTP/HTTPS redirect and HSTS

In fly.toml, configure HTTP → HTTPS redirect (force_https = true). Set HSTS headers in your app to prevent downgrade attacks.

9. Set health checks that don’t expose internal data

Health-check endpoints are unauthenticated. Return only “alive” — not version strings, not feature flags, not config snapshots.

10. Use Machines lifecycle hooks for graceful shutdown

fly_process_kill_signal and [restart] config affect how machines shut down. Misconfigured shutdown can leave secrets in temp files or in-memory state on disk.

11. Restrict who can rotate secrets

fly secrets set/unset requires deploy-level access. Audit who has it; restrict.

12. Set up build-time secret scanning

Add gitleaks to your build command. Fail builds when secrets are detected.

Medium (fix when you can)

13. Ship logs to a SIEM

flyctl logs is interactive; for ongoing analysis, ship to your log aggregator. You want a record of failed-auth attempts, unusual outbound traffic, and machine restarts.

14. Set memory and CPU limits to prevent abuse

Fly Machines run with the resources you allocate. A noisy neighbor or attack can cause OOM kills if limits are wrong.

15. Document the deploy approval workflow

Who can flyctl deploy to production? Document the policy; enforce via token scope.

16. Audit installed Fly extensions

Each extension (Postgres, Redis, Sentry, Tigris) is attack surface. Audit; remove unused.

17. Restrict who can create new apps in the org

App creation is cheap and quiet. A compromised developer account can create apps that mine crypto on your billing.

18. Set up alerting on machine count anomalies

A spike in machines (especially from a deploy you didn’t expect) is a sign of attack or misconfiguration.

After every deploy

  • Re-confirm secrets aren’t in the new image.
  • Re-test inter-app traffic uses 6PN.
  • Confirm HSTS and HTTPS redirect are still set.
  • Verify deploy token scope.

Common attack patterns we see in Fly.io apps

The Dockerfile-baked secret. Developer added ENV STRIPE_KEY=sk_live_... to test in a side branch. Branch got merged and the image shipped. Image is in the registry; anyone with pull access reads it.

The org-level deploy token. CI uses an org-level deploy token. Token leaks via a public CI log. Attacker uses it to deploy crypto-miners to every app in the org.

The public internal API. Worker app calls API app via the public hostname instead of .internal. Internal API has no auth because “it’s internal”. Public hostname is discoverable; full API exploitable.

The over-permissive SSH. Five contractors have org access. One contractor’s laptop is compromised. Attacker SSHes to production machines and reads memory.

How to Secure Fly.io

Step-by-step guide for hardening a Fly app — secrets management, 6PN setup, Dockerfile hygiene, and the deploy-token strategy above in long form.

Is Fly.io Safe?

In-depth analysis of Fly’s defaults — what’s locked down, what isn’t, and what we find when we audit a typical Fly-deployed app.

Automate Your Checklist

A checklist tells you what to look for. A scanner tells you what’s actually broken in the deployed app right now. VibeEval drives a real browser through your Fly deployment, attempts the missing-auth and image-leak attacks above, and reports what got through.

SCAN YOUR APP

14-day trial. No card. Results in under 60 seconds.

START FREE SCAN