Skip to content

Add a host

Railway and AWS are both made from packages/deploy/src/production.ts, which says what production is apart from any host. A new host (Fly.io, Render, Google Cloud, Kubernetes…) is made from it too. Your coding agent can do this with the add-host skill, which follows this page.

To run the images on a host by hand instead, without code, see Other hosts.

Export What it is
services api, worker, web, site: their Dockerfile, port, command, health path
migrate The command to run in the API’s image before a new version starts
apiSettings What the API is told that only the owner knows. The host adds the rest
webBuildSettings What the web app’s build is given, for Sentry
siteBuild() The site’s build arguments: its address and the app’s are baked into its pages

Every host must give production what it needs:

  1. Postgres and Redis, reachable only from the services. The API takes DATABASE_URL, or PGHOST, PGPORT, PGUSER, PGPASSWORD and PGDATABASE. Redis must never evict: maxmemory-policy noeviction.
  2. One public address for the web app and one for the site, over HTTPS, with http:// sent on to it. The API has none: the web image passes /api to it on the private network, at API_URL.
  3. The API’s settings from the host’s own secret store, never written into what is committed. An empty value in the settings file means “leave it as it is on the host”.
  4. The migrations before each new version, and nothing more if they fail.
  5. A health check on every service with a healthPath, and the old version kept until the new one passes it.
  6. The visitor’s address in X-Forwarded-For from a private address. Caddy in the web image believes only those. See Other hosts.

Copy what AWS has, under packages/deploy/src/<host>/:

File Does
.env.<host>.example At the root. The host’s own settings, then the API’s, copied from .env.aws.example
stack.ts or similar The resources, made from production.ts. Nothing about a service written twice
deploy.ts plan, apply, destroy: checks the settings, fills the secret store, runs the tool
*.test.ts That each requirement above holds, without the host

Then:

  • Add the host to HOSTS in packages/deploy/src/settings.ts, its settings to HOST_SETTINGS and its rules to ofTheHost in before.ts, so bun run deploy:check --before <host> checks them, and to NEXT in init.ts.
  • Add deploy:plan:<host>, deploy:apply:<host> and deploy:destroy:<host> to the root package.json.
  • Write docs/deploy/<host>.md like AWS: what you need, the first deploy, what the settings file controls, what happens on each release.

A host whose tool runs your file in a sandbox, as Railway’s does, may not let it import production.ts. Then write it out there, and add a test that holds the two together, as railway.test.ts does.

  • bun run check passes, and the new tests fail if you break a requirement on purpose.
  • A real deploy, from an empty account, through deploy:check --email, and a release with a migration that fails, to see the old version keep serving.
  • deploy:destroy:<host> leaves nothing behind that costs money.