# Deploying Shade Shade ships as a single Docker image that contains the prekey server, observer dashboard, OpenAPI contract, and stale cleanup. You deploy one container per project. ## Quick start ```bash docker run -d \ --name my-project-shade \ -v my-project-shade:/data \ -p 3900:3900 \ -e SHADE_OBSERVER_TOKEN=change-me-to-at-least-16-chars \ gt.zyon.no/stian/shade-prekey:latest ``` That's it. Your projects can now register identities and exchange prekey bundles via `http://localhost:3900`. ## Why one container per project Each project is self-contained. Nova doesn't depend on Orchestrator being up. Future projects can be added without touching existing ones. The container is tiny (~260 MB), idle resource usage is near zero, and each container owns its own SQLite volume. ``` Project A Project B Future projects ───────── ───────── ──────────────── app + frontend app + frontend app + frontend │ │ │ ↓ ↓ ↓ shade-a container shade-b container shade-n container (port 3900) (port 3901) (port 390n) sqlite volume sqlite volume sqlite volume ``` ## Dokploy deployment 1. Go to Dokploy → Projects → New Project → Docker Compose 2. Paste the `docker-compose.yml` from [`examples/05-dokploy-deployment`](../examples/05-dokploy-deployment/docker-compose.yml) 3. Set env vars in the Dokploy UI: - `SHADE_OBSERVER_TOKEN` (generate a random 32+ char string) 4. Set the container name unique per project (e.g., `nova-shade`, `orchestrator-shade`) 5. Deploy Dokploy will pull the image, create the volume, and health check the container automatically. ## Volumes and backup The `/data` volume holds: - `shade-prekeys.db` — the SQLite database with all identities, prekeys, and activity timestamps - WAL journal files for crash safety **Backup:** Copy the `.db` file while the container is stopped, or use SQLite's online backup API: ```bash docker exec my-project-shade sqlite3 /data/shade-prekeys.db ".backup /data/backup.db" docker cp my-project-shade:/data/backup.db ./local-backup.db ``` **Restore:** Stop the container, copy the `.db` file into the volume, restart. ## PostgreSQL instead of SQLite If you want to share a Postgres instance (or need HA), set `SHADE_PREKEY_PG_URL`: ```yaml environment: - SHADE_PREKEY_PG_URL=postgres://shade:shade@postgres:5432/shade ``` Tables will be created automatically with the `shade_server_*` prefix, so they coexist cleanly with any other tables in the same database. ## Environment variable reference | Var | Default | Description | |-----|---------|-------------| | `PORT` | `3900` | HTTP port | | `SHADE_PREKEY_DB_PATH` | `/data/shade-prekeys.db` | SQLite file location | | `SHADE_PREKEY_PG_URL` | unset | Postgres URL (overrides SQLite) | | `SHADE_OBSERVER_TOKEN` | unset | Enables dashboard at `/shade-observer/dashboard/`. Min 16 chars. | | `SHADE_STALE_DAYS` | `30` | Purge identities with no activity in N days | | `SHADE_CLEANUP_INTERVAL_HOURS` | `24` | Cleanup cycle interval | | `SHADE_LOG_LEVEL` | `info` | `debug` / `info` / `warn` / `error` | ## Health and observability - **Health:** `GET /health` — returns `{"status":"ok"}` when the storage backend is reachable. Docker's HEALTHCHECK uses this. - **Metrics:** `GET /metrics` — Prometheus format with counters, histograms, and gauges for all routes. - **OpenAPI:** `GET /openapi.yaml` — machine-readable API contract for any language. - **Redoc viewer:** `GET /docs` — human-readable API reference. - **Dashboard:** `GET /shade-observer/dashboard/` — live activity viewer (requires token). ## Stale cleanup Identities with no activity (no bundle fetches, no replenishments, no registration refreshes) for more than `SHADE_STALE_DAYS` days are automatically purged from the database. This keeps the database bounded without manual housekeeping. The cleanup task runs once at startup and then every `SHADE_CLEANUP_INTERVAL_HOURS` hours. Each cycle logs the number of purged identities. ## Multiple Shade instances on the same host Run multiple projects side-by-side with different container names and ports: ```yaml services: nova-shade: container_name: nova-shade image: gt.zyon.no/stian/shade-prekey:latest ports: ["3900:3900"] volumes: [nova-shade-data:/data] environment: - SHADE_OBSERVER_TOKEN=nova-token-32-chars-minimum-xxx orch-shade: container_name: orch-shade image: gt.zyon.no/stian/shade-prekey:latest ports: ["3901:3900"] volumes: [orch-shade-data:/data] environment: - SHADE_OBSERVER_TOKEN=orch-token-32-chars-minimum-xxx volumes: nova-shade-data: orch-shade-data: ``` ## CI publishing Tagged releases auto-publish to the Gitea container registry via `.gitea/workflows/docker.yml`. To cut a release: ```bash bun run version 1.0.1 git push --tags ``` ## Security notes - **Never commit `SHADE_OBSERVER_TOKEN`.** Use Dokploy secrets or environment-specific `.env` files. - The prekey server stores **public keys only**. No private keys ever touch it. - Rate limiting is on by default (5 registrations per hour per IP, etc.). Tune via `createPrekeyRoutes` options if embedding, or configure at reverse-proxy level for the container. - Put the container behind a reverse proxy (Traefik, Caddy) for TLS termination.