Skip to content

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

git clone https://github.com/RDG88/watchgrid.git
cd watchgrid
cp .env.example .env

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

docker compose -f docker-compose.prod.yml up -d

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:

  1. Traefik requests a certificate from Let's Encrypt
  2. Uses the Cloudflare API to create a DNS TXT record for validation
  3. Certificate is automatically renewed before expiry
  4. 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:

docker compose -f docker-compose.prod.yml logs -f server

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
# Provision a 14-day trial
./trials/provision.sh --email customer@example.com --days 14

# List active trials
./trials/cleanup.sh --list

# Clean up expired trials
./trials/cleanup.sh