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
| From | How |
|---|---|
| A Git push | Connect GitHub on the service; see Builds |
| A repository, now | apprail build shop/web --git URL --ref main |
| An image | apprail deploy shop/web --image ghcr.io/acme/web:1.4 |
| Another environment | apprail promote shop/web --from staging --to production |
| An earlier release | apprail rollback shop/web |
| Settings or variables | apprail 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
- Preparing — the image tag is pinned to its exact digest and recorded as a release, with the service’s settings.
- Pre-deploy command — if set, runs once before any traffic moves.
- 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.
- Rolling out — new instances start and must pass their health check; old instances keep serving until they do, then drain gracefully.
- 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.
apprail services update shop/web --port 3000 --health /healthz --apply productionUse 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.
apprail services update shop/web --pre-deploy "npx prisma migrate deploy"| Framework | Typical command |
|---|---|
| Prisma | npx prisma migrate deploy |
| Rails | bundle exec rails db:migrate |
| Django | python manage.py migrate |
| Laravel | php artisan migrate --force |
| Drizzle | npx drizzle-kit migrate |
- It runs for image deploys, Git builds, promotions, and settings changes. Rollbacks never re-run it — re-running old migrations can undo newer ones.
- If it exits non-zero or takes more than 15 minutes, the deployment fails with its exit code and last output, and the current release keeps serving. On success, its last lines are kept in the deployment’s history.
- The image must contain
shand whatever the command needs.
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.
apprail services update shop/web --canary 10 --canary-minutes 10- If the canary crashes, restarts even once, or fails its health check, it is taken out of traffic automatically and deleted; the deployment fails with the reason and nothing else changed.
- A canary that stays healthy for its time is removed and the release rolls out through the usual health-checked update.
- Only public traffic is split; service-to-service calls keep going to the stable release.
- First deployments, rollbacks, and workers skip the canary.
- It is also judged on the public requests it answers, counted at Apprail’s gateway. Once it has served 20 requests, it is removed if more than 5% of them fail with a server error (5xx) and that is at least 5 points worse than the current release, or if 95% of its requests take more than twice as long as the current release’s and at least 250 ms longer. When it rolls out, the deployment history records what it served.
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.
apprail services update shop/web --blue-green on --blue-green-minutes 10- 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.
- When every instance is ready, all public traffic moves to it in one step.
- 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.
- 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.
- It needs room for twice the service’s instances while it runs; if the region is full, the new copy waits and fails after five minutes without touching the live release.
- It is one strategy or the other: a service uses a canary or blue/green, not both. Web services only; first deployments and rollbacks switch directly.
- Only public traffic switches; service-to-service calls keep going to the standby until the end.
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.
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.
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- Each argument after the project is a stage; commas put services in the same stage.
apprail promote PROJECT(no service) promotes every service whose live release differs, together — also on the project page under Multi-service releases → Promote environment. - If a service fails, later stages never start. By default the services this release already made live are rolled back to the releases they had before (
--no-rollbackleaves them). The result says exactly what was rolled back and what was not. - In a protected environment, a release started by a token, agent, CI, or developer waits for one approval that covers all of its services.
- Stopping a release (
apprail releases shop --cancel rlg_…) starts no further stages; deployments already running finish.
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”):
- 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.
- 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). - 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.