Fly.io Apps skill for Claude Code

Machines, volumes and regions configured on purpose. Claude writes fly.toml with real health checks, request concurrency limits and deliberate auto stop settings, runs at least two Machines for anything that must stay up, and explains that each volume lives on one host and needs its own backups. Migrations run as release commands that are safe during rolling deploys, and rollback is a redeploy of a recorded image.

  • namefly-io
  • size193 lines · ≈2,000 tokens
  • files1
  • licenseMIT
  • updated

What Claude sees before loading it

This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.

description: Deploys and operates apps on Fly.io - fly.toml configuration, http_service and concurrency limits, health checks, auto stop and start of Machines, min machines, VM sizing, multi-region placement, volumes and their single-host limits, secrets, release commands for migrations, deploy strategies, private networking, Postgres options, logs, SSH and rollback. Use when launching an app on Fly, editing fly.toml, adding volumes or regions, fixing failed deploys, health checks or machines that do not wake, or planning backups and scaling on Fly.io.

Try asking

  • Deploy this Rails app to Fly with Postgres and a release migration
  • Our Fly app loses its SQLite data sometimes. What is going on?
  • Make this Fly app scale to zero but wake fast for the first request

Use it when

  • Launching or reconfiguring an app on Fly.io
  • Adding volumes, regions or more Machines
  • Deploys fail health checks or Machines do not wake up
  • Planning backups, secrets and rollback on Fly

Skip it when

How to check the result

fly config validate passes, fly status shows healthy Machines on the new version in each region, health URLs return 200, and the previous image reference is recorded for rollback.

The common mistake it prevents

Treating a Fly volume like replicated cloud storage, when it is one disk on one host that needs snapshots and app-level replication.

~/.claude/skills/fly-io/SKILL.md

--- name: fly-io description: Deploys and operates apps on Fly.io - fly.toml configuration, http_service and concurrency limits, health checks, auto stop and start of… license: MIT ---

A solid Fly deployment has a fly.toml that declares the service, health checks and concurrency limits; runs at least two Machines for anything that must stay up; treats each volume as a single disk on a single host with its own backups; keeps secrets in fly secrets; and runs migrations in a release command that blocks bad deploys.

Workflow

- [ ] 1. fly status / fly config show: read current app, regions, machines, volumes
- [ ] 2. Review fly.toml: build, env, http_service, checks, vm, mounts, deploy
- [ ] 3. Decide machine count, regions, auto stop/start, sizing
- [ ] 4. Volumes only for state that cannot live in a managed database or object storage
- [ ] 5. Secrets; release_command for migrations
- [ ] 6. fly deploy; watch health checks; know the rollback
- [ ] 7. Logs, metrics, backups

fly.toml baseline

app = "orders-api"
primary_region = "fra"

[build]
  # Dockerfile in repo root is used by default; or: image = "registry/app:tag"

[env]
  PORT = "8080"
  LOG_LEVEL = "info"

[deploy]
  release_command = "npm run migrate"     # runs once in a temporary Machine; failure aborts deploy
  strategy = "rolling"                    # rolling | immediate | canary | bluegreen

[http_service]
  internal_port = 8080
  force_https = true
  auto_stop_machines = "stop"             # "off" | "stop" | "suspend"
  auto_start_machines = true
  min_machines_running = 1                # in primary region
  [http_service.concurrency]
    type = "requests"                     # "connections" for raw TCP/websockets
    soft_limit = 200                      # proxy prefers other machines above this
    hard_limit = 250                      # proxy stops sending new requests above this

[[http_service.checks]]
  grace_period = "10s"
  interval = "15s"
  timeout = "2s"
  method = "GET"
  path = "/healthz"

[[vm]]
  size = "shared-cpu-1x"
  memory = "512mb"

Validate before deploying: fly config validate. Check the effective config of a running app: fly config show.

Machines, scaling, regions

NeedSetting
Survive a host failureAt least 2 Machines (fly scale count 2); fly deploy creates 2 by default for new apps with services
Scale to zero for low-traffic appsauto_stop_machines = "stop" (or "suspend" for faster wake), min_machines_running = 0; first request pays the start latency
Always warmmin_machines_running = 1 or more
Predictable CPUperformance-* sizes instead of shared-cpu-* (shared CPU has burst quotas)
Users on several continentsAdd regions: fly scale count 3 --region fra,iad,syd; keep the database close to writers
fly scale show
fly scale vm shared-cpu-2x --memory 1024
fly scale count 2 --region fra
fly machine list
fly regions list

