Skip to main content
If you have a Linux server — whether a cloud VPS from DigitalOcean, Hetzner, Linode, or a physical machine in your data center — you can run MergeWatch with Docker Compose. This gives you full control over the deployment with minimal dependencies.

Overview

This guide covers running MergeWatch and Postgres as Docker containers, setting up a reverse proxy with TLS using nginx or Caddy, and configuring the GitHub App webhook to point at your domain.

Prerequisites

1

Install Docker and Docker Compose

# Ubuntu / Debian
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
Verify the installation:
docker --version     # 20.10+
docker compose version   # 2.0+
2

Open port 443

Ensure your firewall allows inbound HTTPS traffic on port 443. If you use ufw:
sudo ufw allow 443/tcp
3

Point a domain at your server

Create a DNS A record pointing your domain (e.g. mergewatch.example.com) to your server’s public IP address. A domain is required for TLS certificates.
4

Gather your GitHub App credentials

VariableDescription
GITHUB_APP_IDNumeric App ID from the GitHub App settings page
GITHUB_PRIVATE_KEYPEM-formatted private key generated for the App
GITHUB_WEBHOOK_SECRETSecret used to validate incoming webhook payloads
5

Choose an LLM provider

Set LLM_PROVIDER to your preferred provider. For the default Anthropic provider, you also need ANTHROPIC_API_KEY.

Deploy with Docker Compose

1

Create the project directory

mkdir -p /opt/mergewatch && cd /opt/mergewatch
2

Create the docker-compose.yml

version: "3.8"

services:
  mergewatch:
    image: ghcr.io/santthosh/mergewatch:latest
    container_name: mergewatch
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - GITHUB_APP_ID=${GITHUB_APP_ID}
      - GITHUB_PRIVATE_KEY=${GITHUB_PRIVATE_KEY}
      - GITHUB_WEBHOOK_SECRET=${GITHUB_WEBHOOK_SECRET}
      - LLM_PROVIDER=${LLM_PROVIDER}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - DATABASE_URL=postgresql://mergewatch:${DB_PASSWORD}@postgres:5432/mergewatch
    depends_on:
      postgres:
        condition: service_healthy

  postgres:
    image: postgres:15-alpine
    container_name: mergewatch-postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=mergewatch
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=mergewatch
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U mergewatch"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  pgdata:
3

Create the .env file

cat > .env << 'EOF'
GITHUB_APP_ID=your_app_id
GITHUB_PRIVATE_KEY=your_private_key
GITHUB_WEBHOOK_SECRET=your_webhook_secret
LLM_PROVIDER=anthropic
ANTHROPIC_API_KEY=your_anthropic_key
DB_PASSWORD=a_strong_random_password
EOF
Protect the .env file. Set permissions to owner-only: chmod 600 .env
4

Start the services

docker compose up -d
Verify both containers are running:
docker compose ps
NAME                  STATUS          PORTS
mergewatch            Up 10 seconds   0.0.0.0:3000->3000/tcp
mergewatch-postgres   Up 12 seconds   5432/tcp

Set up Postgres

By default, the docker-compose.yml above runs PostgreSQL as a container alongside MergeWatch. Data is persisted to a Docker volume (pgdata).
If you prefer an external managed database, remove the postgres service from docker-compose.yml and set DATABASE_URL in your .env file to the external connection string.

Set up a reverse proxy with TLS

Expose MergeWatch on port 443 with TLS using nginx or Caddy. GitHub requires HTTPS for webhook delivery.
# /etc/nginx/sites-available/mergewatch
server {
    listen 443 ssl http2;
    server_name mergewatch.example.com;

    ssl_certificate     /etc/letsencrypt/live/mergewatch.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/mergewatch.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    server_name mergewatch.example.com;
    return 301 https://$host$request_uri;
}
Caddy automatically provisions and renews TLS certificates from Let’s Encrypt. With nginx, use certbot to obtain certificates: sudo certbot --nginx -d mergewatch.example.com
Enable the nginx site and reload:
sudo ln -s /etc/nginx/sites-available/mergewatch /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

Configure the webhook URL

Set the webhook URL on your GitHub App to your domain followed by /webhook:
https://mergewatch.example.com/webhook
Do not expose port 3000 directly to the internet without TLS. Always use a reverse proxy with HTTPS.

Next steps