skills/fly-deploy/SKILL.md
Deploy and manage apps on Fly.io via the flyctl CLI. Use when user wants to deploy, ship, launch, release, or manage a Fly.io app — including `fly deploy`, `fly status`, `fly logs`, `fly secrets`, `fly certs`, scaling, and setting up custom domains for Dockerised frontend/backend apps.
npx skillsauth add RonanCodes/ronan-skills fly-deployInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Ship Docker containers to Fly.io using flyctl. Covers launch, deploy, status, logs, secrets, scaling, and custom domains.
/ro:fly-deploy launch # first-time scaffold for this repo
/ro:fly-deploy deploy # build + push + release
/ro:fly-deploy status # current machines + health
/ro:fly-deploy logs [--tail]
/ro:fly-deploy secrets set KEY=value ...
/ro:fly-deploy certs add api.myapp.com # custom domain (pairs with /ro:cloudflare-dns)
flyctl installed — if missing: brew install flyctl (macOS) or curl -L https://fly.io/install.sh | shflyctl auth whoami. If not authed, use one of:
flyctl auth login (browser, interactive)FLY_ORG_TOKEN in ~/.claude/.env. Generate at https://fly.io/dashboard → your org → Access Tokens → Create Org TokenFLY_API_TOKEN in ~/.claude/.env. Generate at https://fly.io/user/personal_access_tokensDockerfile in the project (or use flyctl launch to generate one)This skill is designed around an organization-scoped token (FLY_ORG_TOKEN) — lower blast radius than a personal access token.
| Capability | Org token | Personal token | |---|---|---| | Deploy, read, write, manage secrets within its own org | ✅ | ✅ | | Access multiple orgs | ❌ (token is pinned to one org) | ✅ | | Create new orgs / manage billing | ❌ | ✅ | | Appropriate for agent automation | ✅ | ⚠️ overly broad |
flyctl natively reads FLY_API_TOKEN only, so the skill shadows it at invocation:
export FLY_API_TOKEN="${FLY_ORG_TOKEN:-${FLY_API_TOKEN}}"
The skill verifies the token before any destructive-ish command by running flyctl auth whoami and reporting token: Organization Token so the user can confirm they're about to act with the scope they expect.
flyctl launch --no-deploy # generates fly.toml, asks region + org
Review the generated fly.toml before deploying:
app — globally unique name (becomes <app>.fly.dev)primary_region — closest to users (e.g. lhr, iad, sjc)[http_service] — internal_port must match the container's listen port[[vm]] — size (default shared-cpu-1x / 256MB — bump for FE builds with SSR)Then: flyctl deploy.
drizzle/ exists)Run this BEFORE flyctl deploy for any app with a drizzle/ directory. Shipping code whose schema prod lacks is a user-facing P1 (nutmeg 2026-06-11); a green local gate cannot catch it because tests run against the local DB.
pnpm drizzle-kit generate — generate any pending migration; commit the file if one appears.DATABASE_URL="$PROD_DATABASE_URL" pnpm drizzle-kit migrate # or the repo's db:migrate script
information_schema.columns / information_schema.tables against $PROD_DATABASE_URL and exit non-zero (abort the deploy) on any missing table or column. Print the drift; never swallow it. A migrate that no-ops still has to prove the schema is present.Full rule and rationale: canon/db-deploy-gate.md.
flyctl deploy --remote-only # build on Fly's builders (no local Docker needed)
Flags to know:
--strategy immediate — replace all machines at once (dev only)--strategy rolling — default, zero downtime--ha=false — single machine only (cheaper for staging)--dockerfile <path> — non-default Dockerfile--build-arg KEY=value — build-time argsAfter a successful deploy, if docs/infrastructure/ is absent (first deploy) or the deploy changed apps/volumes/secrets/machines, run /ro:infra-docs to generate or refresh the living architecture docs (live resource inventory, C4 + sequence diagrams, security model, provisioning runbook). It discovers live Fly state via flyctl. Idempotent, so re-run after notable deploys.
flyctl status -a <app> # machine list, regions, health
flyctl logs -a <app> # stream logs
flyctl releases -a <app> # deploy history
flyctl ssh console -a <app> # shell into a running machine
Secrets are baked into the container env at runtime (restarts machines):
flyctl secrets set DATABASE_URL=postgres://... STRIPE_KEY=sk_... -a <app>
flyctl secrets list -a <app>
flyctl secrets unset OLD_VAR -a <app>
Never paste secrets into fly.toml — use flyctl secrets set.
flyctl certs create api.myapp.com -a <app>
flyctl certs show api.myapp.com -a <app> # shows required DNS + validation status
flyctl ips list -a <app> # A/AAAA targets if you prefer A over CNAME
Then chain with /ro:cloudflare-dns to add the records. Poll certs show until status is Issued (usually 30s–2min).
flyctl scale count 2 -a <app> # horizontal (machines)
flyctl scale vm shared-cpu-2x -a <app> # vertical (CPU/RAM preset)
flyctl scale memory 1024 -a <app> # memory only
shared-cpu-1x machine pricing (approx, Amsterdam tier — other regions have small multipliers):
| Memory | $/month (always on) | $/hour | |--------|---------------------|--------| | 256MB | $2.02 | $0.0028 | | 512MB | $3.32 | $0.0046 | | 1GB | $5.92 | $0.0082 | | 2GB | $11.11 | $0.0154 |
fly volumes extend only goes up. Plan sizes carefully.auto_stop_machines="suspend" + min_machines_running=0) → you only pay compute while serving. A personal tool typically costs $0.30–$1/mo all-in.suspend vs stop (both free of extra cost)| Mode | Cold start | When to use |
|------|-----------|-------------|
| suspend | ~200ms (keeps memory snapshot) | Default for most apps — UX win at zero cost |
| stop | ~1-3s | Long-idle apps or edge cases where snapshots misbehave |
Live pricing: https://fly.io/docs/about/pricing/
-a, or token is pinned to a different org than the app lives in. Run flyctl orgs list to confirm the token's org. Org tokens cannot reach apps in other orgs.FLY_API_TOKEN isn't shadowed from FLY_ORG_TOKEN. Check with echo "${FLY_API_TOKEN:-unset}".flyctl deploy --vm-memory 2048 or upgrade machine sizeinternal_port in fly.toml doesn't match what the container listens onawaiting_configuration — DNS record missing or wrong proxied flag (Fly needs unproxied / grey-cloud on Cloudflare for TLS termination)flyctl apps destroy and flyctl machine destroy are irreversible — always confirm with the user firstflyctl deploy to a prod app: show the user the current release and the new image tag before shippingfly.toml with hardcoded secrets — use [env] for non-secret config only/ro:cloudflare-dns — add the subdomain after certs createtesting
--- name: linear-pipeline description: The Fable orchestrator for a single dispatched Linear ticket. Holds almost no context itself; it receives `--issue <ID> --detached`, decides the stage sequence, and fans out a sub-agent per stage, passing forward only each stage's artifact (never re-derived, never inlined into its own context). Step zero, before any planning or stage routing, is a boundary triage against `canon/security-boundary.md` (#199): a match tags Ronan Connolly and stops the run, no
development
--- name: in-your-face description: Capture a chat-only answer into a durable artifact (markdown + HTML, PDF when cheap) and launch it automatically so the user cannot miss it. Use when user says "in your face", "don't let me lose this", "save that answer", "make that durable", or right after answering a substantive side question (a recipe, comparison, how-to, or generated prompt) that would otherwise die with the context. category: workflow argument-hint: [--no-open] [--vault <short>] [hint of
tools
One-shot headless OpenAI Codex CLI calls for background/admin AI tasks — summaries, classification, extraction, admin glue. The default engine for anything that runs AI constantly in the background (daemon-driven, per-event), because it bills the flat ChatGPT subscription instead of Claude usage or per-token API spend, and it keeps working while Claude is rate-limited. NEVER for coding — coding stays Claude. Use when a skill or daemon needs a cheap always-on AI call, when the user says "use codex", "ask codex", "codex as backup", or when building a background summarizer/classifier into a listener or loop. Reads auth from ~/.codex/auth.json (ChatGPT account, no API key).
research
Turn a warranty rejection, repair quote, or RMA email into a cited decision brief — legal read (NL/EU consumer law), is the part user-serviceable, live part and new-unit prices, repair-vs-DIY-vs-new economics, before-you-send-it checklist, deadlines. Use when the user pastes or screenshots a repair quote, warranty rejection, "not covered" email, onderzoekskosten fee, or asks "should I repair or replace this".