-
Notifications
You must be signed in to change notification settings - Fork 0
Deployment
Production deployment guide for Vantyr — the Rust/Axum server plus its bundled React dashboard. The shipped artifact is a single Docker image (API + dashboard) backed by PostgreSQL.
See also: Configuration · Environment-template · Development · Security · Usage
| Component | Requirement |
|---|---|
| Server | Docker + Docker Compose v2 (recommended), or a native build of vantyr-server + PostgreSQL — see Development. |
| Database | PostgreSQL 16 (bundled in Compose as postgres:16-alpine). |
| Agent | Windows 10/11 (64-bit) or Linux. Built and distributed separately — see Development → Agent. |
The server image is fully self-contained: the React dashboard is compiled to static files at build time and served from /app/static. There is no Node.js in the production image and no separate frontend container to run.
cp .env.example .env
# edit .env: set POSTGRES_PASSWORD and ADMIN_PASSWORD at minimum
docker compose up -dCompose reads .env automatically for variable substitution, and the server service also loads it via env_file: .env. Only POSTGRES_PASSWORD and ADMIN_PASSWORD are required; everything else has a safe default.
By default the dashboard and API are then available at http://<host>:9000. Log in with ADMIN_USERNAME (default admin) and your ADMIN_PASSWORD.
The default
docker compose upruns over plaintext HTTP (ENFORCE_HTTPS=falsein.env.example). This exposes session cookies, agent tokens, screen frames and keystroke data on the wire — only acceptable on localhost or a trusted LAN. For anything internet-exposed, put Vantyr behind a TLS-terminating reverse proxy (below).
The Compose file references the published GHCR image but also includes a build: stanza, so you can build from source instead of pulling:
docker compose up -d --buildThis rebuilds ghcr.io/gladsonsam/vantyr/server:latest locally from server/Dockerfile.
server/Dockerfile is a 3-stage build:
| Stage | Base | Produces |
|---|---|---|
| 1 — frontend | node:20-alpine |
npm ci && npm run build → React SPA in dist/
|
| 2 — server | rust:1 |
cargo build --release --locked -p vantyr-server (the agent dir is not a workspace member, so no Windows deps are pulled) |
| 3 — runtime | debian:bookworm-slim |
the server binary + SPA, run as non-root uid 10001
|
Key runtime facts:
- The dashboard is baked into
/app/static(STATIC_DIR=/app/static); the binary serves it as the SPA fallback. - SQL migrations are embedded (
sqlx::migrate!) and run at startup — no separate migration step. - A Docker
HEALTHCHECKpolls/healthz(--interval=10s --timeout=3s --retries=6). - The image exposes port 9000.
| Service | Image | Notes |
|---|---|---|
db |
postgres:16-alpine |
Data on the pgdata named volume. Published only on 127.0.0.1:5432 (not exposed to the LAN). Has its own pg_isready healthcheck; the server depends_on it service_healthy. |
server |
ghcr.io/gladsonsam/vantyr/server:latest |
Runs with network_mode: host so mDNS/LAN discovery works (Docker's bridge does not forward multicast). DATABASE_URL is built from the POSTGRES_* vars and points at 127.0.0.1:5432. |
Because server uses host networking, it binds directly to the host's port 9000 (from LISTEN_ADDR, default 0.0.0.0:9000) — there is no Docker port mapping for it.
| Endpoint | Purpose |
|---|---|
GET /healthz |
Liveness. Always reachable, even when ENFORCE_HTTPS=true. Used by the Docker HEALTHCHECK. |
GET /readyz |
Readiness — includes a database connectivity check. Use this as the load-balancer / orchestrator readiness probe. |
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9000/healthz # expect 200
curl -sS -o /dev/null -w "%{http_code}\n" http://127.0.0.1:9000/readyz # 200 once DB is upNo TLS or reverse-proxy config ships in the repo. For any deployment beyond localhost or a trusted LAN, terminate TLS in front of the server (Caddy / nginx / Traefik) and configure these two variables in .env:
| Variable | Set to |
|---|---|
ENFORCE_HTTPS |
true — the server then returns 426 Upgrade Required on requests it sees as plain HTTP. It trusts the proxy's X-Forwarded-Proto: https header (and wss for WebSocket upgrades). |
TRUSTED_PROXY_CIDRS |
the CIDR(s)/IP(s) of your proxy, e.g. 10.0.0.0/8,172.16.0.0/12. Required so X-Forwarded-For / X-Real-IP is trusted for login rate limiting and lockout. Leave empty to trust no proxy (keys on the direct TCP peer). |
Also set PUBLIC_BASE_URL=https://your-host (used for deep links, OIDC redirects, and mDNS advertisement). Agents should then connect over wss://your-host/ws/agent.
A plain Docker bridge with Traefik labels gives you TLS, but mDNS from inside the container will not reach the LAN. To get both, keep the default stack shape — network_mode: host on server, Postgres on 127.0.0.1 — and attach Traefik labels that forward to the host's port 9000:
labels:
- traefik.enable=true
- traefik.http.routers.vantyr.rule=Host(`your.domain.example`)
- traefik.http.routers.vantyr.service=vantyr
- traefik.http.routers.vantyr.entrypoints=websecure
- traefik.http.routers.vantyr.tls=true
- traefik.http.routers.vantyr.tls.certresolver=cloudflare
- traefik.http.services.vantyr.loadbalancer.server.url=http://172.17.0.1:9000- Replace
172.17.0.1with yourdocker0gateway if different (ip -br addr show docker0). - Do not set
loadbalancer.server.scheme/portalongsideserver.url— Traefik rejects that combination. - With
network_mode: host, omittraefik.docker.networkon this service. - In
.envsetENFORCE_HTTPS=true,PUBLIC_BASE_URL=https://your.domain.example,VANTYR_MDNS_PORT=443.
Traefik 404 ⇒ the Host() rule doesn't match the browser hostname; 502 ⇒ the backend server.url IP/port is wrong.
The server advertises itself on the LAN so agents can auto-discover it. It needs a resolvable WebSocket URL; set one of:
-
PUBLIC_BASE_URL(HTTP or HTTPS — the server derivesws:///wss://for the TXT record), or -
VANTYR_MDNS_WSS_URLexplicitly (e.g.wss://host:port/ws/agent).
| Variable | When to set |
|---|---|
VANTYR_MDNS_PORT |
When TLS terminates on a different port (e.g. 443 behind Traefik). Defaults to the listen port (9000). |
VANTYR_MDNS_ADDRESSES |
Comma-separated LAN IPs if the host advertises the wrong interface. |
Allow UDP 5353 on the server host firewall. Host networking is required for multicast to reach the LAN; Docker Desktop on Windows/macOS does not replicate Linux host networking, so prefer a manual agent WebSocket URL + correct PUBLIC_BASE_URL there.
A Prometheus metrics endpoint is exposed at GET /metrics when METRICS_ENABLED=true (the default). It is exempt from the HTTPS-required check. Point a Prometheus scrape job at it for per-method/status HTTP counters plus pool/agent/viewer gauges. Set LOG_JSON=true for structured JSON logs (Loki/ELK).
| Data | Lives in |
|---|---|
| PostgreSQL data | the pgdata named volume (/var/lib/postgresql/data). |
| Telemetry, screenshots, keystrokes, software inventory, audit log, alert-event screenshot blobs | all in Postgres — backing up the database backs up everything. |
There are no host bind mounts by default. Back up with docker compose exec db pg_dump (or snapshot the pgdata volume while stopped). Telemetry growth is bounded by the retention job — see Configuration for RETENTION_INTERVAL_SECS, ALERT_EVENT_RETENTION_DAYS, SOFTWARE_INVENTORY_RETENTION_DAYS, SCRIPT_EXECUTION_RETENTION_DAYS, and METRICS_RETENTION_DAYS.
docker compose pull # fetch the new server image (or: git pull && docker compose build)
docker compose up -d-
Migrations run automatically at startup (embedded via
sqlx::migrate!), so an upgrade applies any new SQL migrations on first boot of the new image. There are 55+ migrations inserver/migrations/. - Never edit an already-applied migration — a checksum guard will refuse to start. (Migration
0021is intentionally absent; numbering jumps0020 → 0022.) - The agent updates independently of the server (minisign-verified MSI auto-update on Windows); upgrading the server does not push agent updates.
Build and run the server binary directly:
cargo build --release -p vantyr-server
DATABASE_URL=postgres://user:pass@host:5432/db \
ADMIN_PASSWORD=... \
STATIC_DIR=./static \
./target/release/vantyr-serverProvide the same environment variables as the Docker path (see Configuration and Environment-template), point STATIC_DIR at the built dashboard (frontend/dist), and ensure PostgreSQL is reachable via DATABASE_URL.
Install and configure
Day to day
Integrations
Developers and security