> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pullbase.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Server Container Deployment

> Run the Pullbase central server using Docker or container orchestrators.

This guide covers deploying the **Pullbase central server** using containers. The central server coordinates environments, monitors Git repositories, and serves the web dashboard.

<Warning>
  **Agents should run natively on your Linux servers, not in containers.** Container agents require privileged access and have significant limitations. See [Agent Operations](/agent-operations) for native agent installation.

  This page is for deploying the **central server only**.
</Warning>

Pullbase publishes a container image for the central server (`pullbaseio/pullbase`). This guide shows how to deploy it with Docker Compose, but the same environment variables apply if you run the image under Kubernetes, Nomad, or another orchestrator.

## Prerequisites

* Docker 24.0+ with the Compose plugin (or an equivalent container runtime)
* PostgreSQL 15+ (container service or managed instance)
* Git repository containing your environment configuration (`config.yaml`)
* (Production) Reverse proxy for TLS termination

<Note>
  For production clusters, place PostgreSQL on managed infrastructure, store secrets in a vault, and terminate TLS at a reverse proxy. The examples below target a single host for clarity.
</Note>

## Directory layout

```
/opt/pullbase/
├── docker-compose.yml
├── .env
├── config/
│   └── github-app.pem   # optional, mounted read-only
└── logs/                # optional bind mount for log shipping
```

* `config/` holds the GitHub App private key or other secrets you mount read-only.
* `logs/` can be bound if you prefer file-based log collection; otherwise rely on `docker logs`.

## Compose template

```yaml docker-compose.yml theme={null}
services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: pullbaseuser
      POSTGRES_PASSWORD: pullbasepass
      POSTGRES_DB: pullbasedb
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U pullbaseuser -d pullbasedb']
      interval: 10s
      timeout: 5s
      retries: 5

  pullbase:
    image: pullbaseio/pullbase:latest
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      # Database
      PULLBASE_DB_HOST: db
      PULLBASE_DB_PORT: 5432
      PULLBASE_DB_USER: pullbaseuser
      PULLBASE_DB_PASSWORD: pullbasepass
      PULLBASE_DB_NAME: pullbasedb
      PULLBASE_DB_SSLMODE: disable
      # Server
      PULLBASE_SERVER_PORT: 8080
      PULLBASE_SERVER_HOST: 0.0.0.0
      # Authentication
      PULLBASE_JWT_SECRET: ${PULLBASE_JWT_SECRET}
      PULLBASE_JWT_EXPIRY_HOURS: 24
      # Webhooks
      PULLBASE_WEBHOOK_SECRET_KEY: ${PULLBASE_WEBHOOK_SECRET_KEY}
      # Git integration
      PULLBASE_GIT_ENABLED: ${PULLBASE_GIT_ENABLED:-false}
      PULLBASE_GIT_CLONE_PATH: /app/git-repos
      PULLBASE_GIT_POLL_INTERVAL: 60
      # GitHub App (when using private repos)
      PULLBASE_GITHUB_APP_ID: ${PULLBASE_GITHUB_APP_ID:-}
      PULLBASE_GITHUB_APP_PRIVATE_KEY_PATH: /config/github-app.pem
      PULLBASE_GITHUB_APP_API_BASE_URL: https://api.github.com
    volumes:
      - ./config:/config:ro
    ports:
      - '8080:8080'

volumes:
  postgres_data:
```

Populate secrets in `.env`:

```bash theme={null}
cat <<EOF > .env
 PULLBASE_JWT_SECRET=$(openssl rand -hex 32)
 PULLBASE_WEBHOOK_SECRET_KEY=$(openssl rand -hex 32)
 PULLBASE_GIT_ENABLED=false
EOF
```

## Launch the stack

<Steps>
  <Step title="Start services">
    ```bash theme={null}
    docker compose up -d
    ```
  </Step>

  <Step title="Verify health">
    ```bash theme={null}
    curl http://localhost:8080/api/v1/healthz
    ```
  </Step>

  <Step title="Tail logs">
    ```bash theme={null}
    docker compose logs -f pullbase
    ```
  </Step>
</Steps>

## TLS configuration

Pullbase supports two approaches for TLS in production.

### Option 1: Native TLS

Enable native TLS by adding these environment variables to your Compose file:

```yaml theme={null}
environment:
  PULLBASE_TLS_ENABLED: "true"
  PULLBASE_TLS_CERT_PATH: /config/server.crt
  PULLBASE_TLS_KEY_PATH: /config/server.key
```

Mount your certificates in the config volume:

```yaml theme={null}
volumes:
  - ./config:/config:ro  # Contains server.crt, server.key, github-app.pem
```

For development, you can start the server with `--generate-dev-certs` to auto-generate self-signed certificates.

### Option 2: Reverse proxy

For existing infrastructure, place Pullbase behind a reverse proxy that handles TLS:

### NGINX example

```nginx theme={null}
server {
    listen 443 ssl http2;
    server_name pullbase.example.com;
    
    ssl_certificate /etc/ssl/certs/pullbase.crt;
    ssl_certificate_key /etc/ssl/private/pullbase.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    
    location / {
        proxy_pass http://pullbase:8080;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}
```

### Traefik example

```yaml theme={null}
# Add to docker-compose.yml
traefik:
  image: traefik:v3.6.2
  command:
    - "--providers.docker=true"
    - "--entrypoints.websecure.address=:443"
    - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
    - "--certificatesresolvers.letsencrypt.acme.email=admin@example.com"
    - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
  ports:
    - "443:443"
  volumes:
    - /var/run/docker.sock:/var/run/docker.sock:ro
    - letsencrypt:/letsencrypt

# Add labels to pullbase service
pullbase:
  labels:
    - "traefik.enable=true"
    - "traefik.http.routers.pullbase.rule=Host(`pullbase.example.com`)"
    - "traefik.http.routers.pullbase.entrypoints=websecure"
    - "traefik.http.routers.pullbase.tls.certresolver=letsencrypt"
```

<Warning>
  Always use TLS in production. Agents transmit authentication tokens over the network, and the web UI handles user credentials.
</Warning>

## External databases

Using Amazon RDS, Azure Database for PostgreSQL, or another managed service?

1. Create the database and grant Pullbase a dedicated user.
2. Set `PULLBASE_DB_HOST`, `PULLBASE_DB_USER`, `PULLBASE_DB_PASSWORD`, and `PULLBASE_DB_NAME` to match the instance.
3. Enable TLS by setting `PULLBASE_DB_SSLMODE=require` or `PULLBASE_DB_SSLMODE=verify-full`.
4. Remove the `db` service from `docker-compose.yml`.

## Upgrades

* Pin the image tag (for example, `pullbaseio/pullbase:vX.Y.Z`) and update intentionally.
* Run `docker compose pull && docker compose up -d` to roll forward with minimal downtime.
* Review release notes for database migrations and watch container logs during the upgrade.

<Tip>
  Automate backups before upgrades: `docker compose exec db pg_dump -U pullbaseuser pullbasedb > backup.sql`. Managed databases often provide scheduled snapshots—enable them.
</Tip>
