Skip to Content

Production Guide: Deploy GlitchTip with Docker Compose + Caddy + PostgreSQL on Ubuntu

A production-first GlitchTip deployment with reverse proxy, hardened secrets, backups, and operational verification.

Error monitoring often starts as an afterthought and then becomes urgent when customers hit failures you cannot reproduce. In many teams, that shows up as Slack screenshots, partial stack traces, and a lot of guessing under pressure. GlitchTip solves this by giving you self-hosted error tracking that is Sentry-compatible, lightweight to run, and practical for engineering teams that want ownership of logs and incident data.

In this production guide, you will deploy GlitchTip on Ubuntu using Docker Compose, front it with Caddy for automatic HTTPS, run PostgreSQL as the durable data store, and add operational controls for backups, upgrades, and troubleshooting. The focus is not just getting containers online; it is building a deployment that your team can maintain safely during real incidents and routine maintenance windows.

You will also get a practical runbook approach: baseline hardening before deployment, explicit health checks after each major step, and a rollback mindset for upgrades. The same discipline is what keeps an observability platform useful under stress instead of becoming another fragile dependency. Throughout the guide, we will call out what to verify, why it matters, and where teams usually encounter avoidable failure modes.

Architecture and flow overview

This deployment uses a simple but robust architecture: Caddy terminates TLS and handles inbound traffic, the GlitchTip web service handles UI and ingestion endpoints, worker components process background jobs, and PostgreSQL persists events and project metadata. You can run all components on one VM for small-to-medium workloads and later split database or worker tiers as traffic grows.

  • Edge: Caddy provides TLS, redirects HTTP to HTTPS, and protects your app entrypoint.
  • App: GlitchTip web container serves dashboard and receives SDK events.
  • Workers: Background queue consumers process jobs and notifications.
  • Data: PostgreSQL stores users, projects, issue state, and event references.

Data flow is straightforward: applications send events to the GlitchTip ingestion endpoints over HTTPS; web and worker processes persist and process event data in PostgreSQL; the dashboard renders grouped issues and release context for engineering workflows. Operationally, your most important responsibilities are preserving database durability, maintaining TLS health, and ensuring workers stay ahead of incoming event volume.

Client SDKs -> https://errors.example.com/api/*
Caddy (TLS + reverse proxy) -> glitchtip-web:8000
GlitchTip workers -> PostgreSQL
Ops workflows -> backup, restore, rotate secrets, upgrade

If the copy button does not work in your browser, select the code block and copy it manually.

Prerequisites

Before you deploy, confirm DNS, firewall rules, and host baseline hardening. A clean Ubuntu 22.04 or 24.04 host with at least 4 vCPU, 8 GB RAM, and 80 GB SSD is a reasonable starting point for moderate traffic. Reserve more I/O if you expect bursty exception volume.

  • Ubuntu server with sudo access
  • A DNS record (for example errors.yourdomain.com) pointing to the server IP
  • Ports 80/443 open in cloud firewall and host firewall
  • Docker Engine + Docker Compose plugin
  • A backup destination for PostgreSQL dumps

Plan DNS and certificates before application cutover. If you already have a hosted Sentry endpoint in production, keep both platforms available during migration and route one service at a time. That phased approach lets you validate event capture and alert routing without creating a blind spot.

sudo apt update && sudo apt -y upgrade
sudo apt -y install ca-certificates curl gnupg ufw jq
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

If the copy button does not work in your browser, select the code block and copy it manually.

Step-by-step deployment

1) Install Docker and Compose

Install Docker from the official repository, then validate daemon health before continuing. Do not continue if Docker info fails; fix host setup first.

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
docker version
docker compose version

If the copy button does not work in your browser, select the code block and copy it manually.

2) Create deployment directory and environment file

Store configuration under /opt/glitchtip. Keep secrets in an env file with restrictive permissions so accidental world-readable leaks do not happen.

