# Configuration

Shipyard reads one YAML file at startup — `.shipyard.yaml` in the working directory, or the path given with `-config`. Restart Shipyard after editing it by hand; projects added in the console are written to the file and take effect at once.

`-config` also accepts an HTTPS URL. Shipyard downloads the file at startup and treats it as read-only: the console cannot add projects to it. If the server needs a token, put it in `SHIPYARD_CONFIG_TOKEN`; it is sent as a bearer token.

```sh
SHIPYARD_CONFIG_TOKEN=… shipyard -config https://config.example.com/shipyard.yaml
```

Unknown keys, duplicate keys, a second YAML document and files over 1 MiB are rejected at startup. Errors name the offending line.

## Minimal file

```yaml
listen: 127.0.0.1:8080
projects:
  - id: my-app
    repository: you/my-app
    enabled: true
    issue_label: agent-ready
    release_interval: 48h
    content:
      interval: 168h
      publish_mode: pull_request
```

Everything else has a default.

## Server and access

| Key | Default | Meaning |
|---|---|---|
| `listen` | — | Address and port, e.g. `127.0.0.1:8080`. |
| `runtime.api_token_env` | — | Name of the environment variable holding the API token. |
| `runtime.embedded_worker` | `false` | Run the queue worker inside the server process. |

Without a token the console is read-only, and Shipyard only starts on a loopback address. With a token:

- queuing, cancelling and retrying runs require it;
- reading runs, logs, pipelines and `/metrics` requires it too.

If `api_token_env` names a variable that is not set, Shipyard refuses to start.

## Projects

```yaml
projects:
  - id: my-app
    repository: you/my-app
    enabled: true
    issue_label: agent-ready
    auto_merge: false
    release_interval: 48h
    models:
      verification: code
    content:
      enabled: true
      interval: 168h
      min_score: 70
      target: blog
      topics: [Go, static sites]
      publish_mode: pull_request
```

| Key | Meaning |
|---|---|
| `id` | Unique ID: letters, digits, `.`, `_`, `-`. |
| `repository` | `owner/name`. |
| `enabled` | Only enabled projects accept runs. |
| `issue_label` | Label that marks issues ready for automated work. |
| `auto_merge` | Must be `false`: every change goes through review. |
| `release_interval` | Release cadence as a duration, e.g. `48h`. |
| `models` | Per-project override of the AI role routing below. |
| `content.interval` | How often content is reviewed, e.g. `168h`. |
| `content.min_score` | Score from 0 to 100 a topic needs before an article is drafted. |
| `content.publish_mode` | `pull_request`: articles are proposed as pull requests. |
| `runs_on` | Labels such as `arch=arm64`; runs go to a ship that carries all of them. See [Ships](/ships/). |

## AI providers and models

```yaml
ai:
  providers:
    primary:
      type: openai-compatible
      base_url: https://api.openai.com/v1
      api_key_env: OPENAI_API_KEY
      timeout: 120s
    local:
      type: openai-compatible
      base_url: http://127.0.0.1:11434/v1
  models:
    code:
      provider: primary
      model: your-coding-model-id
      max_output_tokens: 8192
    researcher:
      provider: local
      model: your-local-model-id
  tasks:
    coding: code
    research: researcher
    writing: code
    verification: code
    release_notes: code
```

- **Providers** — `type: openai-compatible` works with OpenAI and with local servers that speak the same API (Ollama, vLLM, LM Studio). `base_url` defaults to OpenAI; it must be HTTPS, or HTTP on loopback. `timeout` defaults to `120s`.
- **Credentials** — set exactly one of `api_key_env` (environment variable), `api_key_file` (file such as `/run/secrets/openai_key`, up to 64 KiB) or `api_key` (inline; keep that file private). Local servers need none.
- **Models** — an alias names a provider and the provider's model ID. `max_output_tokens` defaults to 4096.
- **Roles** — `tasks` maps the roles `coding`, `research`, `writing`, `verification` and `release_notes` to model aliases. A project's `models` overrides them. A role without a model fails the run instead of silently using another one.

Keys are read when a run needs them, so the server starts without AI credentials.

## Storage, logs and retention

| Key | Default | Meaning |
|---|---|---|
| `storage.path` | `./data/shipyard.db` | SQLite database: queue, history, audit. |
| `logging.directory` | `./data/logs` | One JSONL log per run attempt. |
| `logging.max_run_size_mb` | `50` | Log size cap per run. |
| `retention.logs_days` | `30` | Delete run logs after this many days. |
| `retention.completed_runs_days` | `90` | Delete finished runs after this many days. |
| `retention.audit_days` | `365` | Delete audit entries and alert records after this many days. |
| `retention.max_disk_size_mb` | `2048` | When the log directory reaches this size, new runs are refused until retention frees space. |

Keep both paths on a local disk. `logs_days` cannot exceed `completed_runs_days`. Details in [Monitoring and retention](/monitoring/).

## Monitoring and alerts

| Key | Default | Meaning |
|---|---|---|
| `observability.metrics` | `false` | Serve Prometheus metrics on `/metrics`. |
| `observability.otel_endpoint` | — | OTLP/HTTP collector for traces, e.g. `http://127.0.0.1:4318`. |
| `prometheus.<name>.url` | — | Prometheus server for `prometheus` pipeline steps. |
| `prometheus.<name>.query` | — | The PromQL instant query the step runs. |
| `prometheus.<name>.token_env` | — | Optional bearer token variable for that server. |
| `alerts.token_env` | — | Token variable Alertmanager must send. |
| `alerts.project_label` | `shipyard_project` | Alert label that names the project. |
| `alerts.enqueue_diagnostics` | `false` | Queue a diagnostic run for each firing alert. |

## Fleet

| Key | Default | Meaning |
|---|---|---|
| `fleet.listen` | — | Address of the ship endpoint, e.g. `0.0.0.0:8443`. Unset means no ships. |
| `fleet.address` | — | `host:port` ships dial; used in join commands. |
| `fleet.hosts` | — | DNS names and IPs written into the endpoint's certificate. |
| `fleet.directory` | `<storage dir>/fleet` | Where the fleet certificate authority is kept. Back it up: losing it means re-joining every ship. |

The fleet requires `runtime.api_token_env`. Setup is described in [Ships and docks](/ships/).

## Pipelines

```yaml
pipelines:
  project-check:
    steps:
      - id: config
        type: diagnostic
        timeout: 30s
```

A pipeline has 1 to 20 steps with unique IDs. Step types and examples are in [Pipelines](/pipelines/).

## Secrets in logs

Configured API keys and tokens are masked in run logs and errors, together with text that looks like `token=…`, `password: …` or an `Authorization` header. Do not put other secrets into run input.
