# Docker Deployment Run the OpenSquilla gateway as a container on a machine that stays on — a home server, a NAS, or a small VPS. This page uses Debian 12 as the worked example, but any host with Docker Engine works the same way, on both `amd64` and `arm64`. Docker is the right install path when: - the host has no Python 3.12+ toolchain (or you do not want one), - you want the gateway to survive reboots and upgrade by pulling a new image, - you deploy on a NAS or headless server and use the Web UI from another device. For a desktop machine, the [quickstart](quickstart.md) installs are simpler. ## Prerequisites Install Docker Engine with the Compose plugin. On Debian 12, follow the [official Docker instructions](https://docs.docker.com/engine/install/debian/), then verify: ```sh docker --version docker compose version ``` Nothing else is required on the host for the prebuilt-image path — no Python, no Git, no build tools. ## Quick Start with the Prebuilt Image Prebuilt multi-arch images are published to [`ghcr.io/opensquilla/opensquilla`](https://github.com/opensquilla/opensquilla/pkgs/container/opensquilla) for each release tag. The immutable `v0.5.0rc3` tag identifies Preview 3, while `latest` follows the most recently pushed release tag, including previews and backports. If a backport moves `latest`, the newest release workflow is rerun to restore the intended ordering. If the release you want predates image publishing, use [Build the Image Yourself](#build-the-image-yourself) instead. A pull that fails with `denied` or `manifest unknown` means the image for that tag has not been published (or the package is not public yet) — check the package page for available tags, or build from source. Create a directory for the deployment and write this `compose.yaml`: ```yaml services: gateway: # Pin v0.5.0rc3 for reproducibility; latest follows the most recent tag push. image: ghcr.io/opensquilla/opensquilla:latest environment: # In-container bind. Keep it 0.0.0.0 — what the network can reach is # decided by `ports` below, not by this value. OPENSQUILLA_LISTEN: "0.0.0.0" # Token auth is required to administer a containerized gateway through # the Web UI, even from the same host. Generate a token with: # openssl rand -hex 32 OPENSQUILLA_AUTH_MODE: token OPENSQUILLA_AUTH_TOKEN: ${OPENSQUILLA_AUTH_TOKEN:?generate one with openssl rand -hex 32} OPENROUTER_API_KEY: ${OPENROUTER_API_KEY:-} TZ: ${TZ:-UTC} volumes: # All state — config, session DBs, memory, logs, workspace — lives under # /var/lib/opensquilla. The named volume makes it survive recreates. - opensquilla-state:/var/lib/opensquilla ports: # Loopback-only: reachable from this host, invisible to the network. # For NAS/LAN access see "Reach the Web UI from Your LAN" below. - "127.0.0.1:18791:18791" restart: unless-stopped volumes: opensquilla-state: ``` Put the two secrets in a `.env` file next to `compose.yaml` (Compose reads it automatically; keep it out of version control and private: `chmod 600 .env`): ```sh OPENSQUILLA_AUTH_TOKEN= OPENROUTER_API_KEY= ``` Start it: ```sh docker compose up -d docker compose logs -f gateway ``` Then open the Web UI with the token in the URL: ```text http://127.0.0.1:18791/control/?token= ``` The token is consumed once and stored for the browser session. The first request also writes the token into the gateway access log, so treat `docker compose logs` output as sensitive — or open `/control/` without the query parameter and paste the token into the connection panel instead. From there, finish provider onboarding and configuration in the Web UI — provider changes apply immediately and persist in the state volume. Why token auth is not optional here: the container binds a wildcard address, so the gateway treats every browser — including one on the same host — as a remote operator. Remote operators without a token can chat but cannot administer configuration or onboarding (only a small allowlist of safe runtime toggles stays writable). With `OPENSQUILLA_AUTH_MODE=token` the token grants the operator scopes that Web UI administration needs. Use `token` mode specifically; `password` and `trusted-proxy` modes do not support the Web UI connection. ## Reach the Web UI from Your LAN On a headless NAS you will use the Web UI from another device. Two rules: 1. Publish the port on all interfaces by changing the `ports` entry — do **not** change `OPENSQUILLA_LISTEN`: ```yaml ports: - "18791:18791" ``` 2. Keep token auth configured (already true if you followed the quick start). The gateway warns, but does not refuse, when it is network-reachable — exposure is your call, auth is not. Recreate the container (`docker compose up -d`) and open `http://:18791/control/?token=` from your device. If the host runs a firewall, allow inbound TCP 18791 from your LAN only. LAN traffic to the gateway is plain HTTP, so the token is visible to anyone who can observe that network — if your LAN is not fully trusted, put the gateway behind a TLS reverse proxy or use the VPN option below. Do not forward the gateway port to the internet. For remote access away from home, use a VPN (WireGuard, Tailscale) or a reverse proxy with TLS and its own authentication in front. See the safety defaults in [`gateway.md`](gateway.md). ## Keep State on Your Own Storage (Bind Mount) The named volume is the safest default. If you prefer a directory you manage (RAID storage, backup tooling), bind-mount it — but the container runs as non-root UID 10001, so give it ownership first or the gateway fails at boot: ```sh sudo mkdir -p /srv/opensquilla sudo chown -R 10001:10001 /srv/opensquilla ``` ```yaml volumes: - /srv/opensquilla:/var/lib/opensquilla ``` Everything worth backing up is under that one directory: `config.toml`, `state/` (session and scheduler databases), `logs/`, `workspace/`, `media/`, and an optional `.env`. ## Configure Providers and Secrets Three ways, in order of preference: 1. **Web UI** — provider onboarding and most config changes at `/control/` hot-apply and persist to `config.toml` in the state volume. Channel, memory-embedding, and sandbox-posture changes need a restart — the Web UI marks these, and `docker compose restart gateway` applies them. 2. **Compose `environment`** — pass provider keys by env-var name, as in the quick start. Environment values always win over `.env` files. 3. **A `.env` inside the state volume** — the gateway loads `/var/lib/opensquilla/.env` at startup, so keys survive image upgrades without appearing in `compose.yaml`. On a bind mount, keep it owned by the container user and private: `chown 10001:10001 .env && chmod 600 .env`. Caveat: a key listed under `environment:` in `compose.yaml` shadows the state-volume `.env` even when the host variable is unset (Compose passes an empty value through) — remove it from `environment:` if you manage it in the state volume. One precedence caveat for auth: values saved to `config.toml` — for example by the Web UI — take precedence over environment variables at boot. If the `OPENSQUILLA_AUTH_*` variables stop taking effect after configuring through the Web UI, `config.toml` now owns the `[auth]` settings; rotate the token there (or in the Web UI) and restart. Hand-edits to `/var/lib/opensquilla/config.toml` are read at boot only — restart to apply them: ```sh docker compose restart gateway ``` ## Change the Published Port Change the host side of the mapping and keep the container side at 18791: ```yaml ports: - "127.0.0.1:8080:18791" ``` Setting `OPENSQUILLA_GATEWAY_PORT` does **not** change the listen port of the container entrypoint — the port lives in the mapping above. ## Health and CLI Access `/healthz` answers liveness without auth, and `/readyz` returns 503 until the gateway is fully ready. The image ships a healthcheck; inspect it with: ```sh docker inspect --format '{{.State.Health.Status}}' $(docker compose ps -q gateway) ``` The full CLI is available inside the container: ```sh docker compose exec gateway opensquilla doctor docker compose exec gateway opensquilla gateway status ``` ## Upgrade and Roll Back State lives in the volume, so containers are disposable: ```sh docker compose pull docker compose up -d ``` To roll back, pin the previous release tag in `image:` and `docker compose up -d` again. Pinned tags plus a state backup make both directions routine. ## Build the Image Yourself The source checkout ships the same `Dockerfile` and a `compose.yaml` that defaults to a self-built `opensquilla:local` image (override with `OPENSQUILLA_GATEWAY_IMAGE` to use the GHCR image instead). Building needs `git`, `git-lfs`, and the Git LFS router assets: ```sh sudo apt install -y git git-lfs git clone https://github.com/opensquilla/opensquilla.git cd opensquilla git lfs pull --include="src/opensquilla/squilla_router/models/**" docker build -t opensquilla:local . docker compose up -d ``` On a low-power or `arm64` NAS this build is slow; prefer the prebuilt image and keep source builds for development machines. ## If Something Fails - `docker compose logs gateway` shows boot errors, including an unwritable state directory (fix ownership as above). - `docker compose exec gateway opensquilla doctor` reports readiness and recovery steps. - The Docker sections in [`troubleshooting.md`](troubleshooting.md) cover the common failures: unreachable Web UI, rejected configuration changes, bind-mount permissions, and LFS-related build errors. --- [Docs index](README.md) · [Product guide](../README.product.md) · [Improve this page](contributing-docs.md) · [Report a docs issue](https://github.com/opensquilla/opensquilla/issues/new?template=docs_report.yml)