sudo mkdir -p /opt/glitchtip/{caddy,postgres,backups}
cd /opt/glitchtip
openssl rand -hex 32  # generate SECRET_KEY candidate
cat > .env <<'EOF'
GLITCHTIP_DOMAIN=errors.yourdomain.com
POSTGRES_DB=glitchtip
POSTGRES_USER=glitchtip
POSTGRES_PASSWORD=REPLACE_STRONG_DB_PASSWORD
SECRET_KEY=REPLACE_LONG_SECRET_KEY
[email protected]
EOF
chmod 600 .env

If the copy button does not work in your browser, select the code block and copy it manually.

3) Compose stack definition

The stack includes PostgreSQL, web, worker, and Caddy. We pin image families where practical and keep durable volumes for stateful services.

cat > docker-compose.yml <<'EOF'
services:
  db:
    image: postgres:16
    restart: unless-stopped
    env_file: .env
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - ./postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 10

  web:
    image: glitchtip/glitchtip:latest
    restart: unless-stopped
    env_file: .env
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      GLITCHTIP_DOMAIN: https://errors.yourdomain.com
      SECRET_KEY: ${SECRET_KEY}
      PORT: 8000
    depends_on:
      db:
        condition: service_healthy

  worker:
    image: glitchtip/glitchtip:latest
    restart: unless-stopped
    env_file: .env
    command: ./bin/run-celery-with-beat.sh
    environment:
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      GLITCHTIP_DOMAIN: https://errors.yourdomain.com
      SECRET_KEY: ${SECRET_KEY}
    depends_on:
      db:
        condition: service_healthy

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - ./caddy/data:/data
      - ./caddy/config:/config
    depends_on:
      - web
EOF

If the copy button does not work in your browser, select the code block and copy it manually.

4) Configure Caddy reverse proxy

Caddy automates certificate issuance and renewal. Keep this configuration minimal and explicit. Add middleware only when you can test its behavior under load.

cat > caddy/Caddyfile <<'EOF'
errors.yourdomain.com {
  encode zstd gzip
  reverse_proxy web:8000
  header {
    Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
    X-Content-Type-Options "nosniff"
    Referrer-Policy "same-origin"
    X-Frame-Options "SAMEORIGIN"
  }
}
EOF

If the copy button does not work in your browser, select the code block and copy it manually.

5) Start services and run initialization checks

After bringing up the stack, watch logs for migrations and worker startup. First boot can take longer while migrations initialize tables.

cd /opt/glitchtip
docker compose up -d
docker compose ps
docker compose logs web --tail=120
docker compose logs worker --tail=120
curl -I https://errors.yourdomain.com

If the copy button does not work in your browser, select the code block and copy it manually.

If the dashboard loads but ingestion looks empty, run a controlled test error from a non-critical app. Validate that event metadata appears as expected and that no sensitive request payloads are accidentally captured. Observability quality is not just uptime; it is signal fidelity and safe defaults.

Configuration and secrets handling

Production reliability depends on secret hygiene and repeatable change control. Keep plaintext secrets out of shell history and git repositories. If your team uses a secret manager (Vault, 1Password Connect, AWS Secrets Manager), template the env file from managed values during deployment and never commit rendered files.

At minimum, rotate POSTGRES_PASSWORD and SECRET_KEY on a planned cadence, and rotate immediately after personnel changes or potential host compromise. Restrict backup access because event payloads may contain sensitive metadata depending on what your apps emit.

For compliance-heavy environments, define explicit data retention windows and a deletion process for sensitive projects. Coordinate legal, security, and SRE stakeholders on retention durations before ingesting production events at scale.

# Example permission model
sudo chmod 750 /opt/glitchtip
sudo chmod 640 /opt/glitchtip/.env

# Optional: render .env from secret manager values
# export POSTGRES_PASSWORD=$(secretctl read path/to/db_password)
# envsubst < .env.template > /opt/glitchtip/.env

