Guide
Orbit deploys a project from your local Git work tree to a server that you control. It synchronizes the project over SSH, then runs a deployment script from the repository on that server.
The three parts
Orbit uses three terms throughout this manual:
The project is the application repository. It owns the scripts that know how to build, start, check, and stop the application.
The host is the remote machine. Orbit connects to it through SSH and uploads the project.
A slot is one place on that host where the application runs. It contains the uploaded source and separate locations for configuration, durable data, and runtime files.
A hook is one of the application-owned scripts under ops/. The required ops/deploy.sh hook tells the remote machine how this project runs.
The same project can run in more than one slot:
weather project ops/deploy.sh | | Orbit uploads the same project | +--> production slot + production configuration +--> staging slot + staging configuration +--> demo slot + temporary configuration and expiryThe source and hooks stay the same. Each slot supplies its own environment values and secrets. A slot can be permanent or temporary; the application does not need a separate deployment model for each case.
Orbit owns the upload, slot structure, and hook calls. The project owns the application-specific work.
One deployment
After you create a slot and set its environment, orbit deploy:
Uses Git to select tracked files, local changes, and non-ignored untracked files.
Makes the slot's remote
srcdirectory match that selection.Runs
ops/deploy.shfrom the remotesrcdirectory.Streams the hook output and returns its exit status.
The project hook can build, start, check, switch, and reload the application. Orbit does not translate those steps into its own configuration format. It calls the hook and returns the result.
Slot lifecycle
Run orbit setup once for a host. Each slot then moves through the same small lifecycle:
orbit createcreates its directories and optional expiry timer.orbit envwrites application values outside the synchronized source.orbit deploysynchronizes source and runsops/deploy.sh.orbit statusrunsops/status.shwhen the project provides it.orbit removerunsops/down.sh, then removes source and runtime files.
A normal removal keeps the slot's durable data and environment file. You can create the same explicit slot again and reuse them. orbit remove --purge and automatic expiry delete the complete slot.
The deploy hook also receives an ID for the current deployment, the local Git revision, and paths to every part of the slot. The Hooks page defines each value.
Why a convention
The application knows more about its deployment than a general configuration format does. It knows what to build, which migrations to run, how to start, what health means, and how to stop.
Orbit keeps that knowledge close to the application in repository scripts. It only standardizes the common outer structure: source upload, slots, paths, external configuration, hook names, status, and removal.
This gives different projects the same recognizable shape without forcing their internal deployment steps to match. Moving between projects stays simple: find ops/deploy.sh, inspect the slot configuration, and follow the same lifecycle.
Application support
The Orbit command uses Python, and its remote actions and hooks use Bash. The application can use any language or runtime. For example, ops/deploy.sh can:
build and replace a Podman or Docker container
compile a Go binary and restart a systemd service
install a Python application into a virtual environment
copy static files into place
run migrations and health checks
The deployment decisions remain in readable scripts beside the application.
Before you start
The local machine needs:
Python 3.14 or newer
Git
OpenSSH
rsync
The current host setup supports Ubuntu and needs:
Bash
OpenSSH
rsync
Caddy with a system service
sudoaccess during setupa user systemd manager for optional slot expiry
1. Install Orbit
The Python distribution is named stario-orbit. The command is orbit. The unrelated orbit package on PyPI is not this project.
uv tool install stario-orbit# or: uv add --dev stario-orbit# or: pip install stario-orbitorbit --helpFor development from a checkout:
uv syncuv run orbit --help2. Name the SSH host
Orbit passes the host value to ssh and rsync. An OpenSSH Host alias keeps the user and address out of deployment commands:
Host ship HostName host.example.com User deployThe rest of this guide uses ship for the host. You can also pass user@hostname, an address, or a hostname directly.
Confirm that the connection works before you continue:
ssh ship3. Prepare the host
Orbit setup changes the system Caddy service so that it reads /srv/orbit/Caddyfile instead of /etc/caddy/Caddyfile. Existing Caddy sites on that host stop unless you move them into Orbit slots. Start with a dedicated or disposable Ubuntu host.
Run setup once for the host:
orbit setup ship --email you@example.comSetup creates /srv/orbit, prepares the slot permissions, configures Caddy, enables user lingering for optional expiry timers, and checks that the deploy user can reload Caddy.
Setup uses an interactive SSH session so sudo can prompt. Open a new SSH session after setup so the shell receives the new orbit group membership.
4. Add the deployment hook
At minimum, add an executable deployment hook:
your-project/ ops/ deploy.shOrbit runs the hook from the synchronized project directory. The hook receives paths such as $SLOT_SRC, $SLOT_DATA, $SLOT_ENV, and $SLOT_RUN.
The shape of a deployment hook is:
#!/bin/bashset -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.Make the hook idempotent. It must exit with 0 when deployment succeeds and a non-zero status when deployment fails.
If you want a working starting point, follow the Podman recipe. It shows the complete build, start, health check, traffic switch, status, and removal sequence. The Hooks page is the full hook contract. Other runtimes sit under Recipes.
5. Create the slot and deploy
The example uses app.example.com as both the slot name and the public hostname. Create it:
orbit create ship app.example.comProvide the environment values and secrets for this instance of the application before the first deploy:
orbit env ship app.example.com --from .env.productionNow deploy and check the result:
orbit deploy ship app.example.comorbit status ship app.example.comDo not keep application secrets in the synchronized repository. A local .env file that Git does not ignore is uploaded with the source. Use orbit env for slot environment values. The project hook decides how to pass $SLOT_ENV to the application.
Orbit streams the deployment hook output. A successful hook exits with 0. If the project has ops/status.sh, the status command prints the short status that the hook returns.
Later deployments
After a successful create or deploy, Orbit remembers the Git remote and branch to host and slot mapping. Later deployments from the same branch can use:
orbit deployTemporary previews
A temporary preview uses the same project and hook as production. Use {} in a slot name to create a readable generated name. Add --ttl to purge the slot automatically:
orbit create ship {}.example.com --ttl 6h# created ship calm-otter.example.comorbit deploy ship calm-otter.example.comThe maximum lifetime is 30 days. Expiry uses a transient user systemd timer, which does not survive a host reboot.
Next steps
Hooks explains the lifecycle, slot paths, hook variables, Caddy integration, status, and removal.
CLI reference lists every command and target rule.