The concurrency soft_limit/hard_limit drive both load balancing and autostart: machines start when existing ones exceed the soft limit. Set limits from load tests; defaults are rarely right for your app.

Volumes

  • A volume is a slice of NVMe on one physical host, attached to one Machine, in one region. It is not replicated. If the host fails, that volume’s data is unavailable or lost until restored from a snapshot.
  • One volume per Machine; to run two Machines with volumes, create two volumes and replicate data at the application layer (or use LiteFS/Litestream for SQLite).
  • Daily snapshots are taken automatically with limited retention; configure retention and also back up to object storage for anything important.
fly volumes create data --region fra --size 10            # GB
fly volumes list
fly volumes extend <volume-id> --size 20                  # grow only, never shrink
fly volumes snapshots list <volume-id>
[mounts]
  source = "data"
  destination = "/data"

Prefer a managed database (Fly Managed Postgres, or an external provider) and Tigris or another object store for files, over self-run databases on volumes. Legacy unmanaged fly postgres clusters are apps you operate yourself, including upgrades and failover. Destroying volumes or apps requires explicit user confirmation.

Secrets and config

fly secrets set DATABASE_URL="postgres://..." STRIPE_KEY=sk_live_...   # triggers a rolling restart
fly secrets set --stage FEATURE_X_KEY=...                                # apply on next deploy
fly secrets list                                                         # names and digests only
fly secrets unset OLD_KEY

Non-secret config belongs in [env] in fly.toml so it is versioned with the code.

Deploys and rollback

fly deploy                                     # build remotely, release_command, rolling update
fly deploy --strategy bluegreen                # needs health checks; full parallel set then switch
fly deploy --image registry.fly.io/orders-api:deployment-01J...   # redeploy a previous image
fly releases --image                           # history with image references
fly status                                     # machine states and versions

Rollback means redeploying a previous image reference from fly releases --image. Migrations in release_command must be backward compatible with the running version (expand/contract), because old Machines keep serving during a rolling deploy. Production deploys require user confirmation.

Networking

  • Private network (6PN): apps in the same organization reach each other at <app>.internal (IPv6). Bind internal services to [::] or fly-local-6pn.
  • Flycast (fly ips allocate-v6 --private) gives a private address that goes through the Fly proxy, so autostart and load balancing work for internal services.
  • fly proxy 5432 -a orders-db tunnels a private port to your machine for admin work.
  • Custom domains: fly certs add api.example.com, then create the DNS records it prints.
  • The fly-replay response header lets a read-replica region forward write requests to the primary region.

Operations

fly logs -a orders-api                          # live logs
fly ssh console -a orders-api                   # shell in a running Machine
fly machine restart <id>
fly checks list

Metrics live in the managed Grafana linked from the app dashboard. Ship logs to a provider with the Fly log shipper app if you need retention or search beyond the live tail.

Diagnosing

SymptomLikely cause
Deploy hangs then fails health checksApp listens on 127.0.0.1 or the wrong port vs internal_port; slow start beyond grace_period
“No machines available” or 502 burstsAll machines at hard_limit, or stopped with auto_start_machines = false
Data missing after deployWriting outside the mounted volume path; or new Machine got a new empty volume
Machine will not start in regionVolume pinned to another region or host capacity; create volume in target region
OOM restarts (exit code 137, “out of memory” in logs)Increase memory or fix leak; add swap via swap_size_mb only as a buffer

Gotchas

  • fly launch writes a fly.toml with defaults (auto stop on, min 0); review before production.
  • Scaling with volumes requires a volume per new Machine in that region; fly scale count can create them, but data does not copy.
  • Release commands do not have access to volumes.
  • Shared CPU Machines get throttled under sustained load; check CPU quota metrics before blaming code.

Verification

  1. fly config validate passes; fly status shows the expected Machine count in each region, all on the new version, checks passing.
  2. curl -i https://<app>.fly.dev/healthz returns 200; with scale-to-zero, the first request after idle still succeeds within the acceptable start time.
  3. For volumes: snapshots listed, and a restore has been tested to a scratch volume.
  4. Previous image reference recorded so rollback is a single fly deploy --image ....

About this skill

Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.

esc