Skip to content

Configuration and paths

works

Config values come from, in decreasing priority:

  1. Command-line flags.
  2. HOBBY_* environment variables.
  3. A hobby.json found by walking up from the current directory.
  4. Defaults.

Paths are separate and simpler: they always come from $HOBBY_HOME, or ~/.hobby if that is unset.

Key Env Default What
image postgres:18-alpine The Postgres image
proxyPort HOBBY_PROXY_PORT 5432 Where the wire-protocol proxy listens
proxyHost HOBBY_PROXY_HOST 127.0.0.1 Which addresses it binds. An address, "tailnet", or "all". See below
proxyTls HOBBY_PROXY_TLS_CERT, HOBBY_PROXY_TLS_KEY, HOBBY_PROXY_TLS_HOSTNAME null TLS for the proxy: { certFile, keyFile, hostname }. See below
project null Which project this directory belongs to. Written by hobby link
studioPort HOBBY_STUDIO_PORT 8443 Nothing listens on this. Studio is served by the daemon on apiPort. The value is only used by a preflight port check
apiPort 7432 The daemon API, loopback only
httpPort 7433 The HTTP wake router
queuePort 7434 The queue endpoint
domain localhost The base domain apps and workers get hostnames under
sleepAfterSeconds 300 Idle time before a resource sleeps
wakeTimeoutMs 30000 How long a wake may take before it is a failure
readinessPollMs 25 How often readiness is polled during a wake
caddyEnabled false Whether the Caddy front door runs at all
caddyAdminPort 2019 Caddy’s admin API, loopback only
caddyStudioHost null The public hostname for Studio. null publishes no Studio route

A minimal hobby.json:

{
"sleepAfterSeconds": 900,
"caddyEnabled": true,
"domain": "example.com"
}

proxyHost decides what the Postgres proxy binds, and it defaults to loopback.

Value Binds
"127.0.0.1" (default) Loopback only. Nothing off the box can connect
"tailnet" Loopback and this machine’s Tailscale address
"all" Every interface
any address That address literally

"tailnet" is the recommended setting for a box you want to reach from your laptop. It binds loopback as well, because hobby connect builds its string against 127.0.0.1 and would otherwise stop working on the box itself.

From an app host that is not on your tailnet

Section titled “From an app host that is not on your tailnet”

Give the proxy a certificate. With proxyTls set, it answers an SSLRequest with S, terminates TLS itself, and refuses a plaintext connection from any other machine with FATAL 28000. Loopback stays plaintext. That is what makes "all" safe.

On the box, with a DNS name pointing at it (DNS only, not proxied: Cloudflare’s proxy cannot carry the Postgres protocol) and port 80 free:

Terminal window
certbot certonly --standalone -d db.example.com

Then set all three, for example in a systemd drop-in, since the daemon does not read /root/hobby.json when started by systemd:

/etc/systemd/system/hobby.service.d/tls.conf
[Service]
Environment=HOBBY_PROXY_HOST=all
Environment=HOBBY_PROXY_TLS_CERT=/etc/letsencrypt/live/db.example.com/fullchain.pem
Environment=HOBBY_PROXY_TLS_KEY=/etc/letsencrypt/live/db.example.com/privkey.pem
Environment=HOBBY_PROXY_TLS_HOSTNAME=db.example.com

hobby new then prints a third line, public:, a connection string for that hostname ending in ?sslmode=require. The certificate files are re-read when they change, so certbot’s renewals need no restart. All three settings or none: a partial set stops the daemon from starting. ADR 0019.

~/.hobby/
state.db the daemon's record of everything
hobby.sock the unix socket for the CLI and MCP
hobby.json NOT read. See the note below
projects/
<project>/
<resource>/
pgdata/18/docker postgres: a plain PGDATA
bundle/ worker: the built script and its manifest
state/ worker: KV, R2, D1, cache
do/ worker: Durable Object sqlite
queue/messages.sqlite queue: the messages

An app has no directory: it is stateless by design.

$HOBBY_HOME moves all of it. That is the supported way to keep state on a different disk.

Postgres 18’s official image refuses to start when a bind mount lands directly on what used to be PGDATA. The mount point is therefore the postgres home directory, and the image’s entrypoint places the real data directory in a subdirectory named for the major version. resolvePgdataPath in packages/core/src/config.ts is the one place that is written down; everything needing the true on-disk path derives it from there rather than hardcoding the subdirectory.

Resources that need a directly reachable host port are allocated one from 15000 to 19999. That is the port hobby ls prints. It is not the port you normally dial: for Postgres that is the proxy on 5432, which is what makes wake work.

Read by the bootstrap script, not by the daemon:

Variable Default What
HOBBY_SRC_DIR ~/.hobby/src Where the checkout lives
HOBBY_REPO_REF main Branch or tag to install
HOBBY_REPO_URL the canonical repository Where to clone from
HOBBY_BIN_DIR /usr/local/bin or ~/.local/bin Where the launcher goes