CLI Reference

doctor

Read-only diagnosis of every host in the config, connecting as the identity your deploys actually use.


Synopsis

odysseus doctor [--config FILE]

For every host in the config, doctor connects as ssh.user — whatever your config says, which is the identity deploy itself uses, never a bootstrap identity like setup --as — and runs five checks. A host that's perfectly fine for root can be unusable for a deploy user; connecting as anyone else would pass hosts that would still fail at the first deploy.

It changes nothing

No check writes to the host: no directory is created, no package is installed, nothing is repaired. The state-directory check in particular proves writability by walking up to the nearest existing ancestor and testing that — it never creates the directory itself in order to find out. That's what makes doctor safe to run against a production host: it can only tell you what's true, not change what's true.


Checks

Run in this order, against every host:

distro

Reads /etc/os-release and reports the distro and version. This is informational only — an unsupported distro is a warning, not a failure. Deploys work anywhere Docker does, and nothing else here depends on which distro provided it. This is the deliberate asymmetry with setup, which refuses to run against an unsupported distro because it's about to change the host and won't proceed on a guess; doctor only ever reads, so it has nothing to protect by refusing.

If this warns, there's nothing to do — the warning exists so you know why, not so you fix it.

docker

Runs docker info as the deploy user. If the daemon doesn't answer, this fails: nothing else can work without it. If it fails, either Docker isn't installed or the deploy user can't reach it — setup installs Docker and gets a fresh host to this point in one step.

docker_group

Checks whether the deploy user is in the docker group, which is what lets it reach the Docker socket. Skipped (reported :ok) for root, which needs no group membership. If this fails for a non-root user, add them to the group — setup does this as part of creating the user, so a failure here on an already-setup host usually means the user was created some other way.

state_dir

Checks whether odysseus's state directory is writable by the deploy user — root's is /var/lib/odysseus, anyone else's is ~/.odysseus. The directory may legitimately not exist yet on a host that's never deployed, so the check walks up to the nearest existing ancestor and tests that instead of creating anything. If this fails, the deploy user can't write where odysseus needs to keep its state; fix ownership or permissions on the ancestor the failure names, or run setup again.

deploy_log

Reports where rollback will read and write this service's deploy history, and warns if older history exists at a location the current deploy user can read but no longer write to — the case of a host that moved from a root install to a deploy user. Losing that history isn't loud; a rollback just quietly offers fewer versions than it could. If this warns, there's no fix required, only something to know about: rollback will still work, it just won't see history written before the move.

Caddy's data directory is deliberately not checked: it doesn't exist until the first deploy starts Caddy, so a correctly configured host that hasn't deployed yet would be reported broken.


Exit codes

CodeMeaning
0Every check passed, or passed with only warnings
1At least one check failed on at least one host, or a connection dropped mid-survey

A warning never sets a non-zero exit; only :fail does. One host's connection dropping mid-survey — a :fail reported as reachable — doesn't stop the others: doctor moves on and surveys the rest, then still exits non-zero once every host has been tried.


Output


  Odysseus Doctor
  Service: myapp
  Deploy user: deploy

  ▸ app.example.com
  01  ✓  distro: ubuntu 24.04
  02  ✓  docker: docker 27.3.1
  03  ✓  docker_group: deploy is in the docker group
  04  ✓  state_dir: /home/deploy/.odysseus is writable
  05  ✓  deploy_log: /home/deploy/.odysseus/myapp/deploys.log

  ✓ This host is ready.

What it's for

Preparing servers at scale belongs to OpenTofu, Terraform or an equivalent tool, not to odysseus. setup does a deliberately narrow slice of that — a trial-scale bootstrap for one host — and its own last step already proves Docker and the state directory work as the new user. doctor answers the same question — is this host actually usable by odysseus, as the identity your deploy.yml names — on any host, however it was prepared, including one a provisioning tool built. It's the acceptance test for setup, and it's equally worth running on anything else.


  • setup - Prepare a fresh host for odysseus to deploy to
  • deploy - Deploy to a host once it's ready
Previous
setup
Next
deploy