| name | fly-io-deployment-workflow |
| description | Plan, configure, deploy, and diagnose Fly.io deployments for server-side Swift services, including Vapor and Hummingbird apps, Dockerfile handoffs, fly.toml settings, environment variables, secrets, health checks, Postgres attachment, process groups, and deployment validation. |
| license | Apache-2.0 |
| compatibility | Designed for Codex and compatible Agent Skills clients working with Fly.io, flyctl, Dockerfile-based Swift services, Vapor, Hummingbird, and server-side Swift projects on macOS or Linux. |
| metadata | {"owner":"gaelic-ghost","repo":"socket","category":"server-side-swift-fly-io"} |
| allowed-tools | Read Bash(rg:*) Bash(git:*) Bash(swift:*) Bash(docker:*) Bash(fly:*) Bash(flyctl:*) Bash(curl:*) |
Fly.io Deployment Workflow
Purpose
Prepare, deploy, or diagnose a Fly.io deployment for a server-side Swift service without confusing platform configuration with framework routing, Docker image construction, persistence design, or local development workflow.
The practical decision is which Swift executable runs in the Fly Machine, which Dockerfile builds it, which fly.toml settings expose it, how secrets and non-secret environment variables reach the process, which health checks prove readiness, and which command proves the deployed app is serving the expected behavior.
When To Use
- Use this skill when adding or changing
fly.toml, Fly app configuration, Fly secrets, Fly Postgres attachment, Fly process groups, Fly health checks, or deployment validation for a Vapor or Hummingbird service.
- Use this skill when diagnosing
fly launch, fly deploy, remote builder, Machine rollout, health-check, app binding, DATABASE_URL, secret, region, scaling, or deployed HTTP failures.
- Use this skill when deciding whether a server-side Swift service is ready for Fly.io deployment or should first receive Docker, Vapor, Hummingbird, persistence, or observability work.
- Use this skill when preparing handoff guidance for a Dockerfile-based hosted deployment on Fly.io.
- Do not use this skill for ordinary route, middleware, model, migration, request-context, or SwiftPM package changes unless Fly deployment behavior is the reason for the change.
- Do not use this skill for generic Dockerfile design without Fly-specific configuration. Use
docker-workflow for Docker image structure.
- Do not use this skill for Apple Containerization local development. Use
apple-containerization-workflow for Apple's container CLI or Containerization APIs.
Source Check
Use repo-local Swift files, checked-out dependency sources, Dash MCP or Dash HTTP for installed Fly.io or Swift framework docsets, and then official docs when Dash/local coverage is missing or stale. Check one of those source-specific paths before claiming Fly.io or framework deployment behavior:
Use Vapor, Hummingbird, SwiftPM, Docker, Swift Docker image, or persistence documentation when the deployment depends on app commands, package targets, image shape, runtime assets, migrations, database drivers, or framework-specific binding behavior.
Planning Workflow
- Inspect project shape:
Package.swift
- executable target and local
swift run command
- Vapor or Hummingbird app entry point, host binding, port binding, and graceful shutdown behavior
Dockerfile, .dockerignore, Compose files, and existing deployment files
fly.toml, Fly app name, primary region, service or http_service shape, process groups, release command, checks, scaling, and volumes
- environment variable names, secret names, database URL handling, and migration commands
- Identify the deployment job:
- first deployment
- redeploy an existing Fly app
- add database-backed behavior
- split web and worker processes
- fix health checks, port binding, or startup failures
- validate production readiness
- Check official Fly docs before recommending CLI flags, config keys, secrets behavior, or health-check behavior.
- Check framework docs before changing the app's run command, host, port, migration timing, or background-worker command.
- Keep Docker image structure in
docker-workflow unless the Fly change only needs to verify that the existing image exposes and runs the right process.
- Keep app routes and readiness endpoints in
vapor-server-workflow or hummingbird-server-workflow unless they are already present and only need Fly wiring.
- Keep database schema and migration design in
persistence-workflow; this skill owns when Fly should run or require those commands, not what schema they create.
- Validate in the narrowest useful order: local SwiftPM or Docker check, Fly config check through
fly deploy or documented Fly commands, deployed health or HTTP check.
First Launch
For a service that already has a production-ready Dockerfile, prefer Fly's Dockerfile flow:
fly launch --no-deploy
Use --no-deploy when the app needs secrets, database attachment, region choice, process groups, health checks, or port review before the first release.
After reviewing config and secrets:
fly deploy
Use plain fly launch only when immediate deployment is acceptable and the repository's Dockerfile, port, env, and health behavior are already ready.
Do not commit credentials, generated local tokens, or machine-local paths while adding Fly files.
fly.toml Shape
Prefer the fly.toml generated by fly launch as the starting point. Edit it deliberately instead of replacing it wholesale.
Check these fields for Swift services:
app: Fly app name, not a package module name unless they intentionally match
primary_region: chosen operator region for the app
[build]: only when the repository needs a non-default Dockerfile path, build target, or build arguments
[env]: non-secret runtime values only
[deploy]: release commands, rollout strategy, wait timeout, or other deploy behavior only when the app needs them
[processes]: separate web, worker, queue, migration, or job processes only when the service has real process boundaries
[http_service] or [[services]]: the internal port must match the container's listening port
- checks: TCP checks for basic binding, HTTP checks for real readiness, and Machine checks only for deploy-time behavior that cannot be proven by port or HTTP readiness
Do not add a readiness endpoint only to satisfy Fly config if the app does not have a real readiness signal. Hand route design to the Vapor or Hummingbird skill.
Ports And Binding
Fly routes traffic to the app's configured internal service port. The Swift process must listen on the same port inside the Machine.
For Vapor:
- confirm whether the image runs
App serve, App serve --hostname 0.0.0.0 --port <port>, or an equivalent entry point
- make sure Vapor does not bind only to
127.0.0.1 in production
- keep Fluent migration commands separate from the web process unless the repository intentionally uses a release command
For Hummingbird:
- confirm the executable target and any command-line options exposed by the package
- confirm the service binds to a container-reachable host and the same port named in Fly config
- preserve template-generated host, port, and log-level flags unless the user explicitly wants a different configuration model
- if the repository was generated as an
hb Lambda app, stop and choose an appropriate Lambda deployment workflow instead of forcing the Fly.io long-running process model onto it
When diagnosing failures, report the exact Fly internal port, container EXPOSE port when present, framework port, host binding, process command, and health-check path involved.
Secrets And Environment
Use [env] only for non-sensitive strings that are safe to commit.
Use Fly secrets for credentials, API keys, database URLs, JWT signing keys, cookie/session secrets, OAuth client secrets, SMTP credentials, and service tokens:
fly secrets set NAME=value
fly secrets list
Use fly secrets set NAME=value --stage when secrets should be staged for a later deploy.
Do not print secret values in logs, deploy summaries, diagnostics, issue comments, or commit messages. When reporting secret state, name only the variable and whether it is missing, staged, listed, or expected by the app.
Databases And Migrations
For Vapor with Fluent and Postgres, Fly's Vapor docs describe creating or attaching Postgres so the app receives DATABASE_URL.
Use Fly Postgres attachment only when it matches the user's target deployment:
fly pg create
fly pg attach <postgres-app-name>
Treat migration timing as an explicit deployment decision:
- one-off manual migration command
- Fly release command
- application startup migration, only when the project already documents that risk tradeoff
- separate migration Machine or process group
Do not run destructive migration reverts, database resets, or production data repair commands without explicit user approval.
Health Checks And Rollouts
Use service-level TCP checks when the platform only needs to know whether the process is listening.
Use service-level HTTP checks when the app exposes a real readiness endpoint that returns a 2xx response after required dependencies are ready.
Use Machine checks only when deploy-time validation needs to run a command inside an ephemeral Machine, such as a dependency or background-service check that a simple HTTP endpoint cannot prove.
When health checks fail, inspect:
- the deployed process command
- startup logs
- host and port binding
- health-check path and expected status
- missing secrets or env
- database connectivity
- migration or startup ordering
- image architecture and Linux runtime dependencies
Do not claim a deploy succeeded until fly deploy completes and the relevant Fly output, health checks, or external HTTP check confirms the expected state.
Vapor And Hummingbird Handoffs
Use vapor-server-workflow for:
- Vapor route, controller, middleware, command, environment, server, and Fluent migration behavior
- deciding whether a health route belongs in the app
- Vapor 5 alpha posture or migration readiness
Use hummingbird-server-workflow for:
- Hummingbird router, middleware, request context, application lifecycle, command-line options, and framework testing behavior
- deciding whether a readiness route belongs in the app
Use docker-workflow for:
- Dockerfile stage design, Swift builder/runtime images,
.dockerignore, entry point, runtime assets, non-root execution, and local container validation
Use persistence-workflow for:
- schema changes, migrations, database query behavior, migration tests, seed data, and local dependency setup
Use observability guidance, when available, for:
- log levels, metrics, traces, alerting, health signal design, and production diagnostic dashboards
Testing And Validation
Prefer this order:
- Run the repository's SwiftPM build or tests when the deploy depends on compiled app behavior.
- Validate Docker image behavior locally when startup, port binding, runtime assets, Linux dependencies, or entry point are the risk.
- Review
fly.toml and secret names before first deploy.
- Run
fly launch --no-deploy for a new app when configuration needs review.
- Run
fly deploy for deployment validation. If its remote rollout, health checks, or build remain asynchronous after the bounded command returns, record the app, image/digest, release target, and health URL; reuse a live matching host-native continuation while the gate is pending and healthy instead of holding a shell or polling. On wakeup, continue only if the freshly inspected Fly app, release, image/digest, and health target exactly match the record; otherwise stop and create a new continuation packet before any deployment action. Do not delete/recreate it after an unchanged snapshot; create/update only after it fires or becomes stale, with at least five minutes between rechecks. Codex uses heartbeat; Hermes uses a continuable cronjob with deliver="origin" and attach_to_session=true.
- Use
fly logs, fly status, fly checks list, or Fly's monitoring page when deploy or runtime checks fail.
- Use
curl https://<app>.fly.dev/... only when public HTTP behavior is part of the expected result.
When a Fly command fails, report the exact app, region, command, process group, Machine, image, port, check, secret name, environment variable, or route involved. Include the likely cause, such as missing Dockerfile, wrong executable name, failed remote build, wrong host binding, wrong internal port, missing secret, failed migration, database not attached, health endpoint returning non-2xx, or a readiness check that depends on unavailable infrastructure.
Output Shape
Return:
Deployment shape: Fly app, package root, executable target, Dockerfile, fly.toml service/process shape, port, environment, secrets, database, checks, and public URL when known.
Docs used: Fly.io, Vapor, Hummingbird, Docker, SwiftPM, or persistence docs consulted.
Command path: exact fly, Docker, SwiftPM, migration, log, status, check, or HTTP commands run or recommended.
Runtime behavior: entry point, arguments, binding, health checks, release commands, process groups, migrations, and secrets.
Validation: deploy result, checks, logs, status, or HTTP result.
Handoffs: Vapor, Hummingbird, Docker, persistence, observability, CI, or release follow-up when the task crosses this skill's boundary.
Guardrails
- Do not commit secrets,
.env files, Fly access tokens, machine-local paths, private image credentials, or generated local auth state.
- Do not add Fly deployment files to an ordinary route or model change unless deployment scope was requested.
- Do not claim Fly CLI, config, health-check, secret, or Machine behavior from memory when current official docs can be checked.
- Do not run production-affecting Fly commands such as deploys, secret changes, database attachment, scaling, or app destruction without confirming the target app and intent.
- Do not use Fly deployment success as proof that route behavior is correct unless the route was checked directly or covered by tests.
- Do not let Fly-specific process groups or release commands become a hidden substitute for explicit app lifecycle and migration design.