Production Deployment
This guide covers deploying Watchgrid for production use with SSL, reverse proxy, and proper security.
Architecture
The production stack uses Traefik as a reverse proxy with automatic Let's Encrypt SSL certificates:
Internet
│
├─ HTTPS (:443) ──► Traefik ──► Frontend (:80)
│ └──► Server API (:8080)
│
└─ UDP (:51820) ──────────────► WireGuard
- Traefik handles SSL termination and HTTP→HTTPS redirects
- PostgreSQL is internal only (not exposed to the host)
- Docker Registry is internal only (accessible via VPN at
registry.wg:5000)
Setup
1. Clone and Configure
2. Edit Environment Variables
Edit .env with your production settings:
# REQUIRED
WG_SERVER_ENDPOINT=your-public-ip:51820
JWT_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -hex 24)
ADMIN_PASSWORD=your-secure-admin-password
FRONTEND_HOST=watchgrid.yourdomain.com
# SSL (Cloudflare DNS challenge)
TRAEFIK_ACME_EMAIL=ssl@yourdomain.com
CF_DNS_API_TOKEN=your-cloudflare-api-token
# Optional
SERVER_LATITUDE=52.0705
SERVER_LONGITUDE=4.3007
VERSION=latest
3. Start the Production Stack
4. Verify
Open https://watchgrid.yourdomain.com in your browser.
Required Environment Variables
| Variable | Purpose | Example |
|---|---|---|
WG_SERVER_ENDPOINT |
Public IP:port devices connect to | 203.0.113.50:51820 |
JWT_SECRET |
JWT signing key (min 32 chars) | openssl rand -hex 32 |
POSTGRES_PASSWORD |
Database password | openssl rand -hex 24 |
ADMIN_PASSWORD |
Admin account password | Your secure password |
FRONTEND_HOST |
Domain for SSL certificate | watchgrid.example.com |
TRAEFIK_ACME_EMAIL |
Email for Let's Encrypt | admin@example.com |
CF_DNS_API_TOKEN |
Cloudflare API token for DNS challenge | Your Cloudflare token |
SSL Certificates
The production stack uses Traefik with Cloudflare DNS challenge for Let's Encrypt:
- Traefik requests a certificate from Let's Encrypt
- Uses the Cloudflare API to create a DNS TXT record for validation
- Certificate is automatically renewed before expiry
- All HTTP traffic is redirected to HTTPS
Firewall Rules
Ensure these ports are open:
| Port | Protocol | Purpose |
|---|---|---|
| 80 | TCP | HTTP (redirects to HTTPS) |
| 443 | TCP | HTTPS (web dashboard + API) |
| 51820 | UDP | WireGuard VPN |
Database Backups
PostgreSQL data is stored in a Docker volume. Back it up regularly:
# Dump the database
docker exec watchgrid-postgres pg_dump -U watchgrid watchgrid > backup-$(date +%Y%m%d).sql
# Restore from backup
docker exec -i watchgrid-postgres psql -U watchgrid watchgrid < backup-20240101.sql
Database Migrations
Migrations run automatically on server startup. When upgrading:
docker compose -f docker-compose.prod.yml pull # Get latest images
docker compose -f docker-compose.prod.yml down # Stop services
docker compose -f docker-compose.prod.yml up -d # Start — migrations run automatically
The server waits for PostgreSQL to be ready, then applies any pending migrations from scripts/migrations/. Failed migrations prevent startup (fail-fast).
SSH CA Key Backup
The SSH CA keys are stored in the watchgrid-ssh-ca Docker volume. Back these up — losing them invalidates all issued host certificates:
# Back up CA keys
docker cp watchgrid-server:/etc/watchgrid/ca_user_key ./backup/
docker cp watchgrid-server:/etc/watchgrid/ca_user_key.pub ./backup/
docker cp watchgrid-server:/etc/watchgrid/ca_host_key ./backup/
docker cp watchgrid-server:/etc/watchgrid/ca_host_key.pub ./backup/
Updates
To update Watchgrid:
cd watchgrid
git pull
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml down
docker compose -f docker-compose.prod.yml up -d
Check the logs to confirm a clean startup:
Trial Instances
For running trial/demo instances for customers, see the trials/ directory. It provisions isolated Watchgrid instances on Hetzner VMs with:
- Automated VM creation and DNS setup
- Auto-expiring trials (configurable duration)
- Cron-based cleanup of expired trials