---
title: Reference Deployment
description: Deploy the Gateway with SQLite, scoped bearer auth, and a reverse proxy.
---

The reference deployment runs one Gateway process, one SQLite database file, and a TLS reverse proxy. Use it when you are self-hosting without the managed control plane.

For multi-tenant hosted deployments, pair the Gateway with the [Control Plane](/deployment/control-plane) package for orgs, projects, teams, billing account records, usage events, secret references, and audit export.

## Docker Compose

From the repo root:

```bash
docker compose -f deploy/reference/docker-compose.yml up
```

The Gateway listens on `7331` inside the compose network and Caddy exposes HTTP/TLS. The compose file mounts a SQLite volume at `/data` and reads short-lived bearer grants from `/data/tokens.json`. If `/data/tokens.json` does not exist, the Gateway starts with an empty in-memory token set and denies token auth until you mount or write a token store.

Issue a local token grant. By default, the command returns a scoped action handle and writes the bearer-keyed grant to the local token store without printing the bearer token. Use `--reveal-token` only for manual operator workflows that need to copy the raw bearer.

```bash
bunx smithers-orchestrator token issue --scopes run:read,run:write,approval:submit --ttl 1h --action-id gateway
```

Copy the resulting grant store entry into the token store used by the deployment. Revoking the bearer token also revokes its action handles and records the revocation in the audit trail:

```bash
bunx smithers-orchestrator token revoke <token>
```

Automation can hand a broker handle to an action without exposing the bearer in model-visible context by resolving it locally and injecting the bearer only into the child process environment:

```bash
bunx smithers-orchestrator token exec --handle <action-handle> --action-id gateway --scopes run:read --command 'curl -H "Authorization: Bearer $SMITHERS_API_KEY" http://localhost:7331/v1/runs'
```

## Single Host

Use `deploy/reference/systemd/smithers-gateway.service` for the Gateway process and `deploy/reference/systemd/smithers-caddy.service` for Caddy. Copy `smithers-gateway.env.example` to `/etc/smithers/gateway.env`, then set:

```bash
SMITHERS_DB_PATH=/var/lib/smithers/smithers.db
SMITHERS_TOKEN_STORE=/etc/smithers/tokens.json
SMITHERS_GATEWAY_MODULE=/etc/smithers/gateway.mjs
SMITHERS_GATEWAY_HEADERS_TIMEOUT_MS=30000
SMITHERS_GATEWAY_REQUEST_TIMEOUT_MS=60000
```

`SMITHERS_GATEWAY_MODULE` can export `register(gateway)` or a default function. Register workflows there.

## Gateway Environment

| Variable | Default | Purpose |
| -------- | ------- | ------- |
| `PORT` | `7331` | Gateway listen port inside the container or host. |
| `SMITHERS_DB_PATH` | `/data/smithers.db` | SQLite database path made available to the gateway module for workflow storage. |
| `SMITHERS_TOKEN_STORE` | `/data/tokens.json` | JSON token grant store used by token auth. |
| `SMITHERS_GATEWAY_MODULE` | `/workspace/gateway.mjs` | Module that registers workflows on startup. |
| `SMITHERS_GATEWAY_HEARTBEAT_MS` | `15000` | WebSocket heartbeat interval. |
| `SMITHERS_GATEWAY_EVENT_WINDOW` | `10000` | Per-run replay window size (number of events) for `streamRunEvents`. |
| `SMITHERS_GATEWAY_HEADERS_TIMEOUT_MS` | `30000` | Maximum time to receive complete HTTP headers. |
| `SMITHERS_GATEWAY_REQUEST_TIMEOUT_MS` | `60000` | Maximum time to receive and parse a complete HTTP request, including body. |

## Kubernetes

The minimal manifests in `deploy/reference/k8s/` create:

- a `smithers` namespace
- a single Gateway `Deployment`
- a `ConfigMap` (`smithers-gateway-config`) supplying Gateway environment variables
- a SQLite `PersistentVolumeClaim`
- a token `Secret`
- a `Service` and `Ingress`

Edit `deploy/reference/k8s/secret.example.yaml` to set the bearer token and update the host in `deploy/reference/k8s/ingress.yaml` before applying:

```bash
kubectl apply -f deploy/reference/k8s/
```

## Event Stream Reconnection

Pass `afterSeq` with the last seen sequence number when reconnecting to a stream. The Gateway then:

- Replays any missed events still within the bounded per-run window.
- Emits a `GapResync` frame (fields: `fromSeq`, `toSeq`, run snapshot) if the window has truncated, then resumes with available events.
- Sends `Heartbeat` frames on a separate interval; these do not carry run events.

All HTTP responses include `X-Smithers-API-Version: v1`.
