Install
Docker Compose
Compose is the best option for a single-host production-ish install where you want the database and cache colocated with the server. The stack below runs Wardengate against a local Postgres and Valkey, all three managed by the same compose lifecycle.
For real production you will eventually want external Postgres with backups, replicas, and a clear upgrade path. Compose stays useful as a pattern for pre-prod and small internal deployments.
Layout on disk
Create a directory per environment and keep compose, env, and TLS material side by side. Everything bind-mounts into the containers.
/opt/wardengate/
docker-compose.yml
.env
tls/
fullchain.pem
privkey.pem
data/ # wardengate durable state
recordings/ # recording scratch buffer
pgdata/ # postgres data
valkey/ # valkey AOF if persistence is enableddocker-compose.yml
A working stack with Postgres 16, Valkey 7, and the Wardengate server. All three services join a private bridge network; only the Wardengate container exposes ports to the host.
name: wardengate
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_DB: wardengate
POSTGRES_USER: wardengate
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U wardengate -d wardengate"]
interval: 10s
timeout: 5s
retries: 10
valkey:
image: valkey/valkey:7.2-alpine
restart: unless-stopped
command: ["valkey-server", "--save", "", "--appendonly", "no"]
volumes:
- ./valkey:/data
wardengate:
image: wardengate/server:2.4
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
valkey:
condition: service_started
environment:
WG_DB_URL: postgres://wardengate:${POSTGRES_PASSWORD}@postgres:5432/wardengate?sslmode=disable
WG_REDIS_URL: redis://valkey:6379/0
WG_SECRET_KEY: ${WG_SECRET_KEY}
WG_PUBLIC_URL: ${WG_PUBLIC_URL}
WG_ADMIN_EMAIL: ${WG_ADMIN_EMAIL}
WG_TLS_CERT_FILE: /etc/wardengate/tls/fullchain.pem
WG_TLS_KEY_FILE: /etc/wardengate/tls/privkey.pem
ports:
- "8443:8443"
- "2222:2222"
- "3389:3389"
- "4400:4400"
- "7443:7443"
volumes:
- ./data:/var/lib/wardengate/data
- ./recordings:/var/lib/wardengate/recordings
- ./tls:/etc/wardengate/tls:ro
healthcheck:
test: ["CMD", "wgctl", "system", "probe", "--local"]
interval: 30s
timeout: 5s
retries: 5
start_period: 30sThe Postgres sslmode=disable above is fine on a compose-private network; flip it to require and issue a server cert if you move the database to a different host.
.env file
Keep every secret in an .env next to the compose file. Restrict it to the operator who runs compose; the file includes the vault sealing key, which is as sensitive as the database password.
# /opt/wardengate/.env
POSTGRES_PASSWORD=replace-me-with-a-long-random-string
WG_SECRET_KEY=$(openssl rand -base64 32)
WG_PUBLIC_URL=https://wg.example.com:8443
WG_ADMIN_EMAIL=ops@example.comsudo chown root:docker /opt/wardengate/.env
sudo chmod 640 /opt/wardengate/.envTLS termination
Wardengate terminates TLS itself — the certificate and key you drop in ./tls/ are used by the control plane directly. Mounting the directory read-only means you can swap certificates with a simple file write and restart the service.
# refresh certificate without downtime
sudo cp newcert.pem /opt/wardengate/tls/fullchain.pem
sudo cp newkey.pem /opt/wardengate/tls/privkey.pem
docker compose -f /opt/wardengate/docker-compose.yml kill -s HUP wardengateA SIGHUP tells the server to reload TLS material without dropping active sessions. If you front the stack with an external load balancer that terminates TLS, set WG_TRUST_PROXY_HEADERS=true and configure PROXY protocol v2 on the LB.
First boot
cd /opt/wardengate
docker compose pull
docker compose up -d
docker compose logs -f wardengateThe first run prints a bootstrap URL in the logs — the same link that gets emailed to WG_ADMIN_EMAIL — that you click to set an initial password and generate an API token.
Logs
All services log to stdout in JSON by default. Ship the stream wherever you already send container logs: Loki, Splunk, Datadog, Elastic, CloudWatch. For local inspection:
docker compose logs -f wardengate
docker compose logs --since 15m postgres
docker compose logs --tail 200 valkeyAudit events are a separate stream from the operational log; point WG_AUDIT_SINK at a webhook or syslog target to forward them to a SIEM.
Lifecycle commands
| Command | Effect |
|---|---|
docker compose up -d | Start or reconcile the stack |
docker compose ps | Show container state and health |
docker compose pull && docker compose up -d | Upgrade to the pinned image tag |
docker compose restart wardengate | Recycle the server without touching dependencies |
docker compose down | Stop and remove containers (bind-mounted data is preserved) |
Upgrading
Bump the image tag in docker-compose.yml, then recreate the Wardengate container. Postgres and Valkey are untouched unless you also bumped their tags.
sed -i 's|wardengate/server:2.4|wardengate/server:2.5|' docker-compose.yml
docker compose pull wardengate
docker compose up -d wardengateTroubleshooting common errors
WG_SECRET_KEY is not 32 bytes
The key must be exactly 32 bytes of entropy, base64-encoded. Regenerate with openssl rand -base64 32. Do not trim trailing = padding.
Postgres container keeps restarting
Usually caused by a pgdata/ directory owned by root from a previous run. docker compose down, fix ownership (chown -R 999:999 pgdata), and bring the stack back up.
address already in use on 3389
Another service is bound to the host port. On a shared admin host the usual culprit is xrdp. Either disable that or remap the port in the compose file to something like 33389:3389.
x509: certificate signed by unknown authority
The PEM in tls/fullchain.pem is missing intermediate certificates. Concatenate them in order (leaf first, then intermediates) and reload with SIGHUP.
Container is healthy but UI 502s from nginx
If you front compose with nginx, the upstream must speak HTTPS and trust the Wardengate cert. Setting proxy_ssl_verify off for a self-signed dev cert is fine; production should use a real cert on the Wardengate side and plain proxy_pass https://....
Related
- Docker install — single container without compose
- Kubernetes & Helm — production orchestration
- Backup & restore — protect the data you just installed