# CLI (/docs/reference/cli)



The `ductor` binary is a single executable with subcommands. With &#x2A;*no
arguments, it defaults to `serve`**; a flag-only invocation (`ductor --http-addr
:9000`) is also treated as `serve`.

```bash
ductor <command> [flags]
```

## Global behavior [#global-behavior]

* **`--config <path>`** — path to a config file. Accepted by every subcommand;
  also parsed from raw args (`--config path` or `--config=path`).
* Config precedence: env vars > inline YAML in `DUCTOR_CONFIG` > config file >
  defaults. See the [Configuration reference](/docs/reference/configuration).
* `-h` / `--help` / `help` prints usage; `-v` / `--version` prints the version.
* A `./.env` file is auto-loaded **only** when `DUCTOR_ENVIRONMENT=development`
  or `DUCTOR_LOAD_DOTENV=true`; otherwise it is ignored. Don't rely on it in
  production — set env vars directly.

## Command overview [#command-overview]

| Command           | Purpose                                                                         |
| ----------------- | ------------------------------------------------------------------------------- |
| `serve` (default) | Boot the full runtime via fx; optional startup auto-migrate; graceful shutdown  |
| `dev`             | Boot with embedded miniredis (Postgres still required) and development defaults |
| `migrate`         | Run database migrations — `up` / `down` / `status`                              |
| `schema`          | `schema status` — verify the database schema is ready                           |
| `health`          | HTTP health check against a running server                                      |
| `validate-config` | Validate resolved config and print a value/source table                         |
| `version`         | Print `version` and `build_time`                                                |
| `seed-demo`       | Seed demo tenants                                                               |
| `workflow`        | `validate` / `export` / `bootstrap-routing` for workflow definitions            |
| `pipeline-mode`   | `pipeline-mode set` — flip a pool/tenant pipeline mode                          |
| `audit-shadow`    | Audit shadow-mode partition drift                                               |
| `migrate-shadow`  | Drain/migrate shadow-partition items                                            |
| `scim-token`      | `scim-token mint` — mint a SCIM bearer token                                    |
| `saml-idp`        | `saml-idp set/get/disable` — manage a tenant's SAML IdP config                  |

## `serve` [#serve]

Boots every subsystem through the fx composition root, honoring
`server.role` (`all`/`api`/`worker`/`executor`). If `database.auto_migrate` is
true it migrates first; regardless, it logs a warning at startup if pending
migrations exist. Shuts down gracefully on SIGINT/SIGTERM.

```bash
ductor serve --http-addr :8080 --metrics-addr :9090
```

Every `serve` flag binds to a config key, so a flag is just an inline override of
the corresponding `DUCTOR_*` variable. The most useful:

| Flag                  | Config key                | Notes                                               |
| --------------------- | ------------------------- | --------------------------------------------------- |
| `--log-level`         | `log.level`               | `debug`/`info`/`warn`/`error`                       |
| `--log-format`        | `log.format`              | `json`/`text`                                       |
| `--http-addr`         | `server.http_addr`        | HTTP (REST + Connect) listener                      |
| `--metrics-addr`      | `server.metrics_addr`     | Prometheus listener                                 |
| `--database-url`      | `database.url`            | Postgres DSN                                        |
| `--cache-url`         | `cache.url`               | Redis/Dragonfly URL                                 |
| `--auth-enabled`      | `auth.enabled`            | Toggle authentication                               |
| `--api-enabled`       | `api.enabled`             | Toggle the Connect/gRPC listener                    |
| `--api-addr`          | `api.addr`                | Connect/gRPC address                                |
| `--api-oidc-issuer`   | `api.oidc_issuer`         | OIDC issuer                                         |
| `--api-oidc-audience` | `api.oidc_audience`       | OIDC audience                                       |
| `--workflows-dir`     | `workflows.dir`           | Dir scanned for workflow-definition YAML at startup |
| `--routing-file`      | `routing.definition_file` | Override embedded routing topology                  |
| `--role`              | `server.role`             | Comma role set                                      |

