Skip to content

Docker

Prefer containers? The server ships a compose file. The script installer (Set up your box) is the recommended path because it also handles Tailscale; the container path leaves networking to you.

The shipped file, verbatim:

services:
gray:
build: .
image: gray-selfhost:latest
container_name: gray
restart: unless-stopped
env_file: .env
ports:
# Loopback by default — safe. Front it with your own TLS + auth before
# exposing it. Only bind 0.0.0.0 if something in front authenticates.
- "127.0.0.1:8848:8848"
volumes:
# Brain working dir (/data/workspace) + connections vault (/data/vault).
- gray-data:/data
# To let the brain work on data you ALREADY have, mount it read-only and
# point GRAY_BRAIN_CWD / GRAY_FILE_ROOT at the mount in .env, e.g.:
# - /srv/datasets:/data/workspace:ro
volumes:
gray-data:
Terminal window
cp .env.example .env # fill it in
docker compose up -d
docker compose logs -f gray

The container binds to loopback on purpose — don’t expose 8848 publicly. Publish it inside your tailnet from the host:

Terminal window
tailscale serve --yes --bg --https=8443 http://127.0.0.1:8848

…or front it with your own reverse proxy/tunnel. The app then connects to the tailnet HTTPS address. See Tailscale.

Configuration is the same .env as the script install — see Configuration reference. The gray-data volume holds the workspace and the credentials vault; treat it like the vault directory in Data & backups.

  • The box code prints to the container log (the log line says claim code): docker compose logs -f gray.
  • The box’s HTTPS origin needs configuring manually in .env: set GRAY_RP_ID=<hostname>.<tailnet>.ts.net and GRAY_RP_ORIGIN=https://<that hostname>. The script installer sets these for you; the container path doesn’t, and the server warns at startup while GRAY_RP_ID is still localhost.