Wardengate

Install

Docker install

A single container is the fastest way to stand Wardengate up on one host for a lab, a pilot, or a small team. This page walks through a standalone docker run deployment with an external Postgres and Redis. For a full stack with databases managed alongside the server, skip ahead to Docker Compose.

Prerequisites

  • Docker Engine 24 or later, or a compatible runtime such as Podman 4.6+
  • A Linux host matching the tiers in System requirements
  • PostgreSQL 14+ reachable from the host, with an empty database owned by a dedicated role
  • A Redis or Valkey instance (the default image can embed Valkey for trials; disable for production)
  • TLS certificate and key for the public hostname, or an ACME account

Pull the image

Images are published to Docker Hub and to GitHub Container Registry. Both hold identical digests; pick whichever your environment can pull without extra firewall carve-outs.

docker pull wardengate/server:2.4
# or
docker pull ghcr.io/wardengate/server:2.4

Pin to a specific minor tag in production. The 2.x tag follows the latest 2-series release and is useful for lab rebuilds but dangerous for anything you care about.

Prepare host directories

Two directories on the host hold durable state and the recording scratch buffer. Create them up front so the container does not end up owning root-owned paths.

sudo mkdir -p /var/lib/wardengate/{data,recordings}
sudo chown -R 10001:10001 /var/lib/wardengate
sudo chmod 750 /var/lib/wardengate/data /var/lib/wardengate/recordings

UID 10001 is the non-root user baked into the image.

Environment variables

Configuration is supplied via environment variables. The list below covers the required set for a first boot; see the CLI reference for the full catalogue.

VariablePurpose
WG_DB_URLPostgres DSN, for example postgres://wg:...@db:5432/wardengate?sslmode=require
WG_REDIS_URLRedis or Valkey URL; omit to use the embedded cache
WG_SECRET_KEY32-byte base64 key used to seal vault material; generate once and keep safe
WG_PUBLIC_URLCanonical external URL, for example https://wg.example.com:8443
WG_ADMIN_EMAILFirst administrator; receives the bootstrap sign-in link
WG_TLS_CERT_FILEPath inside the container to the PEM certificate chain
WG_TLS_KEY_FILEPath inside the container to the private key

Generate the secret key once and treat it like a database password:

openssl rand -base64 32

Run the container

The run command below expects TLS material at /etc/wardengate/tls on the host. Adjust paths if you store certificates elsewhere.

docker run -d \
  --name wardengate \
  --restart unless-stopped \
  -p 8443:8443 \
  -p 2222:2222 \
  -p 3389:3389 \
  -p 4400:4400 \
  -p 7443:7443 \
  -v /var/lib/wardengate/data:/var/lib/wardengate/data \
  -v /var/lib/wardengate/recordings:/var/lib/wardengate/recordings \
  -v /etc/wardengate/tls:/etc/wardengate/tls:ro \
  -e WG_DB_URL="postgres://wg:s3cret@db.internal:5432/wardengate?sslmode=require" \
  -e WG_REDIS_URL="redis://cache.internal:6379/0" \
  -e WG_SECRET_KEY="$(cat /etc/wardengate/secret.key)" \
  -e WG_PUBLIC_URL="https://wg.example.com:8443" \
  -e WG_ADMIN_EMAIL="ops@example.com" \
  -e WG_TLS_CERT_FILE=/etc/wardengate/tls/fullchain.pem \
  -e WG_TLS_KEY_FILE=/etc/wardengate/tls/privkey.pem \
  wardengate/server:2.4

On first boot the server runs database migrations, emits a CA certificate into the data volume, emails a sign-in link to WG_ADMIN_EMAIL, and begins accepting connections.

Volume layout

Only two paths matter for persistence. Everything else in the container is reconstructible from configuration and state.

  • /var/lib/wardengate/data — internal CA, host keys, bootstrap state. Back this up.
  • /var/lib/wardengate/recordings — scratch buffer that drains to object storage. Safe to lose on restart, but size it for your peak concurrency.

Health check

The container exposes an unauthenticated liveness probe at /healthz and a detailed readiness check at /readyz. The image ships a built-in Docker healthcheck that hits /readyz every 30 seconds.

docker ps --filter name=wardengate
curl -fsS --cacert /etc/wardengate/tls/fullchain.pem \
  https://wg.example.com:8443/readyz

A healthy readiness response looks roughly like this:

{
  "status": "ready",
  "version": "2.4.1",
  "checks": {
    "postgres": "ok",
    "redis": "ok",
    "tls": "ok",
    "clock_skew_ms": 12
  }
}

Exposing proxy ports

Four proxy ports face the internet or private network: SSH (2222), RDP (3389), database (4400), and the admin/user web UI (8443). If you only use a subset of protocols, remove the corresponding -p flags — disabling an unused port on the host is easier than firewalling it later.

RDP on 3389 is a TCP pass-through; do not attempt to terminate TLS for it at an upstream proxy. The SSH proxy on 2222 speaks OpenSSH wire protocol and authenticates users with short-lived certificates issued by the Wardengate CA.

Upgrading the container

Upgrades are a pull, stop, and run with the new tag. State and configuration live on the host volumes, so the new container picks up where the old one left off. Migrations run automatically during startup of the new version.

docker pull wardengate/server:2.5
docker stop wardengate
docker rm   wardengate
# re-run the same docker run command with the new tag
docker run -d --name wardengate ... wardengate/server:2.5

For anything larger than a lab, prefer the procedure in Upgrading — pre-flight checks and rollback guidance matter more as you scale.

Uninstall

The container holds no persistent state of its own. Removing it leaves your database, recordings, and secret key intact.

docker stop wardengate
docker rm wardengate

# Optional — destroy persisted state
sudo rm -rf /var/lib/wardengate
# Drop the database and bucket out of band

Related