Install the panel
The panel is one container plus PostgreSQL. One compose file runs both, from the published images; your reverse proxy puts HTTPS in front.
Install
Section titled “Install”You need a machine with Docker and the Compose plugin, a domain pointing to it (e.g. panel.example.com) and a reverse proxy (examples below).
-
Copy
compose.ymland.env.examplefrom thedeploy/folder of the panel’s repository into an empty folder, e.g./opt/panel. -
Create
.envand set the two required values:Terminal window cp .env.example .env# POSTGRES_PASSWORD=<a long random password, e.g. from: openssl rand -hex 24># PANEL_PUBLIC_URL=https://panel.example.com -
Start it:
Terminal window docker compose up -dThe first start creates the master key in
keys/master.keyand the database. -
Point your reverse proxy at
127.0.0.1:8080(examples). -
Open the setup link from the log:
Terminal window docker compose logs panel | grep setupThe link carries the setup token, so nobody who finds a fresh panel on the internet can claim it. The setup wizard asks how you’ll use the panel (homelab or hosting, see Overview) and creates your operator account and first tenant.
Next: add a node. A node on the same machine: put its enrollment token into .env as AGENT_ENROLLMENT_TOKEN=… and run docker compose --profile agent up -d; other machines use compose.agent.yml or the command the panel shows.
Settings in .env
Section titled “Settings in .env”| Variable | Meaning |
|---|---|
POSTGRES_PASSWORD |
the database password (required) |
PANEL_PUBLIC_URL |
the address agents and emails use to reach the panel, e.g. https://panel.example.com (required) |
PANEL_CATALOGUE_URL |
the game catalogue to add games from (default: the public one). Also in Admin → Settings. |
PANEL_DOCS_URL |
the documentation website for the panel’s help links (?), default: this site. Also in Admin → Settings. |
PANEL_BIND, PANEL_PORT |
where the panel is published (default 127.0.0.1 and 8080) |
TRUSTED_PROXY_NETWORK |
your reverse proxy’s network (default 172.16.0.0/12, Docker’s networks) |
PANEL_KEYS_DIR |
the folder with the master key (default ./keys) |
PANEL_IMAGE, AGENT_IMAGE |
the panel image, and the agent image the Add node command uses (default: the published latest) |
AGENT_ENROLLMENT_TOKEN, AGENT_SFTP_PORT |
for the agent on this machine (--profile agent) |
Sign-in providers go into panel.env (see panel.env.example, and Email and sign-in). All settings: Environment variables.
The master key
Section titled “The master key”keys/master.key encrypts the keys that protect secrets (Steam passwords, secret variables, the push key) in the database, so a database dump alone reveals nothing. The compose file’s init step creates it on the first start and makes it readable only by the panel; it never replaces an existing one.
The hosting profile requires it: the setup and Admin → Settings refuse hosting without a master key, and a hosting panel without one doesn’t start. Other people’s secrets must not depend on the database alone.
Reverse proxy
Section titled “Reverse proxy”The panel listens on 127.0.0.1:8080 and expects a reverse proxy in front for HTTPS. HTTPS is needed for passkeys, the phone app and push notifications. Whatever proxy you use:
- Set
TRUSTED_PROXY_NETWORKto the network the proxy connects from, so the panel sees the real client addresses and HTTPS. A proxy on the same machine connecting to127.0.0.1:8080arrives from Docker’s network: the default fits. - Pass WebSockets through: the live console and the agents use them.
- Don’t buffer responses for the console stream (the panel sends
X-Accel-Buffering: nofor nginx). - Pass the
Hostheader on unchanged: live connections from another site are refused by comparing it with the browser’sOrigin. - Security headers (Content-Security-Policy,
X-Frame-Options,nosniff,Referrer-Policy) come from the panel itself; the proxy only adds HSTS if you want it. - Allow large request bodies, or accept that browsers upload big files in smaller parts: uploads adapt when a proxy answers
413.
Caddy gets the certificate itself and passes WebSockets and the Host header on by default.
panel.example.com { reverse_proxy 127.0.0.1:8080 { flush_interval -1 }}map $http_upgrade $connection_upgrade { default upgrade; '' close;}
server { listen 443 ssl; http2 on; server_name panel.example.com; ssl_certificate /etc/letsencrypt/live/panel.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/panel.example.com/privkey.pem; client_max_body_size 100m;
location / { proxy_pass http://127.0.0.1:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 1h; }}Traefik
Section titled “Traefik”With Traefik in Docker, the panel joins Traefik’s network instead of publishing a port. Next to compose.yml, a compose.override.yml (Compose reads it automatically):
services: panel: networks: [default, traefik] labels: traefik.enable: "true" traefik.docker.network: traefik traefik.http.routers.panel.rule: Host(`panel.example.com`) traefik.http.routers.panel.entrypoints: websecure traefik.http.routers.panel.tls.certresolver: letsencrypt traefik.http.services.panel.loadbalancer.server.port: "8080"
networks: traefik: external: trueUse your Traefik’s network, entry point and certificate resolver names. Set TRUSTED_PROXY_NETWORK to the Traefik network’s range (docker network inspect traefik).
Coming from compose.panel.yml
Section titled “Coming from compose.panel.yml”Earlier versions had compose.panel.yml and a master.key next to it. The new compose.yml keeps the same project name, so the database stays:
docker compose -f compose.panel.yml downmkdir -p keys && mv master.key keys/master.keydocker compose up -dThe compose file enables IPv6 for the panel’s network, so mail servers, registries and nodes reachable only over IPv6 work too.
Health checks
Section titled “Health checks”GET /health/live: the process runs.GET /health/ready: the database is reachable.