CLI reference
checked against the binary in CI
Every verb below appears in printHelp (packages/cli/src/cli/main.ts). A
script in site/scripts/check-cli-reference.mjs runs in CI and fails if this
page lists a verb the binary does not have, or omits one it does.
Two conventions apply throughout:
<target>isprojectwhen the project holds exactly one resource, andproject/resourceotherwise.--jsonis accepted by every command that returns data, and prints the same object the human output was rendered from, so the two cannot drift.
Host and daemon
Section titled “Host and daemon”hobby init
Section titled “hobby init”Prepares the host. Creates ~/.hobby, checks that Docker is reachable, and
inspects the filesystem for reflink support and the host networking Caddy would
need. Warns rather than failing on either.
Accepts --json.
hobby daemon
Section titled “hobby daemon”Runs the daemon in the foreground. It owns all state and all lifecycle, listens on a unix socket for the CLI and MCP, and on loopback TCP for Studio. Only one may run at a time, and that is enforced rather than assumed.
Takes no flags.
Projects and resources
Section titled “Projects and resources”hobby new <name> [--empty]
Section titled “hobby new <name> [--empty]”Creates a project, puts a postgres resource named primary in it, waits for
that database to accept queries, and prints a connection string.
--empty creates the project with nothing in it.
hobby ls
Section titled “hobby ls”Every project, with every resource and its sleep state. A released project is
still listed, marked (released, not managed). An undeployed app or worker is
marked (no code yet).
A resource whose last automatic wake failed is marked with when the daemon
will try it again and a short form of the error. Until then a connection or
request is answered with an error instead of starting it again. The wait
starts at 30 seconds and doubles with each failure in a row, up to 15 minutes.
A successful wake resets it. hobby wake retries at once, and hobby logs
shows why the start failed.
blog primary postgres running port 15000 site app sleeping site.blog.example.com api app failed api.blog.example.com (wake refused, retry in 3m 12s: docker start failed)With --json, the same thing is the resource’s wakeRefusal field:
{ failures, retryAt, lastError }, or null.
Accepts --json.
hobby create <postgres|app|worker> <name> --project <p>
Section titled “hobby create <postgres|app|worker> <name> --project <p>”Creates a resource row with no code deployed to it, resting in the state
undeployed. Deliberately takes no --port: a port describes code that does
not exist yet.
Accepts --json.
hobby pg create --project <p> <name>
Section titled “hobby pg create --project <p> <name>”The explicit form, for creating a second database. An alias for
hobby create postgres, not a second implementation.
Accepts --json.
hobby branch <project> <name> [--allow-pause]
Section titled “hobby branch <project> <name> [--allow-pause]”Makes a new project, <name>, from a copy of <project>’s Postgres data, so
you can experiment against real data without risking it. On a filesystem with
reflinks (XFS, ZFS, APFS) the copy is instant; on ext4 it is a real copy, and
the command says so.
The branch gets its own container, port, network and data directory, and keeps
the source’s database user and password, which live inside the copied data. It
starts asleep, like hobby new, and is never pinned, whatever the source was.
An asleep source is copied as it is. An awake one is paused for the length of
the copy and then resumed, and the command prints how long the pause was. A
pinned source that is awake is refused unless you pass --allow-pause, because
pinning it said it must stay up. A project holding apps, workers or queues
cannot be branched yet. Remove a branch with hobby rm.
Accepts --json.
hobby rm <target> [--yes]
Section titled “hobby rm <target> [--yes]”Destroys a resource, or a project, with confirmation. --yes skips the prompt.
hobby deploy [path]
Section titled “hobby deploy [path]”Builds what is in the directory and serves it. The kind is detected from the
contents: a Dockerfile means an app, a wrangler.toml,
wrangler.jsonc or wrangler.json means a worker.
Both present is an error rather than a guess.
| Flag | What |
|---|---|
--project <p> |
Which project it belongs to |
--name <n> |
The resource name |
--port <n> |
The port your process listens on inside the container. Apps only |
--env-file <f> |
Variables from a dotenv file, replacing the app’s current set. Apps only |
--kind app|worker |
Only needed when the directory is ambiguous |
--json |
Machine-readable output |
Lifecycle
Section titled “Lifecycle”hobby connect <target>
Section titled “hobby connect <target>”Opens psql against a Postgres resource. The connection string never becomes an
argv element: credentials are passed through the child’s environment, so nothing
reading a process list can see the password.
--json prints connectionString and, when the daemon has a tailnet address,
tailnetConnectionString.
If psql is not on PATH, the connection string is printed instead.
hobby sleep <target>
Section titled “hobby sleep <target>”Stops the resource now, rather than waiting for it to go idle. Accepts --json.
hobby wake <target>
Section titled “hobby wake <target>”Starts it now. Rarely necessary: a resource wakes by itself when something
connects, which is the whole point. wake is for when you want it warm before
something else needs it, or when its last wake failed, you have fixed the cause,
and you do not want to wait for the daemon’s own retry (see hobby ls). Accepts
--json.
hobby pin <project>
Section titled “hobby pin <project>”Stops this project ever being put to sleep automatically. Useful for the one thing on the box that has to answer instantly, or anything whose first request genuinely cannot afford to wait.
Pinning is per project, not per box, so pinning your status page does not keep
nine other projects awake with it. Accepts --json.
hobby unpin <project> [--sleep-after <seconds>]
Section titled “hobby unpin <project> [--sleep-after <seconds>]”Lets it sleep again. Without the flag it goes back to the box-wide default.
If the box-wide default is itself “never sleep”, unpin refuses rather than
writing a value that would make it a no-op reporting success, and asks you for
an explicit number.
Accepts --json.
hobby logs <target> [--tail N]
Section titled “hobby logs <target> [--tail N]”Tails the resource’s logs.
Queues
Section titled “Queues”hobby queue ls [project]
Section titled “hobby queue ls [project]”Every queue, with depth and its bound consumer, or a note that it has none.
hobby queue create <name> --project <p>
Section titled “hobby queue create <name> --project <p>”A queue with no consumer bound yet. Bind one through a worker’s own
wrangler.toml.
hobby queue peek <target> [--limit n]
Section titled “hobby queue peek <target> [--limit n]”The oldest messages, without leasing them, so looking does not change what the consumer will receive.
hobby queue send <target> <json>
Section titled “hobby queue send <target> <json>”Enqueues one message. Works on Linux and macOS, unlike producing from inside a container.
hobby queue purge <target> [--yes]
Section titled “hobby queue purge <target> [--yes]”Deletes every message, with confirmation.
hobby queue rm <target> [--yes]
Section titled “hobby queue rm <target> [--yes]”Destroys the queue, with confirmation.
hobby queue set <target> --retention <seconds>
Section titled “hobby queue set <target> --retention <seconds>”Changes how long messages are kept. Note that retention never sweeps a queue with no drainable consumer.
Connecting from another machine
Section titled “Connecting from another machine”These are the commands that make the CLI work from your laptop against a box. See ADR 0018 for the reasoning, and the remote guide for a walkthrough.
hobby login <url>
Section titled “hobby login <url>”Exchanges the operator password for an API token and stores it. Every command afterwards runs against that box instead of the local socket.
hobby login https://your-box.tailnet.ts.netThe password is read without echoing and never becomes a command-line argument.
--name labels the token, so you can tell one machine’s from another’s when
revoking; it defaults to your hostname.
It refuses to send a token to a plain http:// origin unless it is loopback. A
token can create and destroy every database on the box, and that is not a thing
to send in cleartext.
hobby logout [url]
Section titled “hobby logout [url]”Forgets a remote on this machine. The token stays valid on the box until you
revoke it there with hobby token rm, and the output says so.
hobby remote ls
Section titled “hobby remote ls”Which boxes this machine is logged in to, with the current one marked.
Set HOBBY_REMOTE=<url> to aim a single command at a different one without
changing the default.
hobby link <project>
Section titled “hobby link <project>”Writes the project name into the hobby.json in the current directory, so
commands run from there default to it and stop needing --project.
cd ~/code/bloghobby link bloghobby create worker api # no --project neededAn explicit --project always wins over the linked one. The project must
already exist: link checks, rather than writing a file that points at nothing.
hobby token create <name>
Section titled “hobby token create <name>”Issues an API token. Local only, over the unix socket, so tokens are minted by someone with shell access to the box rather than by anyone already holding one. Shown once and never recoverable: only an argon2id hash is stored.
hobby token ls
Section titled “hobby token ls”Names, creation dates, and the last six characters of each token, which is enough to match one against a value a machine is holding without the file containing anything usable.
hobby token rm <name>
Section titled “hobby token rm <name>”Revokes it. Any machine holding it is locked out on its next request.
Updating
Section titled “Updating”hobby update
Section titled “hobby update”Moves the checkout to the newest release tag and rebuilds it by re-running
install.sh, which is the same path a fresh install takes.
hobby update # update to the newest taghobby update --check # print the newest tag and change nothingIt refuses if the checkout has tracked modifications rather than discarding
them. Untracked files do not block it, which matters because bun install
leaves a bun.lock in every built checkout. If the rebuild fails it says the
checkout has already moved, because believing you are still on the old version
would be worse than the failure. Restart the daemon afterwards.
Studio
Section titled “Studio”hobby studio
Section titled “hobby studio”Prints the Studio URL and opens it. Prints the public host instead of the loopback URL when one is configured through Caddy.
hobby studio passwd
Section titled “hobby studio passwd”Sets the operator password. The prompt never echoes.
Snapshots
Section titled “Snapshots”A snapshot is a copy of a whole project’s data, taken on this box. It is local: it does not survive losing the disk, and the command reminds you of that. There is no point-in-time recovery (see ADR 0016).
hobby snapshot <project> [--allow-pause]
Section titled “hobby snapshot <project> [--allow-pause]”Takes a snapshot. The project is held asleep for the length of the copy, so
nothing can wake it halfway through, and whatever was running is started again
afterwards. A pinned project that has something running is refused unless you
pass --allow-pause, because pinning it said it must stay up; if it holds only
Postgres, --online (below) snapshots it without stopping anything. Prints the
snapshot’s id.
Accepts --json.
hobby snapshot <project> --online
Section titled “hobby snapshot <project> --online”Takes a snapshot with no pause at all, for a project whose resources are all
Postgres; a project with an app, worker or queue is refused, because their
state has no online copy mechanism. A running database is copied with
Postgres’s own online backup, pg_basebackup, run inside its container, and a
sleeping one is copied as it rests. Nothing is stopped, so a pinned project
needs no --allow-pause, and the two flags cannot be combined.
A database restored from an online snapshot runs recovery on its first start, replaying the write-ahead log that came with the backup, before it accepts connections. Each database is consistent on its own; two databases in one project are copied one after the other rather than at one instant. There is still no point-in-time recovery.
Accepts --json.
hobby snapshot ls <project>
Section titled “hobby snapshot ls <project>”The project’s snapshots, newest first. Snapshots outlive their project, so this
still works after the project is deleted. Accepts --json.
hobby snapshot restore <project> <id> [--as <name> | --in-place] [--allow-pause] [--yes]
Section titled “hobby snapshot restore <project> <id> [--as <name> | --in-place] [--allow-pause] [--yes]”Without --in-place, restores into a new project, <project>-restored or the
name given with --as, and leaves the original untouched.
--in-place replaces the project’s own data and keeps its connection strings.
The snapshot is copied first while the project stays up, then the project is
stopped, the data swapped, and whatever was running started again. The old data
is kept until everything has come back, and its path is printed if anything did
not. It asks you to type the project name unless --yes is given, and it
refuses if the project’s resources have changed since the snapshot. It restores
data, not configuration.
Accepts --json.
hobby snapshot rm <project> <id> [--yes]
Section titled “hobby snapshot rm <project> <id> [--yes]”Deletes one snapshot, with confirmation. --yes skips the prompt.
Leaving
Section titled “Leaving”hobby eject <project> [--release]
Section titled “hobby eject <project> [--release]”Emits a docker-compose.yml plus the data. --release also stops Hobbyist
managing the project.
hobby adopt <project>
Section titled “hobby adopt <project>”Manages a released project again.
Exit codes
Section titled “Exit codes”| Code | Means |
|---|---|
0 |
Success |
2 |
Usage error: an unknown flag, a missing argument |
| non-zero | The operation failed, or the daemon could not be reached |
A daemon that cannot be reached says so and suggests hobby init then
hobby daemon, rather than failing with a bare status.