CLI Reference

deploy

Deploy your application to production servers.


Synopsis

odysseus deploy [options]

Deploy your Docker containers to configured servers with zero-downtime rolling updates.


Options

--config FILE

Path to configuration file. Default: deploy.yml

odysseus deploy --config production.yml --image v1.0.0

--image TAG

Docker image tag to deploy. Optional — and the two ways of ending up with a version are not equivalent.

Omit it, from a clean git repository. Odysseus takes the version from the commit SHA and refuses to deploy while the working tree has uncommitted changes — so the tag it uses provably identifies the code that's running. It records that commit, and your identity (git's configured email, falling back to $USER), in each host's deploy log. This is the proper route.

Pass it explicitly, with --image TAG. No git repository is needed, so it works anywhere — the right tool for trying something out, or for deploying an image built elsewhere. It still records who ran the deploy; what it withholds is the commit, because an arbitrary tag has no commit it honestly identifies. It also doesn't stop the same tag being deployed twice, which leaves two entries in the deploy log odysseus can't tell apart.

Outside a git repository, --image stops being optional: with nothing to take a version from, odysseus has no version to deploy without it.

The cost of the quick route is felt at rollback time — see What a version can tell you.

odysseus deploy                 # clean git repo: version = commit SHA
odysseus deploy --image v1.0.0  # explicit tag: works anywhere
odysseus deploy --image latest

--build

Build the Docker image before deploying.

odysseus deploy --build --image v1.0.0

When used with a registry configuration, the image is pushed after building. Without a registry, the image is transferred directly to servers.

--dry-run

Show what would be deployed without making changes. No image is pulled, no container is started, and no host is touched.

odysseus deploy --dry-run --image v1.0.0

Output — four lines per role/host, not one: deploy_role (executor.rb:243-247) prints the Dry run - would deploy... line, then Service: and Role: unconditionally, then Proxy hosts: too when the role is web. Nothing filters these out of the streamed step, so all of them show up as sub-steps, repeating the Service: the header already printed once:

  Odysseus Deploy
  Service: myapp
  Image: myapp:v1.0.0

  01  >  Deploying service
  01  ✓  Dry run - would deploy myapp:v1.0.0 to app1.example.com
  01  ✓  Service: myapp
  01  ✓  Role: web
  01  ✓  Proxy hosts: app.example.com
  01  ✓  Dry run - would deploy myapp:v1.0.0 to app2.example.com
  01  ✓  Service: myapp
  01  ✓  Role: web
  01  ✓  Proxy hosts: app.example.com

  02  ✓  Deployment successful in 0.3s

The last line always carries a duration (cli.rb:69-70 times the whole deploy and passes it to deploy_complete) and its own step number — it's never the bare Deployment successful with nothing after it.

--verbose, -v

This is not the same as --debug for deploy, even though both touch the same command — see Global options for why they're different flags with different effects. -v only makes the executor and every SSH connection it opens verbose (the same Connecting to.../Connected to.../ > <command> lines --debug shows for the SSH layer); it does not switch the step-by-step spinner UI to plain text — that only happens under --debug or ODYSSEUS_DEBUG=1, which deploy never sets from -v (bin/odysseus:15, cli.rb:30).

odysseus deploy --verbose --image v1.0.0

Examples

Basic deployment

Deploy a pre-built image:

odysseus deploy --image v1.0.0

Build and deploy

Build locally and deploy:

odysseus deploy --build --image v1.0.0

Deploy with custom config

odysseus deploy --config staging.yml --image v1.0.0

Debug deployment

odysseus deploy --verbose --image v1.0.0

Test before deploying

odysseus deploy --dry-run --image v1.0.0

Deployment process

For each server and role, Odysseus:

  1. Ensures Caddy is running - Starts it if it isn't already (web roles only)
  2. Starts new container - With configured environment and options
  3. Waits for health check - Web roles always health-check (default timeout 60s); other roles only if that role has its own healthcheck configured, otherwise odysseus pauses briefly for startup instead
  4. Updates Caddy routing - For web roles only
  5. Drains and removes old containers - Out of the load balancer, then stopped and removed (web_deploy.rb:252-253) — the container a deploy replaces never lingers as a stopped one
  6. Cleans up crashed containers - Removes stopped (exited) containers beyond the 2 most recent for this role and host (docker/client.rb:299-301). Because step 5 already removes the container being replaced, what this sweep finds is containers that exited on their own — crashes, not deploy history

Exit codes

CodeMeaning
0Deployment succeeded on every role and host
1Configuration failed to load, a health check failed, or a host errored mid-deploy

Environment variables

ODYSSEUS_MASTER_KEY

Master key for decrypting secrets.

ODYSSEUS_MASTER_KEY=your-key odysseus deploy --image v1.0.0

  • build - Build Docker images
  • pussh - Transfer images to servers
  • status - Check deployment status
  • logs - View application logs
  • rollback - Return to a previously deployed version
  • Global options - --debug, --config, and the rest
Previous
doctor