--- title: "Fly.io" description: "Deploy Obol Docker images to Fly with separate production credentials, private Control networking, and the production domains." --- The Fly configuration uses one organization and one region. Production and local development have independent credentials, databases, Redis state and Docker image tags. Local hot reload remains available through `make -C infra up`. ## Production domains | Surface | Origin | Deployment | | --- | --- | --- | | Website | `https://tryobol.dev` | `obol-landing`; also serves `www.tryobol.dev` | | Documentation | `https://docs.tryobol.dev` | Mintlify static export in `obol-docs` | | Dashboard | `https://app.tryobol.dev` | `obol-web` | | Control API and MCP OAuth issuer | `https://control.tryobol.dev` | `obol-control` | | Gateway | `https://gateway.tryobol.dev` | `obol-gateway` | | Clerk frontend API | `https://clerk.tryobol.dev` | Clerk production instance | | Clerk account portal | `https://accounts.tryobol.dev` | Clerk production instance | Hosted Control and gateway are still being commissioned. Publishing the website, docs and sign-in page does not mean a workspace can already execute agent traffic. ## Public and private networking Control API Machines run two route sets in one supervised process: - Public port 8000 serves operator APIs and OAuth routes. It has no internal router. - Private port 8001 serves health and service-authenticated internal routes. This port is not advertised to Fly Proxy. Gateway binds `[::]:8080` and calls `http://api.process.obol-control.internal:8001` over Fly's private IPv6 network. Process-qualified DNS excludes worker Machines. Both Control listeners drain before the shared database pool closes (ADR-0065). ```toml [processes] api = "docker-entrypoint.sh split-api" worker = "docker-entrypoint.sh worker" ``` Migrations run as the Control release command before new Machines take traffic. The gateway's Docker build selects `GATEWAY_FEATURES=kek-aws`; it receives no PostgreSQL or Stripe credentials. Its `/readyz` check requires Redis and a valid loaded policy snapshot. Do not disable this check for an empty installation. There is no connectors worker deployment. Reviewed native packs use the gateway HTTP executor, and remote MCP servers federate directly. See [trusted workers](/connectors/trusted-workers). ## Production credentials Store owner-only environment files outside the repository, then import runtime values into Fly through standard input. Never place private keys in build arguments. The release helper validates separation from local development before staging secrets; provider authentication must be checked separately. | File | Required settings | | --- | --- | | `control.env` | Production PostgreSQL and Redis URLs, service JWT secret, AWS region/KMS key and Control identity, production Clerk issuer/secret, restricted live Stripe billing key and catalog/webhook settings | | `gateway.env` | Production Redis URL, service JWT secret, AWS region/KMS key and gateway identity | | `web.env` | Production Clerk secret and publishable key | Control and gateway use separate AWS principals. Only gateway may decrypt vendor credentials. Production Clerk uses `pk_live_`/`sk_live_`; local development keeps its test instance. Stripe platform billing uses a restricted `rk_live_` runtime key in Control; local billing remains in the sandbox. ```bash export OBOL_PRODUCTION_DIR="$HOME/.config/obol/production" make -C infra fly-check ``` ## First application release Provision managed PostgreSQL, Redis and AWS KMS, and complete the production configuration first. Deploy Control and web individually: ```bash python3 infra/scripts/production_environment.py \ --directory "$OBOL_PRODUCTION_DIR" --app control --deploy python3 infra/scripts/production_environment.py \ --directory "$OBOL_PRODUCTION_DIR" --app web --deploy ``` Sign in as the legitimate workspace owner and publish an initial policy snapshot. Then deploy gateway: ```bash python3 infra/scripts/production_environment.py \ --directory "$OBOL_PRODUCTION_DIR" --app gateway --deploy ``` For subsequent complete application releases, `make -C infra fly-deploy` deploys Control, web and gateway in that order. API and worker must share the same live Stripe catalog and webhook configuration before paid enrollment. ## Website and documentation releases The website uses the existing landing Dockerfile. Its public sign-in URL is compiled as `https://app.tryobol.dev`. It receives no customer or platform API credentials. Optional waitlist and connector-request webhooks are separate runtime settings; without them submissions return an unavailable response. ```bash make -C infra fly-deploy-landing make -C infra fly-deploy-docs ``` The docs command validates the MDX, runs Mintlify's CLI export and deploys the static bundle with Fly CLI. The Docker build receives only the exported public site and its Dockerfile. Documentation changes require another export and deploy; a static export does not run Mintlify's hosted AI or analytics services. ## DNS and TLS Create certificates using `fly certs add` for each app's real hostnames. Use the returned A/AAAA or CNAME values in Namecheap; preserve existing email and domain verification records. Verify certificate readiness and HTTPS responses before sending visitors to a hostname. Marketing canonical URLs, robots and sitemap use `https://tryobol.dev`; docs navigation links back to the website and dashboard.