If the copy button does not work in your browser, select the code block and copy it manually.

Verification checklist

Do not stop at a green container list. Validate the full user path and operational controls. Create a test project, ingest an intentional error from a sample app, and verify issue grouping, timestamps, and alerting behavior.

  • HTTPS endpoint returns 200 and valid certificate chain
  • Admin login and project creation works
  • Test event is ingested and visible in Issues
  • Workers process scheduled tasks without backlog growth
  • Backup and restore dry-run succeeds
# quick health probes
docker compose ps
docker compose exec db psql -U glitchtip -d glitchtip -c "select now();"
docker compose logs worker --tail=80 | egrep -i "error|exception|traceback" || true

# backup example
TS=$(date +%F_%H%M%S)
docker compose exec -T db pg_dump -U glitchtip glitchtip | gzip > /opt/glitchtip/backups/glitchtip_$TS.sql.gz
ls -lh /opt/glitchtip/backups | tail

If the copy button does not work in your browser, select the code block and copy it manually.

Common issues and fixes

Certificate issuance fails

Usually DNS mismatch, blocked port 80, or stale AAAA record. Verify A/AAAA, remove wrong records, and ensure Caddy can receive ACME challenges over port 80.

Web starts but workers fail

Most often an env mismatch or database readiness race. Confirm both containers read the same .env values and that PostgreSQL healthcheck is healthy before worker startup.

Events not appearing from SDKs

Check DSN/project keys, outbound egress from application environments, and reverse proxy limits. If payloads are large, tune upstream request size limits and keep request body buffering predictable.

Performance degrades during bursts

Scale worker replicas first, then move PostgreSQL to managed high-IO storage if needed. Track queue depth and worker processing latency as primary saturation indicators. Add dashboards for container restart count, queue delay, and DB latency so incidents are visible before customers report impact.

Upgrades break unexpectedly

Use staged rollouts. Test image updates in staging with a restored backup, capture migration timing, and define rollback checkpoints. Never combine major version upgrades with unrelated infrastructure changes in the same maintenance window.

# Scale worker replicas for burst handling
docker compose up -d --scale worker=3

# inspect container resource pressure
docker stats --no-stream

# revert after incident if needed
docker compose up -d --scale worker=1

If the copy button does not work in your browser, select the code block and copy it manually.

FAQ

Can GlitchTip replace hosted Sentry for most teams?

For many teams, yes. GlitchTip is Sentry-compatible for common SDK workflows and offers strong value when data residency, cost control, or customization matter.

How much infrastructure do I need to start?

A single Ubuntu VM with 4 vCPU and 8 GB RAM works for many small-to-mid workloads. Increase worker replicas and database IOPS as event volume grows.

Should I expose PostgreSQL directly to the internet?

No. Keep PostgreSQL private on the Docker network or private subnet. Limit access to application containers and approved administrative channels only.

What is the safest backup approach?

Run frequent compressed pg_dump backups, store them off-host, encrypt at rest, and perform scheduled restore drills to a staging environment.

How do I handle upgrades with minimal risk?

Pin image tags, test upgrades in staging first, snapshot backups before production rollout, then upgrade during a maintenance window with rollback criteria.

Can I run this behind an existing load balancer instead of Caddy?

Yes. You can terminate TLS at your edge load balancer and pass traffic to GlitchTip directly, but keep strict header handling and redirect policies consistent.

How do I prevent sensitive data from entering error payloads?

Use SDK scrubbing rules, disable collection of unnecessary headers and body fields, and run periodic audits of stored events and tags.

Related guides

If you are building a broader reliability stack, these Guides are useful companions:

Talk to us

If you want this implemented with hardened defaults, observability, and tested recovery playbooks, our team can help.

Contact Us

Production Guide: Deploy Vaultwarden with Docker Compose + Caddy + PostgreSQL on Ubuntu
A production-focused Vaultwarden deployment with TLS, secret hygiene, backups, restore drills, and operational verification.