Deployment

  • Docker — PullApprove5 is distributed as a Docker image
  • linux/amd64 (x86-64) — the image is x86-64 only; there is no ARM64 build, so it must run on an x86-64 host (it will not run natively on ARM, e.g. AWS Graviton or Apple Silicon)
  • PostgreSQL 16+ — used for data storage and as the background job queue
  • No license key or activation — once deployed, your instance is fully functional

Self-hosted architecture

A single PullApprove instance can integrate with one GitHub instance (and/or one GitLab instance). Connecting to multiple GitHub instances (e.g., both github.com and a GHES server) from a single PullApprove instance is not currently supported.

1. Get the Docker Image

PullApprove5 is distributed as a single linux/amd64 Docker image, pinned to an explicit version (e.g. pullapprove/pullapprove5:5.x.x — there is no latest tag). Obtain it whichever way fits your environment:

  • Private registry — we grant your organization read-only pull access to the image. Pull the version you need directly, or mirror it into your own registry or proxy it through your artifact manager (Artifactory, ECR, Harbor, Nexus, …).

  • Tarball — for offline or air-gapped installs, a .tar.gz you load and push into your own registry:

    docker load < pullapprove5-5.x.x.tar.gz
    docker tag pullapprove/pullapprove5:5.x.x your-registry.example.com/pullapprove5:5.x.x
    docker push your-registry.example.com/pullapprove5:5.x.x
    

2. Set Up PostgreSQL

PullApprove5 requires PostgreSQL 16 or later. No special extensions are required. Database size depends on your pull request volume but is generally modest — plan for tens of gigabytes rather than hundreds.

The connection string is set with PULLAPPROVE_POSTGRES_URL (see Configure and Deploy below).

Connection pooling (PgBouncer, RDS Proxy)

PullApprove5 runs safely behind a transaction-mode connection pooler. It binds parameters client-side (no server-side prepared statements) and uses only transaction-scoped locks, so nothing requires the pooler to pin connections.

When you use a pooler, point the runtime at it and set a second URL that connects to PostgreSQL directly for management operations. Migrations, schema convergence, and pg_dump run DDL and long transactions that a transaction-mode pooler can't handle, so they must bypass it:

# Runtime — through the pooler
PULLAPPROVE_POSTGRES_URL=postgresql://app@pgbouncer:6432/pullapprove
# Migrations, schema changes, pg_dump — direct to PostgreSQL
PULLAPPROVE_POSTGRES_MANAGEMENT_URL=postgresql://app@postgres:5432/pullapprove

When PULLAPPROVE_POSTGRES_MANAGEMENT_URL is set, the migrate command and other schema operations automatically use it while everything else uses PULLAPPROVE_POSTGRES_URL. Leave it unset if you connect to PostgreSQL directly.

3. Configure and Deploy

All configuration is done through environment variables. It is easiest to set the same env vars on all container instances, regardless of which command they run.

You'll need a server container (HTTP on port 8000) and a worker container. See Proxy Configuration for TLS termination and Container Commands for the full command reference.

The following Kubernetes manifests can give you an idea of the setup — adapt to your platform.

Environment variables:

apiVersion: v1
kind: Secret
metadata:
  name: pullapprove-env
stringData:
  # The full URL where your PullApprove5 instance is hosted
  PULLAPPROVE_BASE_URL: "https://pullapprove5.example.com"
  # Random secret key (at least 50 characters)
  # Generate with: python -c "import secrets; print(secrets.token_urlsafe(50))"
  PULLAPPROVE_SECRET_KEY: "your-random-secret-key"
  # JSON array of allowed hostnames
  PULLAPPROVE_ALLOWED_HOSTS: '["pullapprove5.example.com"]'
  # PostgreSQL connection string (add PULLAPPROVE_POSTGRES_MANAGEMENT_URL too if
  # you run behind a connection pooler — see "Set Up PostgreSQL" above)
  PULLAPPROVE_POSTGRES_URL: "postgresql://user:pass@host:5432/dbname"

  # Git provider env vars go here too (see GitHub/GitLab setup guides)
  # ...

Run database migrations on install and update (or during release/pre-deploy):

apiVersion: batch/v1
kind: Job
metadata:
  name: pullapprove-migrate
spec:
  template:
    spec:
      containers:
        - name: migrate
          image: your-registry.example.com/pullapprove5:5.x.x
          args: ["migrate"]
          envFrom:
            - secretRef:
                name: pullapprove-env
      restartPolicy: Never

Set a CPU limit. Both commands size their process pool to the CPU count they detect, so in Kubernetes a CPU request without a limit lets the container see the whole node and start more processes than its memory allows (OOMKills). The resources blocks below set one; alternatively, pin PULLAPPROVE_SERVER_WORKERS / PULLAPPROVE_JOBS_WORKER_MAX_PROCESSES.

Server (~1 GB memory) — serves the UI and receives webhooks:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pullapprove-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pullapprove
      component: server
  template:
    metadata:
      labels:
        app: pullapprove
        component: server
    spec:
      containers:
        - name: server
          image: your-registry.example.com/pullapprove5:5.x.x
          args: ["server"]
          ports:
            - containerPort: 8000
          livenessProbe:
            httpGet:
              path: /up/
              port: 8000
          readinessProbe:
            httpGet:
              path: /up/
              port: 8000
          envFrom:
            - secretRef:
                name: pullapprove-env
          resources:
            requests:
              cpu: "1"
              memory: 1Gi
            limits:
              cpu: "2" # caps the worker count (see note above)
              memory: 1Gi

Worker (~1 GB memory) — processes pull requests in the background:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: pullapprove-worker
spec:
  replicas: 1
  selector:
    matchLabels:
      app: pullapprove
      component: worker
  template:
    metadata:
      labels:
        app: pullapprove
        component: worker
    spec:
      containers:
        - name: worker
          image: your-registry.example.com/pullapprove5:5.x.x
          args: ["worker"]
          envFrom:
            - secretRef:
                name: pullapprove-env
          resources:
            requests:
              cpu: "1"
              memory: 1Gi
            limits:
              cpu: "2" # caps the job-process count (see note above)
              memory: 1Gi

4. Verify

Once your containers are running, you can verify the deployment by visiting the / URL. Before connecting a Git provider, the login page will show the PullApprove logo with no login options — this is expected.

Login page before connecting a Git provider

Next Steps

Connect your Git provider — each guide covers the required environment variables and webhook configuration:

For logging, monitoring, proxy configuration, and other operational topics, see the operations guide. For a security review, see the security guide.