Skip to content

CLI reference

The docket binary exposes six subcommands. Each one accepts a global --config (-c) flag pointing at a TOML config file; if omitted, Docket looks at ./docket.toml, then $XDG_CONFIG_HOME/docket/docket.toml, then /etc/docket/docket.toml. Environment variables prefixed with DOCKET_ override file values.

Terminal window
docket [--config <path>] <command> [flags]

Start the HTTP server.

Terminal window
docket serve

Listens on [server.host]:[server.port] (default 0.0.0.0:8080). Performs a graceful shutdown when it receives SIGTERM or SIGINT, draining in-flight requests for up to [server.shutdown_timeout] (default 30s). The /readyz endpoint only reports ready after startup gates have all passed.

On startup, serve applies any pending goose migrations under a Postgres advisory lock (so N replicas booting concurrently can safely race — only one wins each migration). Set [database].auto_migrate = false to disable startup migration; serve will then refuse to start if any migration is pending, on the assumption an operator runs docket migrate up as a separate pre-deploy step.

serve also holds a shared advisory lock for its lifetime so destructive maintenance commands (rebuild-tickets) can detect that it’s running and refuse to corrupt live data.

Manage database migrations. Has three subcommands.

Terminal window
docket migrate up # apply all pending migrations
docket migrate down # roll back the most recent migration
docket migrate status # show applied + pending migrations

Migrations are embedded in the binary via go:embed, so they’re versioned with each release. By default serve runs migrate up for you on startup; in scaled deployments where you’d rather a single pre-deploy step apply migrations and have every pod just verify them, set [database].auto_migrate = false and call docket migrate up from your release pipeline before rolling out new pods.

Create the initial admin user. Required on first install before anyone can sign in — though a self-hoster running under Docker, Compose, or Kubernetes can skip this step entirely by setting auth.bootstrap_admin_email and auth.bootstrap_admin_password (and optionally auth.bootstrap_admin_name) and letting serve create the admin on first boot. See the env-bootstrap section below.

Terminal window
docket bootstrap-admin --email <email> --name "<display name>"
FlagRequiredDescription
--emailyesEmail address used for login
--nameyesDisplay name

The command:

  1. Runs any pending migrations.
  2. Prompts for a password (minimum 8 characters; input is not echoed).
  3. Creates the user with local-auth credentials.
  4. Assigns the user to the built-in Administrators group.

Idempotent on email — safe to re-run if it failed midway.

When both DOCKET_AUTH__BOOTSTRAP_ADMIN_EMAIL and DOCKET_AUTH__BOOTSTRAP_ADMIN_PASSWORD are set, serve ensures the admin exists during startup (after migrations). The flow is idempotent — booting N replicas with the same env, or rebooting a single pod, never duplicates the user or group membership. Removes the need to docker exec into a container.

Terminal window
DOCKET_AUTH__BOOTSTRAP_ADMIN_EMAIL=admin@example.com \
DOCKET_AUTH__BOOTSTRAP_ADMIN_PASSWORD='change-me-at-least-8-chars' \
DOCKET_AUTH__BOOTSTRAP_ADMIN_NAME='Admin' \
docket serve

Rebuild the tickets projection table from scratch by replaying every row in the ticket_events spine. Destructive — drops FK constraints and truncates tickets + ticket_tags before replaying.

Terminal window
docket rebuild-tickets --yes
FlagRequiredDescription
--yesyesConfirm the destructive rebuild (no interactive prompt)

This is a disaster-recovery tool. Because the event spine is the source of truth (see Architecture), the projection can always be regenerated. Use this after restoring from a backup, or if a projection bug is suspected.

rebuild-tickets tries to take an exclusive advisory lock on the docket-serve lock ID and refuses to run if any docket serve is alive against the same database — stop serve first. Without the --yes flag it exits without touching the database, so an accidental invocation cannot corrupt data.

Inspect the embedding migration pipeline (active model, target model, pending backfill counts).

Terminal window
docket embedding status

Prints JSON describing the current embedding_state row plus how many published articles and resolved tickets still lack an embedding under the target model:

{
"status": "migrating",
"active": { "Provider": "openai", "Model": "text-embedding-3-small", "Dim": 1536 },
"target": { "Provider": "openai", "Model": "text-embedding-3-large", "Dim": 3072 },
"articles_pending": 4322,
"tickets_pending": 11801
}

status is stable (no migration in flight) or migrating. When migrating, watch the pending counts trend to zero — the periodic DBOS finalizer flips state back to stable once both reach zero. The same data is exposed at GET /api/v1/admin/embedding/status for admin UIs.

A change to [ai.embedding].provider or [ai.embedding].model automatically triggers a migration on the next serve boot by default. Set [ai.embedding].auto_migrate = false to stay on the active model after a config change — useful in production where re-embedding a large corpus is expensive and you want to schedule it during a maintenance window.

Verify that a new release is safe to deploy by counting orphaned AI agent runs.

Terminal window
docket check-deploy [--max-orphaned-runs <n>]
FlagDefaultDescription
--max-orphaned-runs0Maximum allowed orphaned runs before exit 1

An AI agent run is “orphaned” if it is still running or paused and its pinned spec_hash (agent_runs.spec_hash) isn’t in the active spec registry. Exits non-zero if the count exceeds the threshold.

The CLI loads the active registry from the agent_specs table, the same source the running server uses, so the hash comparison is symmetric.

It also warns, without failing, about in-flight AI runs and AI episodes recorded under an older workflow step layout (the new release hands them to staff as superseded_by_deploy) and about in-flight workflows recorded under a workflow-compatibility version other than the new release’s, which it will never run. Every release shares that version, so the second count is zero unless a release deliberately changed it. Workflows a release from before the shared version left in flight are taken over automatically at startup.

Wire this into your deploy pipeline as a pre-flight check. If it fails, either wait for in-flight runs to finish, or explicitly accept the orphan budget by bumping --max-orphaned-runs.

Self-hosters who don’t run a pipeline get a softer version of the same signal for free: serve runs the orphan scan once during startup (after DBOS is up) and logs a warning per offending hash. Readiness is unaffected.