Deployment

Target: single VPS, Docker Compose, Caddy for TLS. Managed databases (RDS, Cloud SQL, Supabase) cannot load custom Rust extensions and are out of scope.

Minimum requirements

The stack (two containers)

Use (or adapt) the docker-compose.yml and Caddyfile that pg-web init (or this docs site) provides.

services:
  postgres:
    image: pgweb/postgres:latest
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: app
    volumes:
      - pgdata:/var/lib/postgresql/data

  # caddy: (uncomment for prod)
  #   image: caddy:2
  #   ports: ["80:80", "443:443"]
  #   volumes: ["./Caddyfile:/etc/caddy/Caddyfile", ...]

First production deploy (summary)

  1. SSH to VPS, clone your app repo (or copy compose + Caddyfile).
  2. Create a .env with a strong POSTGRES_PASSWORD.
  3. docker compose up -d.
  4. From your laptop, open a background SSH tunnel: ssh -L 5432:localhost:5432 user@vps.
  5. pg-web migrate apply --url "postgres://postgres:pass@localhost:5432/app"
  6. pg-web push --url "..." (or pg-web push --with-migrate).
  7. Visit https://yourdomain. Caddy handled Let's Encrypt.

Updating the site / app

Push new code with pg-web push --with-migrate (or the in-image CLI via docker compose exec postgres pg-web push once you have the repo on the VPS).

Zero-downtime for code changes — routes/templates/handlers are swapped in a single transaction inside Postgres.

Upgrading the pg-web image itself

On the VPS: docker compose pull && docker compose up -d, then ALTER EXTENSION pg_web_ext UPDATE; inside the container.

Full recipe, security checklist, CI example, and backup story: docs/DEPLOYMENT.md in the repo.

This docs site deploys exactly the same way. See site/docker-compose.yml + site/Caddyfile and the README.md in site/ for the exact commands used to keep https://pg-web.dev live.