Build & ship

Deployments

Every deployment pins an immutable release, runs your migrations, checks health before traffic moves, and keeps the previous release one step away.

Ways to deploy

FromHow
A Git pushConnect GitHub on the service; see Builds
A repository, nowapprail build shop/web --git URL --ref main
An imageapprail deploy shop/web --image ghcr.io/acme/web:1.4
Another environmentapprail promote shop/web --from staging --to production
An earlier releaseapprail rollback shop/web
Settings or variablesapprail services update shop/web --apply production redeploys the live image with the current settings and variables

The CLI follows the deployment until it is live or fails and prints the address; add --detach to return at once. apprail status shop/web shows what is live and recent deployments.

What happens during a deploy

  1. Preparing — the image tag is pinned to its exact digest and recorded as a release, with the service’s settings.
  2. Pre-deploy command — if set, runs once before any traffic moves.
  3. Canary or blue/green — if enabled, the release takes a share of traffic first, or starts as a full copy that takes all of it at once.
  4. Rolling out — new instances start and must pass their health check; old instances keep serving until they do, then drain gracefully.
  5. Live — or, if it never becomes healthy, the deployment fails and the previous release keeps serving.

The full list of states is in Core concepts.

Health checks

Each new instance of a web service must answer its health path (default /) on its port with a 2xx or 3xx status before it receives traffic. If the release isn’t healthy within five minutes, the deployment fails with how many instances got ready.

Terminal
apprail services update shop/web --port 3000 --health /healthz --apply production

Use a cheap endpoint that answers as soon as the app can serve requests — not one that waits on every dependency, or a brief database hiccup will stop deployments. Workers have no health path: they are healthy once they’ve stayed running for 15 seconds.

Pre-deploy commands

Database migrations and other setup your app expects before it starts. The command runs once per new release, in the release’s image, with the environment’s variables and the service’s CPU and memory, through sh -c — before any instance gets traffic.

Terminal
apprail services update shop/web --pre-deploy "npx prisma migrate deploy"
FrameworkTypical command
Prismanpx prisma migrate deploy
Railsbundle exec rails db:migrate
Djangopython manage.py migrate
Laravelphp artisan migrate --force
Drizzlenpx drizzle-kit migrate

The command runs on every release, so make it safe to run again: migration tools track what already ran; plain SQL should use CREATE TABLE IF NOT EXISTS. With canaries or blue/green, migrations run before the new release starts, so they must also work with the release that’s still serving most traffic.

Canary releases

Send a share of public traffic (1–50%) to each new release, on one extra instance, for a few minutes before it rolls out everywhere.

Terminal
apprail services update shop/web --canary 10 --canary-minutes 10

Blue/green releases

Start a full copy of each new release beside the live one, switch all public traffic to it at once when every instance is healthy, and keep the previous release running as a standby you can fall back to instantly.

Terminal
apprail services update shop/web --blue-green on --blue-green-minutes 10
  1. The new release starts at full size with no traffic. If any instance fails its health check within five minutes, crashes, or restarts, it is deleted before a single request reaches it.
  2. When every instance is ready, all public traffic moves to it in one step.
  3. The previous release stands by for the set time (1–60 minutes, default 10). If the new release crashes or restarts meanwhile, fails the same error-rate and response-time gates as a canary (compared with how the previous release did just before the switch), or you deploy or roll back, traffic goes straight back to the standby, which never stopped.
  4. After that, the standby is updated to the new release with the usual health-checked update while the copy keeps serving; then traffic returns to it and the copy is removed.

Promote and roll back

Promotion deploys the exact release that is live in one environment to another — the same image digest and settings, nothing rebuilt. Rollback returns to the previous live release, or any earlier one with --to, including the scaling it ran with. Both go through health checks like any deployment.

Terminal
apprail promote shop/web --from staging --to production
apprail rollback shop/web
apprail rollback shop/web --to dpl_…

Release several services together

When services depend on each other — an API before the web app that calls it — release them as one group, in stages. A stage starts only when every service in the previous stage is live; services in one stage deploy side by side. Each still goes through its own health check, canary, or blue/green.

Terminal
apprail release shop --env production api=ghcr.io/acme/api:2.4 web=ghcr.io/acme/web:2.4,worker=ghcr.io/acme/worker:2.4
apprail promote shop --from staging --to production
apprail releases shop

A multi-service release is ordered, not atomic. Rollbacks are ordinary deployments, so for a minute old and new versions run side by side, and database changes made by pre-deploy commands are never undone. Make every release work with the schema before and after it, as below.

Backward-compatible migrations

Change the database in steps that each work with the code running on both sides of them (“expand and contract”):

  1. Expand — add new columns or tables, nullable or with defaults. Old code ignores them. Ship this migration alone or with code that doesn’t depend on it yet.
  2. Migrate — release code that writes both the old and new shape and reads the new one, then backfill existing rows (a one-off apprail run).
  3. Contract — once nothing live reads the old shape, and you won’t roll back past that release, drop it in a later release.

Never rename or drop a column in the same release as the code that stops using it: a rollback would bring back code that needs it. Put the service that owns the migration in the first stage, so the schema changes before the services that rely on it start.

Production approval

Protect an environment and deployments not started by an owner or admin in the console wait for approval — including every token, CI job, coding agent, and git push. See Protect production.

Zero-downtime details

During a rollout, an old instance stops receiving new requests, waits 5 seconds for the edge to catch up, then gets SIGTERM and has the rest of 30 seconds to finish in-flight requests. Handle SIGTERM by closing the server gracefully. WebSocket connections are closed at that point, so clients should reconnect; they’ll reach a new instance.