Deployments and rollbacks

Statuses

StatusMeaning
QueuedWaiting for a server to pick it up.
BuildingA server is building the image from your code.
BuiltThe image is ready; the new version is starting (shown as Starting or Switching).
LiveIt runs and takes the requests.
FailedThe build failed, or the new version never became ready: the log and the error say why.
SupersededA newer deployment replaced it before it went live, or after.

Click a deployment in the Deployments tab for its log: the build, then each step of the switch.

Deploys without downtime

A new version of a public service doesn't replace the running one at once:

  1. The new version starts next to the running one. Visitors still reach the running one.
  2. Alf waits until the new version is ready: its port answers, and its health check path if you set one.
  3. New requests go to the new version. Requests still open on the old one finish (up to 60 seconds, WebSockets included).
  4. The old version stops and is removed.

If the new version isn't ready within 3 minutes (or exits), it's removed, the deployment fails, and the old version keeps running as if nothing happened. Nothing is retried by itself: fix the cause and deploy again.

1. The new version is built or pulled while the old one keeps serving; if the build fails, the deployment fails and nothing else changes. 2. Its release command runs, if it has one: once, in a container of the new version, in the project's network; if it fails or runs over 30 minutes, the deployment fails and the new version never starts. 3. The new version starts next to the old one, and visitors still reach the old one. 4. It's ready when its port answers, and its health check path if it has one; if not within 3 minutes, or it exits, it's removed, the deployment fails and the old one carries on. 5. New requests go to the new version, which is live; the old one finishes its open requests (up to 60 seconds, WebSockets too) and is removed.

A few details:

Release commands

A service's release command runs once per deploy, before the new version takes over: what database migrations are for (npx prisma migrate deploy, python manage.py migrate, bin/rails db:migrate, php artisan migrate --force). Set it in the service's settings (Command, variables and deploys → Release command) or as release_command in alf.toml; alf launch sets it when it recognises the code's migrations.

  1. The new version's image is built (or pulled), while the old version keeps serving.
  2. The command runs in a container of the new version, with its variables and secrets and in the project's network, so it reaches the database. It runs like alf exec: through the image's entrypoint.
  3. When it ends without an error, the new version starts and takes over as usual. Its output is in the deployment's log.
  4. When it fails (or runs longer than 30 minutes), the deployment fails with what it printed, the new version never starts, and the old one keeps serving. Nothing is tried again until the next deploy.

Good to know:

Candidates: try a version before it's live

A candidate is the new version started beside the live one and held there:

Visitors and the project's other services keep reaching the live version. When the candidate is healthy it shows as Ready to promote:

Start one from the arrow next to Deploy, from Deploy new code (tick *Hold it as a candidate*), with --candidate on the deploy command, or have an agent do it (deploy, upload_code or push_code with candidate).

Good to know:

When a deploy fails

A failed deployment says what kind of failure it was and shows what the app last printed:

It saysMeaningUsual fix
The app stoppedThe process exited before it was ready (with its exit code; "out of memory" if it used more than its limit).Read its last output: a missing variable or secret, a database it can't reach, a wrong start command. When the output says a variable is missing, the failure names it and the alf env set that sets it.
Nothing on its portIt runs, but nothing accepts connections on the service's port. If it listens somewhere else, that port is named.Set the service's Port to the one named, or make the app listen on $PORT and on 0.0.0.0.
Health checkThe port is open, but the health check path answered with an error, or not at all.Fix the path, or change or clear Health check path in Settings.
Release commandThe release command failed, so the new version never started; the old one still serves.Read its output: often a database it can't reach yet, or a migration that doesn't apply.
Couldn't start / BuildThe container or image couldn't be made.The message and the build log say why; for the usual build failures (a file not sent, a lockfile out of step, a missing script, a runtime too old, a missing base image) the failure says what fixes it.

A service that has never been live and fails its first deploy because its app listens on another port gets that port and deploys again by itself, once: the failed deployment says "Fixed by itself" with the port, and the new one follows it. It's only done when the app listens on exactly one other port. A port changed that way is the service's from then on, as if you had set it: if alf.toml says another, the next alf deploy asks which to keep and can write the new one into the file (without a terminal, as in CI, the new one stays).

The same goes for a service's very first start: a new service is started once, right after it's created, to see that it comes up. A service that sleeps when idle goes to sleep again afterwards.

When the old version stops first

Some services' old container stops before the new one starts (a gap of a few seconds):

A private app is called through its server's front, so it can switch without downtime like a public one: the new version starts beside the old one and takes the calls once it's ready. Choose Without downtime under Settings → When a new version is deployed (or zero_downtime = true in alf.toml) for an API the project calls; leave it for a worker.

You can choose to stop first for any service: Settings → When a new version is deployed → Stop the old version first.

Deploying again

Deploy at the top of the service's page deploys now: image services pull their image again, services built from code rebuild the code pushed last. New code arrives with the deploy command, from your computer or CI. Variable and secret changes waiting to be deployed go along.

Rolling back

Earlier deployments that were live have Roll back: it deploys the same image or commit again, with today's variables and secrets. Pushed code can be rolled back while it's kept (the newest three pushes of a service).

Working inside a container

Service → Terminal opens a shell in a container, for a migration, a database console or a look around. Two kinds:

Either way the container is metered like any other container of the service while it's open, and it closes itself after fifteen minutes without a command.

Your coding agent has the same thing without the terminal: exec runs a command and gives back the exit code and output, and open_session keeps a container between commands. Both need a token that may run commands. Everything run in a session is written down.

Running something on a schedule

Service → Scheduled runs a command over and over: a nightly clean-up, an hourly digest, a report on the first of the month. Each run gets the service's image, variables, secrets, volumes and project network, in a container thrown away afterwards — the same as a one-off command, with a time attached.

The schedule is written the way cron has always been written, in UTC:

0 3 * * *     every day at 03:00
*/15 * * * *  every fifteen minutes
0 9 * * 1     every Monday at 09:00
@daily        the same as 0 0 * * *

One thing that catches people out, and that cron has always done: if you give both a day of the month and a day of the week, it runs on either. 0 0 1 * 1 is the first of the month *and* every Monday, not the first when it falls on a Monday.

Run now runs it without waiting, which is how to see whether it works. A failed run is kept with its output, and you're told through your notification channels. A run still going when the next is due is skipped rather than started twice, and a service that can't be deployed to — an unpaid invoice, a spending limit — is passed over until that's sorted out.