Deployment hooks

Orbit runs on your machine over SSH: sync source, then run ops/* on the remote. Hooks live in your repository under ops/.

                    your machine                 remote host ($SLOT_SRC)
orbit deploy        rsync work tree → src         /bin/bash ops/deploy
orbit status        SSH                           /bin/bash ops/status (optional)
orbit remove        SSH                           /bin/bash ops/remove (optional)

Sync finishes before the hook runs. Hooks execute from $SLOT_SRC with SLOT_* exported. Orbit does not read hook contents.

Lifecycle

  • orbit init — writes ops/ from a recipe. Local only. No remote hook.

  • orbit create — slot directories and optional expiry timer. No hook.

  • orbit env — writes $SLOT_ENV. No hook.

  • orbit deploy — synchronizes $SLOT_SRC, then runs ops/deploy.

  • orbit status — runs ops/status when present.

  • orbit remove — runs ops/remove when present, then deletes src and run. --purge also deletes data and env.

ops/deploy is required. Add ops/status and ops/remove when the app runs as a long-lived process.

Normal removal keeps data and env. Recreate the same slot name to reuse them. orbit remove --purge and automatic expiry delete the whole slot.

Hook files

text
ops/deploy     required   — orbit deploy
ops/status     optional   — orbit status
ops/remove     optional   — orbit remove

The file name is the command name. Orbit runs /bin/bash -- ops/<name>. The files do not need a .sh suffix or the execute bit. A #!/bin/bash line at the top is for humans.

ops/remove stops the app. Orbit then deletes src and run.

Hooks run from $SLOT_SRC, take no arguments, and cannot prompt. Orbit streams output to your terminal.

Writing hooks

bash
#!/bin/bash
set -euo pipefail
 
# Build or install from "$SLOT_SRC".
# Start the new process or container.
# Check that it is healthy.
# Publish it or reload the service.

Idempotent. Exit 0 on success, non-zero on failure.

Docker — build and run containers, health-check a release socket, switch a stable socket, keep $SLOT_DATA and $SLOT_ENV outside the image.

systemd — install or compile under $SLOT_SRC or $SLOT_RUN, unit with EnvironmentFile=$SLOT_ENV, restart, check socket or port, reload Caddy when needed. ops/remove stops the unit; ops/status checks the unit or socket.

See Podman. Other runtimes: name releases with $SLOT_DEPLOY_ID, keep data in $SLOT_DATA, publish under $SLOT_RUN, and make ops/remove find the app by slot label — not a remembered deploy ID.

Slot layout

text
/srv/orbit/
  Caddyfile
  slots/<slot>/
    src/      synchronized repository
    data/     durable application data
    env       application environment, mode 0600
    run/      sockets and generated runtime configuration

The deploy user owns src, data, and env. Caddy can use run/ but cannot read src, data, or env.

Synchronization

Orbit selects with git ls-files. Default deploy requires a clean work tree, so the upload matches HEAD. --force also includes:

  • tracked files with local modifications

  • untracked files Git does not ignore

It mirrors that tree into $SLOT_SRC with rsync. Files absent from the selection are deleted on the remote. The transfer is not atomic; do not run concurrent deploy or remove on the same slot.

Keep secrets, durable data, and runtime files outside $SLOT_SRC.

The deploy hook receives slot paths, $SLOT_DEPLOY_ID, and $SLOT_GIT_REVISION. Use data, env, and run to see what is already running.

Cutover — stop/start, blue/green, migrations — is the hook's job. Orbit does not choose a strategy.

Variables from Orbit

Exported before each hook:

text
ORBIT_ROOT    /srv/orbit
ORBIT_CADDY   /srv/orbit/Caddyfile
 
SLOT_NAME     concrete slot name (after {} expansion)
SLOT_ROOT     /srv/orbit/slots/<slot>
SLOT_SRC      synchronized repository
SLOT_DATA     durable application data
SLOT_ENV      application environment file
SLOT_RUN      sockets and generated runtime files
SLOT_TTL      configured lifetime in seconds, or 0

ops/deploy also receives:

text
SLOT_DEPLOY_ID      unique 12-character ID for this deployment
SLOT_GIT_REVISION   local Git HEAD revision

Use $SLOT_DEPLOY_ID to name releases, images, containers, or sockets. $SLOT_GIT_REVISION is for labels. With --force, Orbit also uploads local changes and non-ignored untracked files.

ops/remove receives ORBIT_PURGE (1 = delete data and env, 0 = keep them).

Environment file

orbit env writes $SLOT_ENV (mode 0600, atomic replace). Orbit does not source it into the hook. Pass it to the app with --env-file, EnvironmentFile=, or equivalent.

Caddy

Host setup prepares system Caddy. Hooks write per-slot routes and reload after the app socket is ready:

  1. Listen on a Unix socket under $SLOT_RUN.

  2. Write $SLOT_RUN/Caddyfile.

  3. Reload after the socket is ready.

Main file imports slot routes:

caddy
{
    email you@example.com
}
import /srv/orbit/slots/*/run/Caddyfile

ops/remove

Stops application-owned processes and removes runtime files. Non-zero exit aborts removal. When missing, Orbit still deletes src and run and warns that processes or routes may remain.

ops/status

Print one short line:

  • exit 0 — healthy

  • exit 1 — stopped or unhealthy

  • exit 2+ — check failed

If ops/status is missing, Orbit prints unknown and exits 0. If src/ is missing, it prints missing and exits 1.