Additional flag groups: archival (`--archival-enabled`, `--archival-backend`,
`--archival-s3-bucket`, …), tracing (`--tracing-enabled`, `--tracing-endpoint`,
`--tracing-grpc`, `--tracing-insecure`, `--tracing-sample-rate`), rules/events
(`--hot-reload`, `--events-enabled`, `--rule-cache-size`, `--rule-cache-ttl`),
and the executor plane (`--exec-addr`, `--exec-target`, `--exec-shared-secret`,
`--exec-runner-kind`, `--exec-isolation-required`, `--exec-task-timeout`).

## `dev` [#dev]

Boots a development instance with an **in-process** Redis (miniredis) so you only
need Postgres. It forces development defaults: `auth.enabled=false`,
`auth.allow_anonymous=true`, `archival.enabled=false`, `tracing.enabled=false`,
`database.auto_migrate=true`, `environment=development`.

```bash
export DUCTOR_DATABASE_URL="postgres://ductor:ductor@localhost:5432/ductor?sslmode=disable"
ductor dev
```

Flags: `--log-level` (default `debug`), `--log-format`, `--http-addr`,
`--metrics-addr`, `--database-url`, `--api-addr`.

## `migrate` [#migrate]

Runs goose migrations. Positional subcommand, no flags:

```bash
ductor migrate up       # apply all pending (default)
ductor migrate status   # show applied/pending
ductor migrate down     # roll back one
```

Uses `database.url` / `DUCTOR_DATABASE_URL`. See
[Database migrations](/docs/operations/migrations).

## `schema status` [#schema-status]

Verifies the database schema is ready (all migrations applied) and prints
`schema=ready`. Useful as a readiness gate in CI/CD before rolling a new version.

```bash
ductor schema status --database-url "$DUCTOR_DATABASE_URL" --timeout 15s
```

| Flag             | Default        | Notes                               |
| ---------------- | -------------- | ----------------------------------- |
| `--database-url` | `database.url` | Falls back to `DUCTOR_DATABASE_URL` |
| `--timeout`      | `15s`          | Schema-check timeout                |

## `health` [#health]

Performs an HTTP `GET /health` against a running server and prints `OK` (or the
error). Defaults the address to `:8080`.

```bash
ductor health --http-addr :8080
```

## `validate-config` [#validate-config]

Validates the fully-resolved configuration and prints a table of each key, its
resolved value, and its source (`[env: …]`, `[config: …]`, or `[default]`), with
DSNs redacted.

```bash
ductor validate-config
```

## `workflow` [#workflow]

Offline and management operations on workflow definitions:

```bash
ductor workflow validate ./definition.yaml      # offline lint; non-zero on any issue
ductor workflow export <family-slug>             # write latest published YAML to stdout
ductor workflow bootstrap-routing [--verify] [--repair]
```

`validate` resolves every action, strategy, hook, and interface reference without
touching the database. `bootstrap-routing` defaults to `--verify` when neither
flag is given.

## Operational subcommands [#operational-subcommands]

| Command                    | Key flags                                                                         | What it does                                                                                        |
| -------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `seed-demo`                | `--tenant`, `--reset`, `--no-migrate`, `--database-url`                           | Seed demo tenants (also seeds an `acme` tenant)                                                     |
| `pipeline-mode set`        | `--pool` \| `--tenant`, `--mode` (`inherit`/`linear`/`shadow`/`dag`), `--timeout` | Flip a pool's or tenant's pipeline mode + fan out                                                   |
| `audit-shadow`             | `--drift-threshold`, `--max-partitions`, `--fail-on-drift`                        | Report shadow-partition drift                                                                       |
| `migrate-shadow`           | `--dry-run`, `--max-items-per-pool`                                               | Drain shadow-partition items                                                                        |
| `scim-token mint`          | `--tenant`, `--name`                                                              | Mint a SCIM bearer token. The plaintext token is printed **once** — it is not recoverable afterward |
| `saml-idp set/get/disable` | `--tenant`, `--metadata-file`, `--attribute-mapping`, `--allow-idp-initiated`     | Manage a tenant's SAML IdP config. `get` truncates `metadata_xml`; `disable` flips `enabled=false`  |

## Exit codes [#exit-codes]

Subcommands exit non-zero on failure — `workflow validate` on any validation
issue, `schema status` when the schema isn't ready, `audit-shadow --fail-on-drift`
when drift is detected, and `serve` when a required dependency is unreachable.
This makes them safe to wire into CI/CD gates.
