# Agent Operations
Source: https://docs.pullbase.io/agent-operations
Deploy, configure, and troubleshoot the Pullbase agent on your servers.
The Pullbase agent enforces desired state on each managed server. It runs as a native binary managed by systemd — the recommended approach for VMs and bare-metal hosts.
## Deployment methods
### One-liner install script (recommended)
The fastest way to deploy an agent is using the install script endpoint. After registering a server in Pullbase, run:
```bash theme={null}
curl -fsSL "https://pullbase.example.com/api/v1/servers/web-01/install-script?token=pbt_xxx" | sudo bash
```
The script:
* Downloads the agent binary from GitHub releases
* Creates a dedicated `pullbase` service user with security restrictions
* Configures `/etc/pullbase/agent.env` with your server URL and token
* Installs a hardened systemd service
* Starts the agent immediately
Generate the install command from the UI: navigate to **Servers → \[your server] → Install** to get a ready-to-run curl command with the token pre-filled.
**Optional parameters:**
| Parameter | Description |
| --------- | ------------------------------------------------------------ |
| `version` | Specific agent version (e.g., `v1.0.0`). Defaults to latest. |
| `ca_cert` | Base64-encoded CA certificate for custom TLS. |
```bash theme={null}
# Install specific version with custom CA
curl -fsSL "https://pullbase.example.com/api/v1/servers/web-01/install-script?token=pbt_xxx&version=v1.2.0&ca_cert=$(base64 -w0 ca.crt)" | sudo bash
```
### Manual systemd setup
If you prefer manual control or need to customize the installation:
```bash theme={null}
curl -fsSL -o pullbase-agent "https://github.com/pullbase/pullbase/releases/latest/download/pullbase-agent-linux-amd64"
sudo install -m 0755 pullbase-agent /usr/local/bin/pullbase-agent
```
```bash theme={null}
sudo mkdir -p /etc/pullbase
sudo tee /etc/pullbase/agent.env > /dev/null <<'EOF'
SERVER_ID=web-01
CENTRAL_SERVER_URL=https://pullbase.example.com
AGENT_TOKEN=pbt_your_token_here
CACERT_PATH=/etc/pullbase/ca.crt
SKIP_TLS_VERIFY=false
EOF
sudo chmod 600 /etc/pullbase/agent.env
```
```ini /etc/systemd/system/pullbase-agent.service theme={null}
[Unit]
Description=Pullbase Agent
After=network-online.target
Wants=network-online.target
[Service]
EnvironmentFile=/etc/pullbase/agent.env
ExecStart=/usr/local/bin/pullbase-agent
Restart=always
RestartSec=10
User=root
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/etc /var
[Install]
WantedBy=multi-user.target
```
```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now pullbase-agent.service
sudo systemctl status pullbase-agent.service
```
## Environment variables
| Variable | Required | Default | Description |
| -------------------- | -------- | ------- | ------------------------------------------------------------------- |
| `SERVER_ID` | Yes | - | Must match the server ID registered in Pullbase |
| `CENTRAL_SERVER_URL` | Yes | - | Base URL of the Pullbase API (e.g., `https://pullbase.example.com`) |
| `AGENT_TOKEN` | Yes | - | Token provided when the server is created. Rotate regularly. |
| `CACERT_PATH` | No | - | Path to CA bundle that trusts the Pullbase certificate |
| `SKIP_TLS_VERIFY` | No | `false` | Set to `true` only for development with self-signed certificates |
| `AGENT_DRY_RUN` | No | `false` | When `true`, agent reports what would change without applying |
The agent also supports legacy environment variable names with `PULLBASE_` prefix: `PULLBASE_SERVER_ID`, `PULLBASE_SERVER_URL`, `PULLBASE_AGENT_TOKEN`, `PULLBASE_CACERT_PATH`.
### Dry-run mode
Run the agent in dry-run mode to preview changes without applying them:
```bash theme={null}
# Via environment variable
AGENT_DRY_RUN=true pullbase-agent
# Via command-line flag
pullbase-agent --dry-run
```
In dry-run mode, the agent:
* Checks for configuration drift
* Logs what packages, files, and services would change
* Reports status to the server with "Dry-Run:" prefix
* **Does not apply any changes**
This is ideal for testing new configurations or auditing drift before enabling auto-reconcile.
## Supported package managers
The agent auto-detects the host's package manager:
| Package Manager | Distribution | Detection |
| --------------- | ---------------------------- | ----------------------------------- |
| APK | Alpine Linux | Checks for `apk` command |
| APT | Debian, Ubuntu | Checks for `apt-get` command |
| DNF | RHEL 8+, Fedora, Rocky Linux | Checks for `dnf` command |
| YUM | RHEL 7, CentOS 7 | Checks for `yum` command (fallback) |
Package states:
* `present`: Install if not present
* `latest`: Install or upgrade to latest available version
* `absent`: Remove if installed
## Supported service managers
The agent auto-detects the init system:
| Service Manager | Init System | Detection |
| --------------- | ------------------------- | ------------------------------------------------ |
| systemd | Most modern distributions | Checks for `systemctl` and `/run/systemd/system` |
| supervisor | Docker, custom setups | Checks for `supervisorctl` |
| OpenRC | Alpine Linux, Gentoo | Checks for `rc-service` |
Override detection by setting `system.serviceManager` in `config.yaml`:
```yaml theme={null}
system:
serviceManager: supervisor
containerized: true
```
## Reconciliation cycle
Agent authenticates with its token and calls `GET /api/v1/agent/serverinfo` to obtain Git metadata and the target commit hash.
If the environment uses a GitHub App, agent invokes `GET /api/v1/agent/git-token` to retrieve a short-lived installation token.
Repository is cloned into `/etc/pullbase/repo`. The agent checks out the target commit.
Packages, services, and files from `config.yaml` are reconciled using the detected package and service managers.
Agent posts results (status, drift, error message) to `PUT /api/v1/agent/status`.
## Drift detection and auto-reconciliation
The agent performs drift checks every 60 seconds and compares:
* Package installation state
* Service running/enabled state
* File content (SHA256 hash) and permissions
When drift is detected and auto-reconcile is enabled for the server's environment, the agent automatically applies the configuration to restore desired state.
## Logging
View agent logs using journalctl:
```bash theme={null}
# Follow logs in real-time
journalctl -u pullbase-agent -f
# Last 100 lines
journalctl -u pullbase-agent -n 100
# Since last boot
journalctl -u pullbase-agent -b
```
The agent logs reconciliation details including:
* Package installation/removal operations
* Service start/stop/enable/disable operations
* File writes and permission changes
* Drift detection results
* Git clone/pull operations
## Troubleshooting
* Token revoked or expired → create a new token and update `/etc/pullbase/agent.env`
* Server ID mismatch → ensure `SERVER_ID` matches the registered server
* Clock skew → ensure NTP is running; JWT validation is time-based
* Check network reachability to GitHub (proxy, firewall)
* For private repos, confirm the GitHub App installation and permissions
* Validate `CACERT_PATH` so TLS trust is correct
* Ensure the package manager is available and in PATH
* Check that package repositories are configured and reachable
* Verify the agent runs as root (required for package management)
* Override auto-detection by setting `system.serviceManager` in `config.yaml`
* Ensure the service manager commands (`systemctl`, `supervisorctl`, `rc-service`) are in PATH
* For systemd, verify `/run/systemd/system` exists
* Specify `mode` in `config.yaml` (e.g., `"0644"`)
* Confirm the agent runs as root (required to manage system files)
* Check SELinux/AppArmor policies if files are created but inaccessible
* Check connectivity: `curl -I https://pullbase.example.com/api/v1/healthz`
* Verify token is active in the Pullbase UI
* Check agent logs for authentication errors
## Upgrading the agent
To upgrade an agent to a newer version:
```bash theme={null}
# Stop the agent
sudo systemctl stop pullbase-agent
# Download new version
curl -fsSL -o /tmp/pullbase-agent "https://github.com/pullbase/pullbase/releases/latest/download/pullbase-agent-linux-amd64"
sudo install -m 0755 /tmp/pullbase-agent /usr/local/bin/pullbase-agent
# Start the agent
sudo systemctl start pullbase-agent
# Verify version in logs
journalctl -u pullbase-agent -n 5
```
Or re-run the install script — it handles upgrades automatically.
## Uninstalling the agent
```bash theme={null}
sudo systemctl stop pullbase-agent
sudo systemctl disable pullbase-agent
sudo rm /etc/systemd/system/pullbase-agent.service
sudo systemctl daemon-reload
sudo rm /usr/local/bin/pullbase-agent
sudo rm -rf /etc/pullbase
```
# Architecture Overview
Source: https://docs.pullbase.io/architecture-overview
See how the Pullbase server, agents, database, and Git repositories work together.
Pullbase orchestrates Git-driven configuration for traditional servers. Agents pull desired state from the central server, apply it locally, and report the results.
## Components
Go application that exposes the REST API, web UI, and webhook endpoints. It manages environments, servers, tokens, audit logs, and status history.
SQLite (default) for zero-config deployment, or PostgreSQL for scale. Stores configuration and operational data. Migrations run automatically on startup.
Lightweight binary that authenticates with a scoped token, clones the config repo, reconciles packages/services/files, and reports drift.
Contains `config.yaml` files. Environments point to a repo, branch, and path. Pullbase reads Git to compute the target commit hash.
## Server internals
* **HTTP stack:** REST APIs with middleware for authentication, CSRF protection, structured logging, and request tracing.
* **Data access:** Database repository layer (SQLite or PostgreSQL) that stores environments, servers, tokens, rollbacks, users, and audit history.
* **Git monitor:** Watches repositories for new commits via webhooks or polling and keeps the environment target commit current.
* **Webhook router:** Validates HMAC signatures, queues events, and updates environment target commits immediately.
* **Audit logging:** Actions like user creation, token issuance, and rollbacks are persisted for later review.
## Agent internals
The agent is built with pluggable package and service managers:
**Package managers (auto-detected):**
* APK (Alpine Linux)
* APT (Debian, Ubuntu)
* YUM/DNF (RHEL, CentOS, Rocky Linux, Fedora)
**Service managers (auto-detected):**
* systemd (most modern distributions)
* supervisor (Docker containers, custom setups)
* OpenRC (Alpine Linux, Gentoo)
The `system.serviceManager` field in `config.yaml` can override auto-detection when needed.
## Agent workflow
1. Fetch public server info from `GET /api/v1/serverinfo/{serverID}` to get Git configuration.
2. Authenticate with `AGENT_TOKEN` and fetch full server info via `GET /api/v1/agent/serverinfo`.
3. Obtain a Git credential via `GET /api/v1/agent/git-token` if the environment uses a GitHub App.
4. Clone or update the repository under `/etc/pullbase/repo`.
5. Parse `config.yaml` and reconcile packages, services, and files using the detected managers.
6. Post a status update to `PUT /api/v1/agent/status`, including commit hash, drift flag, and error messages.
## Data flow overview
```text theme={null}
Git commit -> Webhook/polling updates target commit -> Agent polls server info -> Agent applies state -> Agent posts status
```
* Merging a commit triggers a webhook (or is detected via polling).
* Pullbase stores the new `deployed_commit` for the environment.
* Agents poll on a fixed 60-second interval (configurable via `PULLBASE_GIT_POLL_INTERVAL`).
* Agents reconcile to the new commit and report status history entries.
* Operators review drift, roll back, or adjust configuration via Git.
* Notification webhooks fire for drift or apply errors (when configured).
## Network architecture
Pullbase supports two TLS approaches: (A) reverse proxy termination, or (B) native TLS via `PULLBASE_TLS_ENABLED=true`. For production, always use TLS. Agents connect to the HTTPS endpoint regardless of which approach you choose.
## Scaling considerations
* **Single-node:** SQLite is the default — no external database required. Perfect for single-instance deployments.
* **Production scale:** Switch to PostgreSQL (`PULLBASE_DB_TYPE=postgres`) when you need high availability or manage hundreds of servers.
* **High availability:** Run multiple Pullbase replicas behind a load balancer (requires PostgreSQL). Sessions are JWT-based and stateless.
* **Networking:** Agents require outbound HTTPS to the reverse proxy and Git provider. Pullbase requires outbound HTTPS to Git provider (for GitHub Apps and webhooks).
## Rollback mechanics
* Rollbacks (`POST /api/v1/environments/{id}/rollback`) create a record, set the environment's deployed commit to the selected hash, and notify agents on their next poll.
* Agents revert state to the specified commit. Package downgrades rely on package manager capabilities; test in staging before production rollbacks.
## Observability hooks
* **Logs:** Central server logs are structured (JSON when configured). Agents log reconciliation details at info/debug levels.
* **Health endpoint:** `/api/v1/healthz` returns status and service name.
* **Future metrics:** Prometheus metrics are on the roadmap; integrate container logs with your monitoring pipeline meanwhile.
Understanding these components prepares you for installation, environment management, and operational tasks described in subsequent guides.
# Bootstrapping & Authentication
Source: https://docs.pullbase.io/bootstrapping
Create the first admin, onboard additional users, and understand Pullbase authentication flows.
Pullbase ships without default credentials. Use the bootstrap secret to create the first administrator, then manage users through the CLI, API, or web UI.
For a more detailed walkthrough of authentication and user management, see the [CLI Guide](/guides/pullbasectl#authentication).
## Bootstrap workflow
```bash theme={null}
docker compose exec central-server cat /app/secrets/bootstrap.secret
```
The secret is single-use. Copy it carefully and avoid storing it in plaintext documents.
```bash theme={null}
docker compose exec central-server pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret-file /app/secrets/bootstrap.secret \
--username admin_user \
--password 'ChangeMeNow123!'
```
The command returns a JSON payload containing a short-lived `access_token`. Test it immediately:
```bash theme={null}
curl -H "Authorization: Bearer ACCESS_TOKEN" \
http://localhost:8080/api/v1/auth/me
```
The server deletes the bootstrap secret file after a successful bootstrap. Remove any notes or terminals that still display the secret.
## Bootstrap via environment variable
Alternatively, provide the bootstrap secret via environment variable instead of a file:
```yaml theme={null}
environment:
PULLBASE_BOOTSTRAP_SECRET: your-secret-here
```
This is useful in orchestrated environments where mounting files is inconvenient.
## Managing users
### CLI
```bash Create a user theme={null}
pullbasectl users create \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--new-username ops_user \
--new-password 'StrongPassword!2024' \
--role viewer
```
```bash List users theme={null}
pullbasectl users list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--role admin \
--limit 50
```
```bash Delete a user theme={null}
pullbasectl users delete \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--user-id 7 \
--delete-acct-username 'SomeAccount123'
```
### Web UI
1. Sign in at `http://localhost:8080` (or your production URL with TLS).
2. Navigate to **Settings → Users**.
3. Use **Add user** to provision new operators.
4. Delete a user by clicking the trash icon and typing the username to confirm.
Pullbase prevents deleting the last active admin or your own account. Promote another admin before deactivating the original bootstrap user.
## Authentication model
* **Admins/Users:** Authenticate with username/password. The server issues JWT access tokens signed with `PULLBASE_JWT_SECRET`.
* **Agents:** Authenticate with agent tokens scoped to a single server. Tokens are hashed at rest and shown only once at creation time. Prefixed with `pbt_`.
* **GitHub App:** Uses App ID, private key, installation ID, and repository ID to mint short-lived installation tokens for agents.
### Token lifetime
* API tokens expire based on `PULLBASE_JWT_EXPIRY_HOURS` (default 24 hours).
* Agent tokens can be set to expire (`--expires-in` when creating via CLI) or rotated manually.
### Session management
* The web UI stores the JWT in an HTTP-only cookie.
* Sign out from the avatar menu or let the token expire naturally.
* To revoke all sessions, rotate `PULLBASE_JWT_SECRET` and restart Pullbase (forces logout for every user).
## User roles
| Role | Permissions |
| -------- | -------------------------------------------------------- |
| `admin` | Full access: manage users, environments, servers, tokens |
| `user` | Manage environments and servers, view users |
| `viewer` | Read-only access to all resources |
Capture the bootstrap workflow and admin creation steps in your internal runbooks. In an incident you may need to redeploy Pullbase and bootstrap quickly.
# Core Concepts
Source: https://docs.pullbase.io/concepts
Key terminology and mental models for working with Pullbase.
Understanding Pullbase vocabulary helps you map Git content to running infrastructure.
## Environment
A logical grouping of servers that share Git metadata (repository URL, branch, deploy path). Environments are defined in the central server and inherited by every server assigned to them.
* **What it stores:** repository URL, branch, deploy path, the auto-reconcile setting, and optional GitHub App details (installation ID, repository ID, app slug)
* **When to use it:** group servers by lifecycle stage (dev, staging, prod), region, or customer workload
## Server
Represents a managed node. Each server has a unique ID (`SERVER_ID`), belongs to one environment, and possesses its own status history.
* Agents authenticate as their server using an agent token
* Pullbase records every reconciliation so you can review drift and error messages later
* Deleting a server revokes tokens and clears history
## Agent token
A credential generated when you register a server. After creation, it's hashed in the database and only shown once.
* Generate tokens via the UI, CLI, or API
* Optionally set an expiration and rotate them regularly
* Required in the `Authorization: Bearer` header for agent endpoints
* Prefixed with `pbt_` for easy identification
## Desired state (`config.yaml`)
The declarative specification of packages, services, and files for an environment. The agent parses this file and enforces it on the host.
* Supports reload hooks (service restarts) and file permission settings
* Can reference shared files within the repository using `source` attribute
* Supports `system` section to override service manager detection
## Drift
Any divergence between desired state and actual state. Agents detect drift during reconciliation and flag it in status updates.
* Drift reasons include missing packages, changed file contents, or disabled services
* View drift in the web UI or via `GET /api/v1/servers/{id}/status/history`
* When auto-reconcile is enabled, drift is automatically corrected
## Rollback event
Record of reverting an environment to a previous commit via `POST /api/v1/environments/{id}/rollback`.
* Stores `from_commit`, `to_commit`, `initiated_by`, and `reason`
* Agents reconcile automatically after the target commit changes
## Bootstrap secret
One-time secret generated on startup (`bootstrap-admin-secret.txt` in the config directory). Allows the first admin to be created without pre-baked credentials.
* Removed automatically after first use
* Keep it secure and delete any copies once bootstrap is complete
* Can be provided via `PULLBASE_BOOTSTRAP_SECRET` environment variable
## GitHub App integration
Optional mechanism for private Git repositories. Pullbase uses GitHub Apps to obtain short-lived installation tokens for agents.
* After configuration, the central server signs JWTs to call GitHub's `/app/installations/{id}/access_tokens`
* Agents never store long-lived credentials
* Tokens expire after one hour and are refreshed automatically
## Webhook router
Component that receives Git provider webhooks, validates signatures, and updates environment target commits immediately.
* Validates HMAC signatures using `PULLBASE_WEBHOOK_SECRET_KEY`
* Supports GitHub push events
* Faster than polling for detecting new commits
## Package manager
The agent auto-detects the system's package manager to install, update, and remove packages:
* **APK** for Alpine Linux
* **APT** for Debian/Ubuntu
* **YUM/DNF** for RHEL, CentOS, Rocky Linux, Fedora
## Service manager
The agent auto-detects the init system to manage services:
* **systemd** for most modern Linux distributions
* **supervisor** for Docker containers or custom setups
* **OpenRC** for Alpine Linux
Override detection by setting `system.serviceManager` in `config.yaml`.
Keep these concepts in mind as you move through installation and operations—they anchor the terminology used in the CLI, API, and web UI.
# Configuration Repository Guide
Source: https://docs.pullbase.io/configuration-repository
Structure your Git repository and author config.yaml files for Pullbase agents.
Pullbase reads your Git repository to determine the desired state for each environment. By convention you store a `config.yaml` file at the root of the environment directory.
## Repository layout
```
configs/
├── environments/
│ ├── production/
│ │ └── config.yaml
│ └── staging/
│ └── config.yaml
├── shared/
│ ├── nginx.conf
│ └── scripts/
└── README.md
```
* Keep environment-specific files under `environments/`
* Store shared templates or scripts outside the environment folder
* Reference shared files from `config.yaml` using relative paths
## config.yaml schema
The agent parses this file to reconcile packages, services, and files on the managed host.
```yaml config.yaml theme={null}
serverMetadata:
name: web-01
environment: production
packages:
- name: nginx
state: latest
- name: curl
state: present
- name: vim
state: absent
services:
- name: nginx
enabled: true
state: running
managed: true
files:
- path: /etc/nginx/nginx.conf
content: |
user nginx;
worker_processes auto;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
server {
listen 80;
location / {
return 200 'Hello from Pullbase';
}
}
}
mode: "0644"
reloadService: nginx
system:
serviceManager: systemd
containerized: false
```
### Sections explained
#### `serverMetadata`
Optional metadata that appears in the UI and log entries.
| Field | Type | Description |
| ------------- | ------ | ---------------------------------- |
| `name` | string | Human-readable server name |
| `environment` | string | Environment identifier for logging |
#### `packages`
Package manager operations. The agent auto-detects the package manager (APK, APT, YUM/DNF) based on the host OS.
| Field | Type | Values | Description |
| ------- | ------ | ----------------------------- | --------------------- |
| `name` | string | - | Package name |
| `state` | string | `present`, `latest`, `absent` | Desired package state |
* `present`: Install if missing
* `latest`: Install or update to latest version
* `absent`: Remove if installed
#### `services`
Service management using the detected or configured service manager.
| Field | Type | Default | Description |
| --------- | ------- | ------- | ------------------------------------------------ |
| `name` | string | - | Service name |
| `enabled` | boolean | - | Start on boot |
| `state` | string | - | `running` or `stopped` |
| `managed` | boolean | `true` | Set to `false` to observe without altering state |
#### `files`
File content management with optional service reload triggers.
| Field | Type | Description |
| --------------- | ------ | ---------------------------------------------------- |
| `path` | string | Absolute path on the target system |
| `content` | string | File content (inline) |
| `source` | string | Relative path in the repo (alternative to `content`) |
| `mode` | string | File permissions in octal (e.g., `"0644"`) |
| `reloadService` | string | Service to reload/restart when file changes |
| `reloadCommand` | string | Custom command to run when file changes |
#### `system`
Optional system configuration for the agent.
| Field | Type | Values | Description |
| ---------------- | ------- | --------------------------------- | -------------------------------------- |
| `serviceManager` | string | `systemd`, `supervisor`, `openrc` | Override auto-detected service manager |
| `containerized` | boolean | `true`/`false` | Indicate if running in a container |
Large files can be committed alongside `config.yaml` and referenced with the `source` attribute instead of inline `content`.
### Example with source files
```yaml theme={null}
files:
- path: /etc/nginx/nginx.conf
source: ../shared/nginx.conf
reloadService: nginx
```
Ensure the relative path exists in the repository. The agent copies the file to the target location during reconciliation.
### Example with reload command
```yaml theme={null}
files:
- path: /etc/myapp/config.json
content: |
{"debug": false, "port": 3000}
mode: "0640"
reloadCommand: systemctl reload myapp
```
## Supported package managers
The agent auto-detects and supports:
| Package Manager | Distribution |
| --------------- | ---------------------------- |
| APK | Alpine Linux |
| APT | Debian, Ubuntu |
| YUM | RHEL, CentOS 7 |
| DNF | RHEL 8+, Fedora, Rocky Linux |
## Supported service managers
The agent auto-detects and supports:
| Service Manager | Init System |
| --------------- | -------------------------------- |
| systemd | Most modern Linux distributions |
| supervisor | Docker containers, custom setups |
| OpenRC | Alpine Linux, Gentoo |
Override auto-detection using the `system.serviceManager` field when the agent runs in an environment where detection fails (e.g., containers without full init).
## Secrets management
* Avoid committing secrets to Git. Store them in your secret manager and inject them at runtime (for example, via environment variables or file mounts).
* If you must reference credentials, use encrypted files and have the agent decrypt them in a post-processing step.
* Configure package repositories to use system-level credentials (e.g., `/etc/apt/auth.conf`) rather than embedding tokens in `config.yaml`.
## Branching strategy
* Use one branch per promotion stage (for example, `main` → `staging` → `production`).
* Add Pullbase environments pointing to the relevant branch and deploy path.
* Protect branches with pull requests and CI validation to ensure the desired state compiles.
## Testing configuration changes
1. Update `config.yaml` and related files in a feature branch.
2. Validate YAML syntax locally with a linter.
3. Merge to the environment branch.
4. Trigger a webhook or rely on polling to publish the new commit.
The agent includes built-in validation for YAML structure, but semantic checks (such as verifying package names) depend on the target OS. Test changes in staging before promoting to production environments.
# Bare-Metal Deployment
Source: https://docs.pullbase.io/deployment-bare-metal
Run the Pullbase server and agent directly on VMs or physical hosts.
Deploy Pullbase without containers by running the server and agent binaries directly with systemd. This is the recommended approach for production Linux servers.
## Prerequisites
* Ubuntu 22.04+, Debian 12, Rocky Linux 9, or a similar systemd-based distribution
* `curl` and `openssl`
* (Production) Reverse proxy for TLS termination or certificates for native TLS
**Database:** Pullbase uses SQLite by default — no external database required. For high-availability deployments, PostgreSQL 15+ is recommended.
## Configure the server
Create a directory for configuration:
```bash theme={null}
sudo mkdir -p /etc/pullbase
sudo mkdir -p /var/lib/pullbase/git-repos
```
## Install the server
Download the latest release with the web UI embedded:
```bash theme={null}
curl -fsSL -o pullbase-server "https://github.com/pullbase/pullbase/releases/latest/download/pullbase-server-linux-amd64"
sudo install -m 0755 pullbase-server /usr/local/bin/pullbase-server
```
For ARM64 systems, use `pullbase-server-linux-arm64`.
The CLI tool is used for bootstrapping, managing servers, and automation:
```bash theme={null}
curl -fsSL -o pullbasectl "https://github.com/pullbase/pullbase/releases/latest/download/pullbasectl-linux-amd64"
sudo install -m 0755 pullbasectl /usr/local/bin/pullbasectl
```
For ARM64 systems, use `pullbasectl-linux-arm64`.
Verify the installation:
```bash theme={null}
pullbasectl --help
```
```bash theme={null}
curl -fsSL "https://github.com/pullbase/pullbase/releases/latest/download/migrations.tar.gz" | sudo tar -xzf - -C /var/lib/pullbase/
```
Create an environment file with secrets and connection details:
```bash theme={null}
cat <
Store long-lived secrets in a vault or parameter store and template this environment file during provisioning.
### Using PostgreSQL instead
For high-availability or large-scale deployments, use PostgreSQL:
```bash theme={null}
# First, create the PostgreSQL database
sudo -u postgres psql <<'SQL'
CREATE ROLE pullbaseuser WITH LOGIN PASSWORD 'change-me';
CREATE DATABASE pullbasedb OWNER pullbaseuser;
GRANT ALL PRIVILEGES ON DATABASE pullbasedb TO pullbaseuser;
SQL
```
Then update `/etc/pullbase/pullbase.env`:
```bash theme={null}
# Database configuration (PostgreSQL)
PULLBASE_DB_TYPE=postgres
PULLBASE_DB_HOST=localhost
PULLBASE_DB_PORT=5432
PULLBASE_DB_USER=pullbaseuser
PULLBASE_DB_PASSWORD=change-me
PULLBASE_DB_NAME=pullbasedb
PULLBASE_DB_SSLMODE=disable
```
## Create a systemd service
Create the service user:
```bash theme={null}
sudo useradd --system --home /var/lib/pullbase --shell /usr/sbin/nologin pullbase
sudo chown -R pullbase:pullbase /var/lib/pullbase
```
Create the systemd unit:
```ini /etc/systemd/system/pullbase.service theme={null}
[Unit]
Description=Pullbase Server
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
EnvironmentFile=/etc/pullbase/pullbase.env
ExecStart=/usr/local/bin/pullbase-server
Restart=on-failure
RestartSec=5
User=pullbase
Group=pullbase
WorkingDirectory=/var/lib/pullbase
[Install]
WantedBy=multi-user.target
```
Enable and start the service:
```bash theme={null}
sudo systemctl daemon-reload
sudo systemctl enable --now pullbase.service
sudo systemctl status pullbase.service
```
Verify the health endpoint: `curl http://localhost:8080/api/v1/healthz`
## Bootstrap the admin
On first start, Pullbase generates a one-time bootstrap secret. Use it to create your admin account:
```bash theme={null}
# Read the bootstrap secret
sudo cat /var/lib/pullbase/config/bootstrap-admin-secret.txt
# Create the admin user
pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret "YOUR_SECRET" \
--username admin \
--password 'YourSecurePassword123!'
```
The bootstrap secret file is automatically deleted after successful admin creation.
Alternatively, use the secret file directly:
```bash theme={null}
pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret-file /var/lib/pullbase/config/bootstrap-admin-secret.txt \
--username admin \
--password 'YourSecurePassword123!'
```
## TLS configuration
Pullbase supports two approaches for TLS:
### Option 1: Native TLS (simpler)
Enable native TLS by adding these variables to `/etc/pullbase/pullbase.env`:
```bash theme={null}
PULLBASE_TLS_ENABLED=true
PULLBASE_TLS_CERT_PATH=/etc/pullbase/certs/server.crt
PULLBASE_TLS_KEY_PATH=/etc/pullbase/certs/server.key
```
For development, generate self-signed certificates:
```bash theme={null}
pullbase-server --generate-dev-certs
```
**Use CA-signed certificates in production.**
### Option 2: Reverse proxy (for existing infrastructure)
Place Pullbase behind a reverse proxy that terminates TLS:
```nginx /etc/nginx/sites-available/pullbase 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://127.0.0.1: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;
}
}
```
## Install agents on managed servers
Once the central server is running, install agents on each server you want to manage.
### Recommended: Install script
After creating a server and token in the Pullbase UI, run the install script on each managed server:
```bash theme={null}
curl -fsSL "https://pullbase.example.com/api/v1/servers/web-01/install-script?token=pbt_xxx" | sudo bash
```
The script downloads the agent, creates a systemd service, and starts it automatically.
Get the complete install command from the UI: **Servers > \[your server] > Install**
### Manual installation
For more control, see [Agent Operations](/agent-operations#manual-systemd-setup) for step-by-step manual installation.
## Next steps
* [Bootstrap your first admin](/bootstrapping) using the generated secret
* [Integrate a GitHub App](/github-app-integration) if you pull from private repositories
* Review [Security & Hardening](/security-hardening) to lock down secrets, TLS, and network access
***
## Building from source (optional)
If you need to build Pullbase yourself (for development or custom builds):
Install Go 1.22+ and Node.js 20+.
```bash theme={null}
git clone https://github.com/pullbase/pullbase.git
cd pullbase
./scripts/build-with-ui.sh
```
The server binary (with embedded UI) is written to `bin/pullbase-server`.
```bash theme={null}
cd agent
go build -o pullbase-agent
```
# Server Container Deployment
Source: https://docs.pullbase.io/deployment-container
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.
**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**.
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
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.
## 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 < .env
PULLBASE_JWT_SECRET=$(openssl rand -hex 32)
PULLBASE_WEBHOOK_SECRET_KEY=$(openssl rand -hex 32)
PULLBASE_GIT_ENABLED=false
EOF
```
## Launch the stack
```bash theme={null}
docker compose up -d
```
```bash theme={null}
curl http://localhost:8080/api/v1/healthz
```
```bash theme={null}
docker compose logs -f pullbase
```
## 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"
```
Always use TLS in production. Agents transmit authentication tokens over the network, and the web UI handles user credentials.
## 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.
Automate backups before upgrades: `docker compose exec db pg_dump -U pullbaseuser pullbasedb > backup.sql`. Managed databases often provide scheduled snapshots—enable them.
# Managing Environments & Servers
Source: https://docs.pullbase.io/environments-and-servers
Create environments, register servers, and manage agent tokens in Pullbase.
Environments and servers are the core records managed by the central server. This page covers lifecycle actions through the UI, CLI, and API.
## Environments
Each environment links a Git repository to a Pullbase deployment target.
### Create via web UI
Sign in and choose **Environments** from the sidebar.
Provide a name (for example, `staging`) and optional description.
* Repository URL: `https://github.com/your-org/configs.git`
* Branch: `main`
* Deploy path: `environments/staging/config.yaml`
* Auto-reconcile: enabled by default
Supply installation ID, repository ID, and app slug if the repo is private.
After saving, the environment appears in the list. If webhooks are configured, Pullbase receives push events; otherwise it polls periodically.
### Create via API
```bash cURL theme={null}
curl -X POST http://localhost:8080/api/v1/environments \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "staging",
"description": "staging web tier",
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"auto_reconcile": true
}'
```
## Servers
Servers represent managed nodes (VMs, bare-metal hosts, or containers) that run the Pullbase agent.
### Register via API
```bash theme={null}
curl -X POST http://localhost:8080/api/v1/servers \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id": "web-01",
"name": "staging-web-01",
"environment_id": 5
}'
```
The response includes an agent token. Store it securely and set `AGENT_TOKEN` for the agent deployment.
### List servers and status
```bash theme={null}
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:8080/api/v1/servers
```
### Remove a server
```bash cURL theme={null}
curl -X DELETE http://localhost:8080/api/v1/servers/web-01 \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
Deleting a server revokes its tokens and clears status history.
## Agent tokens
Tokens authenticate agents; each server can have multiple active tokens for rotation.
### Token management via CLI
* **List:** `pullbasectl tokens list --server-url http://localhost:8080 --admin-token $ADMIN_JWT --server-id web-01`
* **Create:** `pullbasectl tokens create --server-url http://localhost:8080 --admin-token $ADMIN_JWT --server-id web-01 --description "blue-green" --expires-in 30`
* **Revoke:** `pullbasectl tokens revoke --server-url http://localhost:8080 --admin-token $ADMIN_JWT --server-id web-01 --token-id 17`
### Token fields
| Field | Description |
| -------------- | ------------------------------------------------------------- |
| `id` | Token identifier used for revocation |
| `description` | Free-form label ("staging deployment", "ansible integration") |
| `expires_at` | Expiration timestamp (optional) |
| `is_active` | Indicates whether the token can be used |
| `last_used_at` | Last time the token was used for authentication |
## Automatic vs manual reconciliation
* **Auto-reconcile enabled:** Pullbase notifies agents of new commits. Agents reconcile on their next poll automatically.
* **Auto-reconcile disabled:** Pullbase records the new commit, but agents observe without applying changes until auto-reconcile is re-enabled.
Toggle auto-reconcile via the UI or API (`POST /api/v1/environments/{id}/toggle-auto-reconcile`).
## Rollbacks
Rollback from the environment detail page or via `POST /api/v1/environments/{id}/rollback`:
```bash cURL theme={null}
curl -X POST http://localhost:8080/api/v1/environments/5/rollback \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"to_commit": "8a9d3cba2f...",
"reason": "Reverting faulty nginx config"
}'
```
Pullbase creates a rollback event and updates the target commit. Agents reconcile to the specified commit on their next poll.
## Webhook notifications
Pullbase can send HTTP notifications when drift is detected or apply errors occur. Configure webhooks per environment.
### Configure via API
```bash theme={null}
curl -X PUT http://localhost:8080/api/v1/environments/5 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"notification_webhook_url": "https://hooks.slack.com/services/xxx/yyy/zzz"
}'
```
### Webhook payload
```json theme={null}
{
"event": "drift_detected",
"environment_id": 5,
"environment_name": "staging",
"server_id": "web-01",
"commit_hash": "abc123...",
"timestamp": "2026-01-01T12:00:00Z",
"details": {
"has_drift": true,
"error_message": ""
}
}
```
**Event types:**
* `drift_detected`: Agent detected configuration drift
* `apply_error`: Agent encountered an error applying configuration
* `test`: Sent when testing the webhook endpoint
### Test webhook
Verify your webhook configuration before relying on it:
```bash theme={null}
curl -X POST http://localhost:8080/api/v1/environments/5/test-webhook \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
### Retry behavior
Failed webhook deliveries are retried 3 times with exponential backoff (1s, 2s, 4s). If the server is shutting down or the request context is cancelled, retries stop early.
Use HTTPS endpoints for webhooks. Pullbase validates TLS certificates by default.
## Best practices
* Use descriptive `server_id` values (for example, `prod-api-01`) to simplify searches.
* Rotate agent tokens regularly; maintain at least two active tokens during rotation to avoid downtime.
* Keep environment branches protected and enforce pull-request reviews.
* Tag production commits so rollbacks are easy to target.
* Configure webhook notifications for critical environments to get alerted on drift.
Store environment metadata (repo URL, branch, deploy path) in a configuration management repo to reproduce Pullbase configuration declaratively.
# Real-World Examples
Source: https://docs.pullbase.io/examples
Practical examples showing how to use Pullbase for common server management scenarios.
These examples demonstrate real-world use cases for Pullbase. Each includes complete, working `config.yaml` files that you can adapt for your environment.
## Example 1: Managing nginx across a web server fleet
This example shows how to manage nginx configuration across multiple web servers. All servers get the same base configuration, and changes are rolled out automatically when you push to Git.
### Repository structure
```
infra-config/
production/
web-servers/
config.yaml # Shared config for all web servers
```
### The config.yaml
```yaml production/web-servers/config.yaml theme={null}
serverMetadata:
name: "web-server"
environment: "production"
packages:
- name: nginx
state: present
- name: curl
state: present
- name: htop
state: present
services:
- name: nginx
enabled: true
state: running
managed: true
files:
- path: /etc/nginx/nginx.conf
content: |
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 1024;
use epoll;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
server_tokens off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Logging
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
# Gzip compression
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
mode: "0644"
reloadService: nginx
- path: /etc/nginx/sites-available/default
content: |
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm;
server_name _;
location / {
try_files $uri $uri/ =404;
}
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
}
mode: "0644"
reloadService: nginx
system:
serviceManager: systemd
```
### Workflow: Updating nginx config
Update the nginx configuration in your Git repository:
```bash theme={null}
cd infra-config
vim production/web-servers/config.yaml
# Make your changes to the nginx config
```
Use the CLI to validate your config locally:
```bash theme={null}
pullbasectl validate-config --file production/web-servers/config.yaml
```
Output if valid:
```
Config is valid
```
```bash theme={null}
git add production/web-servers/config.yaml
git commit -m "Update nginx: enable gzip compression"
git push origin main
```
Watch the rollout in the dashboard or via CLI:
```bash theme={null}
pullbasectl status --environment-id 1 --watch
```
Output:
```
Fleet Status Summary
Total: 10 servers
Healthy: 8
Drifted: 2
Errors: 0
SERVER ENVIRONMENT STATUS DRIFTED COMMIT LAST SEEN
web-01 production Syncing yes a1b2c3d just now
web-02 production Applied no a1b2c3d 30 seconds ago
...
```
After agents reconcile:
```
Fleet Status Summary
Total: 10 servers
Healthy: 10
Drifted: 0
Errors: 0
```
***
## Example 2: Rolling out security patches
This example shows how to ensure security-critical packages are always at the latest version across your fleet.
### The config.yaml
```yaml production/security-baseline/config.yaml theme={null}
serverMetadata:
name: "security-baseline"
environment: "production"
packages:
# Security-critical: always latest
- name: openssl
state: latest
- name: openssh-server
state: latest
- name: ca-certificates
state: latest
- name: libssl3
state: latest
# Remove known-vulnerable packages
- name: telnet
state: absent
- name: rsh-client
state: absent
# Standard utilities: just ensure present
- name: fail2ban
state: present
- name: ufw
state: present
- name: unattended-upgrades
state: present
services:
- name: fail2ban
enabled: true
state: running
managed: true
- name: ssh
enabled: true
state: running
managed: true
- name: ufw
enabled: true
state: running
managed: true
files:
- path: /etc/ssh/sshd_config.d/hardening.conf
content: |
# Security hardening for SSH
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
X11Forwarding no
AllowTcpForwarding no
MaxAuthTries 3
LoginGraceTime 60
ClientAliveInterval 300
ClientAliveCountMax 2
mode: "0644"
reloadService: ssh
- path: /etc/fail2ban/jail.local
content: |
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
mode: "0644"
reloadService: fail2ban
system:
serviceManager: systemd
```
### Workflow: Responding to a CVE
When a critical vulnerability is announced (e.g., in OpenSSL):
Because `openssl` is set to `state: latest`, agents will install updates automatically during their next reconciliation cycle.
If you need updates applied immediately, trigger a manual sync from the dashboard or restart agents:
```bash theme={null}
# On each server (or via your automation)
sudo systemctl restart pullbase-agent
```
```bash theme={null}
pullbasectl status --all --output json | jq '.servers[] | {id: .server_id, status: .status}'
```
Check the specific package version on servers:
```bash theme={null}
ssh web-01 'dpkg -l openssl'
```
Set up a [webhook notification](/environments-and-servers#webhook-notifications) to alert your team when drift is detected or errors occur during reconciliation.
***
## Example 3: Environment promotion (staging to production)
This example shows a repository structure for managing multiple environments, making it easy to test changes in staging before promoting to production.
### Repository structure
```
infra-config/
environments/
staging/
config.yaml
production/
config.yaml
shared/
nginx-base.conf # Reference file (not directly used by Pullbase)
```
### Staging config
```yaml environments/staging/config.yaml theme={null}
serverMetadata:
name: "app-server"
environment: "staging"
packages:
- name: nginx
state: latest
- name: nodejs
state: present
- name: redis-tools
state: present
services:
- name: nginx
enabled: true
state: running
managed: true
files:
- path: /etc/nginx/sites-available/app
content: |
upstream app_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 80;
server_name staging.example.com;
# Staging: allow verbose errors
error_page 500 502 503 504 /50x.html;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
}
location /health {
access_log off;
return 200 "staging-ok\n";
}
}
mode: "0644"
reloadService: nginx
- path: /etc/nginx/sites-enabled/app
content: |
# Include directive pointing to the full config
include /etc/nginx/sites-available/app;
mode: "0644"
reloadService: nginx
system:
serviceManager: systemd
```
### Production config
```yaml environments/production/config.yaml theme={null}
serverMetadata:
name: "app-server"
environment: "production"
packages:
- name: nginx
state: present
- name: nodejs
state: present
- name: redis-tools
state: present
services:
- name: nginx
enabled: true
state: running
managed: true
files:
- path: /etc/nginx/sites-available/app
content: |
upstream app_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001 backup;
keepalive 64;
}
server {
listen 80;
server_name app.example.com;
# Production: minimal error exposure
error_page 500 502 503 504 /50x.html;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
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_cache_bypass $http_upgrade;
# Production timeouts
proxy_connect_timeout 10s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
location /health {
access_log off;
return 200 "ok\n";
}
}
mode: "0644"
reloadService: nginx
- path: /etc/nginx/sites-enabled/app
content: |
include /etc/nginx/sites-available/app;
mode: "0644"
reloadService: nginx
system:
serviceManager: systemd
```
### Workflow: Promoting changes
```bash theme={null}
cd infra-config
vim environments/staging/config.yaml
# Add new upstream server, change timeout, etc.
```
```bash theme={null}
git add environments/staging/config.yaml
git commit -m "staging: add connection pooling to upstream"
git push origin main
```
Wait for staging servers to reconcile and verify the changes work.
Copy the tested changes to production config:
```bash theme={null}
# Review the diff
diff environments/staging/config.yaml environments/production/config.yaml
# Apply the specific change to production
vim environments/production/config.yaml
# Make the same changes (with production-specific values)
git add environments/production/config.yaml
git commit -m "production: add connection pooling (tested in staging)"
git push origin main
```
```bash theme={null}
pullbasectl status --environment-id 2 --watch --interval 5
```
If something goes wrong:
```bash theme={null}
# Via CLI
pullbasectl environments rollback \
--server-url https://pullbase.example.com \
--admin-token $ADMIN_TOKEN \
--id 2 \
--commit abc123 \
--reason "Connection pooling causing 502 errors"
# Or via dashboard: Environment > Rollback > Select commit
```
***
## Tips for managing configs at scale
Deploy agents in dry-run mode initially to see what *would* change without actually making changes:
```bash theme={null}
AGENT_DRY_RUN=true ./pullbase-agent
```
Configure webhook notifications to get alerts on drift or errors:
```bash theme={null}
pullbasectl environments update \
--id 1 \
--notification-webhook-url https://hooks.slack.com/...
```
Always validate configs locally before committing:
```bash theme={null}
pullbasectl validate-config --file config.yaml
```
Your Git history becomes your change log. Write clear commit messages:
```
nginx: increase worker_connections to 2048
Load testing showed connection exhaustion at 1024.
Tested in staging env for 24h before promoting.
```
## Next steps
Full reference for all config.yaml options
Complete pullbasectl command documentation
# GitHub App Integration
Source: https://docs.pullbase.io/github-app-integration
Connect Pullbase to private Git repositories using a GitHub App.
Pullbase supports GitHub Apps to securely access private repositories without embedding personal access tokens.
## When to use a GitHub App
* Your configuration repository is private.
* You need auditable, revokable permissions scoped to specific repositories.
* You want Pullbase to fetch short-lived installation tokens on behalf of agents.
You can start with public repositories (set `PULLBASE_GIT_ENABLED=false`). Add a GitHub App once you move configuration to a private repository.
## Create the GitHub App
1. Visit [https://github.com/settings/apps/new](https://github.com/settings/apps/new) (or the equivalent GitHub Enterprise URL).
2. Provide a descriptive **App name** (for example, `Pullbase Config App`).
3. Set the **Homepage URL** to your Pullbase instance (`https://pullbase.example.com`).
4. Set the **Callback URL** to `https://pullbase.example.com/api/v1/github-app/callback` (reserved for future enhancements).
5. Leave the **Webhook** section disabled unless you plan to handle app-level webhooks separately.
Grant only the permissions required:
* Repository permissions → **Contents: Read-only**
* Repository permissions → **Metadata: Read-only**
* All other permissions: **No access**
Pullbase only needs read access to fetch configuration files. Additional permissions are unnecessary and increase risk.
Install the app on the organization/user that owns your configuration repository. Select the repositories Pullbase should access.
After installation, record:
* **App ID** — Found on the app's settings page under "About"
* **App slug** — Lowercase name in the app's URL (e.g., `pullbase-config` from `github.com/apps/pullbase-config`)
* **Installation ID** — Found in the URL after installing: `github.com/settings/installations/{installation_id}`
* **Repository ID** — Query via GitHub API (see below)
* **Private key** — Download the `.pem` file from "Private keys" section
**Finding the Repository ID:**
```bash theme={null}
# Using GitHub CLI
gh api /repos/{owner}/{repo} --jq '.id'
# Example
gh api /repos/acme/infra-config --jq '.id'
# Output: 964854370
```
Or via curl:
```bash theme={null}
curl -s https://api.github.com/repos/{owner}/{repo} | jq '.id'
```
## Configure Pullbase
### Environment variables
```env theme={null}
PULLBASE_GIT_ENABLED=true
PULLBASE_GITHUB_APP_ID=2113565
PULLBASE_GITHUB_APP_PRIVATE_KEY_PATH=/config/github-app.pem
PULLBASE_GITHUB_APP_API_BASE_URL=https://api.github.com
```
Mount the private key into the container at the configured path:
```yaml theme={null}
volumes:
- ./config/github-app.pem:/config/github-app.pem:ro
```
**Private key security:**
* Set restrictive permissions: `chmod 600 github-app.pem`
* Never commit the `.pem` file to Git
* Use Docker secrets or a secrets manager in production
* The `:ro` mount flag ensures the container cannot modify the key
**Using Docker secrets (recommended for production):**
```yaml theme={null}
services:
central-server:
image: pullbaseio/pullbase:latest
environment:
- PULLBASE_GITHUB_APP_PRIVATE_KEY_PATH=/run/secrets/github_app_key
secrets:
- github_app_key
secrets:
github_app_key:
file: ./config/github-app.pem
```
### GitHub Enterprise Server
For GitHub Enterprise Server (self-hosted), update the API base URL:
```env theme={null}
PULLBASE_GITHUB_APP_API_BASE_URL=https://github.mycompany.com/api/v3
```
The app registration and installation process is the same, but use your GitHub Enterprise URL instead of `github.com`.
### Environment-level configuration
When creating an environment (UI, CLI, or API) you provide GitHub App metadata:
```json theme={null}
{
"name": "staging",
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"installation_id": 89968159,
"repository_id": 964854370,
"app_slug": "pullbase-config"
}
```
## CLI validation
Use the bootstrap command to validate credentials locally before storing them on the server:
```bash theme={null}
pullbasectl github-app bootstrap \
--app-id 2113565 \
--private-key /config/github-app.pem \
--installation-id 89968159 \
--repository-id 964854370 \
--app-slug pullbase-config
```
Add `--server-url`, `--admin-token`, and environment details to persist the configuration as part of environment creation.
## Agent flow
1. The environment stores GitHub App metadata (installation ID, repository ID, app slug).
2. An agent requests `GET /api/v1/agent/git-token` using its agent token.
3. Pullbase signs a JWT with the app's private key and calls GitHub's `/app/installations/{id}/access_tokens` endpoint.
4. Pullbase returns the short-lived installation token to the agent, which uses it for `git clone`.
5. Tokens expire in one hour; agents request fresh ones as needed.
## Troubleshooting
* Verify the installation includes the repository. Check [https://github.com/settings/installations](https://github.com/settings/installations) for the app.
* Confirm the app has `Contents: Read-only` permission.
* Regenerate the `.pem` file from the GitHub App settings and update the mounted secret.
* Ensure the file has restricted permissions (readable only by the Pullbase container).
* GitHub Apps share a rate limit per installation. Reduce agent poll interval or enable webhooks to decrease token requests.
* Check `Retry-After` headers in error responses.
* Ensure the webhook secret in GitHub matches `PULLBASE_WEBHOOK_SECRET_KEY`.
Use the GitHub CLI to inspect installation details:
```bash theme={null}
gh api /app/installations --jq '.[].id'
```
# CLI Workflow Guide
Source: https://docs.pullbase.io/guides/pullbasectl
Master the pullbasectl command-line tool for efficient Pullbase management.
While the [Web UI](/web-ui) is excellent for monitoring and visual exploration, the CLI (`pullbasectl`) is the preferred tool for:
* **Automation:** Scripting repetitive tasks or CI/CD pipelines.
* **Initial Setup:** Bootstrapping the first admin and setting up environments.
* **Power Users:** Rapidly executing commands without navigating menus.
This guide focuses on practical, workflow-oriented usage. For a complete list of flags, see the [CLI Reference](/reference/cli).
## Running the CLI
You can run `pullbasectl` in three ways depending on your environment.
### 1. via Docker (Recommended)
The easiest way to run the CLI is using the binary already inside your running `central-server` container.
```bash theme={null}
docker compose exec central-server pullbasectl
```
**Alias Tip:** Add this to your shell profile to run `pb` instead of the long command:
`alias pb='docker compose exec central-server pullbasectl'`
### 2. via Go
If you have Go installed and the repository cloned, you can run directly from source:
```bash theme={null}
go run ./server/cmd/pullbasectl
```
### 3. Native Binary
For frequent usage on your host machine, build the binary:
```bash theme={null}
go build -o pullbasectl ./server/cmd/pullbasectl
# Move to a directory in your PATH, e.g., /usr/local/bin
sudo mv pullbasectl /usr/local/bin/
```
## Authentication
### 1. Bootstrap First Admin
When you first install Pullbase, no users exist. You must "bootstrap" the first admin using a secret file generated by the server.
The server writes this secret to `/app/secrets/bootstrap.secret`.
```bash theme={null}
docker compose exec central-server cat /app/secrets/bootstrap.secret
# Output example: 8f3...b2a
```
Use the secret to create your admin account.
```bash theme={null}
docker compose exec central-server pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret "YOUR_SECRET_FROM_ABOVE" \
--username admin \
--password 'SecurePassword123!'
```
### 2. Login & Token Reuse
Instead of passing credentials with every command, login once and export the token.
```bash theme={null}
# Login and capture the token
TOKEN=$(docker compose exec central-server pullbasectl auth login \
--server-url http://localhost:8080 \
--username admin \
--password 'SecurePassword123!' | grep -oE 'ey[a-zA-Z0-9._-]+')
# Export for your session
export PULLBASE_ADMIN_TOKEN=$TOKEN
```
Now you can run commands without auth flags:
```bash theme={null}
docker compose exec central-server pullbasectl users list --server-url http://localhost:8080
```
## Common Workflows
### Create an Environment
Environments group servers and link them to a Git repository configuration.
```bash theme={null}
docker compose exec central-server pullbasectl environments create \
--server-url http://localhost:8080 \
--name "production" \
--repo-url "https://github.com/your-org/infra-config" \
--branch "main" \
--deploy-path "envs/prod"
```
### Register Server & Install Agent
The standard flow to add a new server:
```bash theme={null}
docker compose exec central-server pullbasectl servers create \
--server-url http://localhost:8080 \
--id "web-01" \
--name "Web Server 01" \
--environment-id 1
```
```bash theme={null}
docker compose exec central-server pullbasectl tokens create \
--server-url http://localhost:8080 \
--server-id "web-01" \
--description "Initial token"
```
*Save the token output starting with `pbt_`.*
You can generate a one-liner to run on the target server:
```bash theme={null}
docker compose exec central-server pullbasectl servers install-script \
--server-url http://localhost:8080 \
--id "web-01" \
--token "pbt_YOUR_TOKEN"
```
### Create/List/Delete Users
Manage access for your team.
```bash theme={null}
# List all users
docker compose exec central-server pullbasectl users list \
--server-url http://localhost:8080
# Create a read-only user
docker compose exec central-server pullbasectl users create \
--server-url http://localhost:8080 \
--new-username "auditor" \
--new-password "AuditPass123!" \
--role viewer
# Delete a user's account
docker compose exec central-server pullbasectl users delete \
--server-url http://localhost:8080 \
--user-id 7 \
--delete-acct-username "SomeAccount123"
```
User deletion is blocked if you attempt to delete the last active admin or your own account.
### Validate Config Locally
Validate your `config.yaml` before pushing to Git to prevent errors.
```bash theme={null}
docker compose exec central-server pullbasectl validate-config \
--file ./configs/prod/config.yaml
```
## Troubleshooting
### Host vs. Container URLs
* **From Host:** Access the API via `http://localhost:8080`.
* **From Container:** If running `pullbasectl` inside another container in the same network, use the service name: `http://central-server:8080`.
### TLS Errors
If you are using self-signed certificates (default in development):
* **Production:** Always trust the CA.
```bash theme={null}
--ca-cert /path/to/ca.crt
```
* **Development Only:** Skip verification (insecure).
```bash theme={null}
--insecure-skip-verify
```
### 401 Unauthorized
If you receive a 401 error, your token has likely expired (default 24h).
1. Run `auth login` again to get a new token.
2. Update your `PULLBASE_ADMIN_TOKEN` variable.
# Installation
Source: https://docs.pullbase.io/installation
Deploy the Pullbase central server to coordinate your Linux server fleet.
Pullbase consists of two components:
1. **Central Server** — Coordinates environments, monitors Git, serves the dashboard.
2. **Agents** — Run on each managed Linux server.
This guide covers deploying the **Central Server**. For installing agents on your servers, see [Agent Operations](/agent-operations).
## Prerequisites
* **Docker** (v24.0+) with Docker Compose
* **Git** repository for your configuration
* **Database:** SQLite (default) or PostgreSQL (production)
## Choose Your Database
Pullbase supports two database backends. Choose the one that fits your needs:
| Feature | SQLite (Default) | PostgreSQL (Recommended) |
| :-------------- | :------------------------------- | :------------------------------ |
| **Best for** | Testing, POCs, Small deployments | Production, High Availability |
| **Setup** | Zero configuration | Requires Postgres container/RDS |
| **Storage** | Single file on disk | External volume/service |
| **Performance** | Good for \< 100 agents | Scales to thousands |
## Quick Start (SQLite)
The easiest way to get started is using the default SQLite database. This requires zero external dependencies and is perfect for testing.
Create a file named `docker-compose.yml` with the following content:
```yaml docker-compose.yml theme={null}
services:
central-server:
image: pullbaseio/pullbase:latest
restart: unless-stopped
ports:
- "8080:8080"
environment:
# Database
- PULLBASE_DB_TYPE=sqlite
- PULLBASE_DB_PATH=/data/pullbase.db
# Security
# ⚠️ IMPORTANT: Generate secure random strings for production use
- PULLBASE_JWT_SECRET=change-this-to-a-secure-random-string
- PULLBASE_WEBHOOK_SECRET_KEY=change-this-to-a-secure-random-string
- PULLBASE_BOOTSTRAP_SECRET_FILE=/app/secrets/bootstrap.secret
# Logging (optional)
- PULLBASE_LOG_FORMAT=json # json or text (default: text)
- PULLBASE_LOG_LEVEL=info # debug, info, warn, error (default: info)
volumes:
- pullbase_data:/data
- pullbase_secrets:/app/secrets
volumes:
pullbase_data:
pullbase_secrets:
```
```bash theme={null}
docker compose up -d
```
Check if the server is running:
```bash theme={null}
curl http://localhost:8080/api/v1/healthz
# Output: {"status":"ok"}
```
You need to create your first admin user to access the UI.
Follow the **[CLI Guide](/guides/pullbasectl#authentication)** to bootstrap your admin account using the secret file.
## Production Setup (PostgreSQL)
For production environments, we recommend using PostgreSQL for better performance, reliability, and concurrency.
Use this configuration to spin up Pullbase with a dedicated PostgreSQL container.
```yaml docker-compose.yml theme={null}
services:
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: ${PULLBASE_DB_USER:-pullbaseuser}
POSTGRES_PASSWORD: ${PULLBASE_DB_PASSWORD}
POSTGRES_DB: ${PULLBASE_DB_NAME:-pullbasedb}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
central-server:
image: pullbaseio/pullbase:latest
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "8080:8080"
environment:
# Database Connection
- PULLBASE_DB_TYPE=postgres
- PULLBASE_DB_HOST=db
- PULLBASE_DB_PORT=5432
- PULLBASE_DB_USER=pullbaseuser
- PULLBASE_DB_NAME=pullbasedb
# Security
- PULLBASE_JWT_SECRET=${PULLBASE_JWT_SECRET}
- PULLBASE_WEBHOOK_SECRET_KEY=${PULLBASE_WEBHOOK_SECRET_KEY}
- PULLBASE_BOOTSTRAP_SECRET_FILE=/app/secrets/bootstrap.secret
# Logging (optional)
- PULLBASE_LOG_FORMAT=json # json recommended for production
- PULLBASE_LOG_LEVEL=info
volumes:
- pullbase_secrets:/app/secrets
volumes:
postgres_data:
pullbase_secrets:
```
Create a `.env` file to store your sensitive secrets. **Do not commit this file to Git.**
```bash .env theme={null}
PULLBASE_JWT_SECRET=generate-a-long-random-string-here
PULLBASE_WEBHOOK_SECRET_KEY=generate-another-random-string
PULLBASE_DB_PASSWORD=generate-a-secure-database-password
```
```bash theme={null}
docker compose up -d
```
## Next Steps
Now that your server is running:
1. **[Bootstrap the Admin](/guides/pullbasectl#authentication)** to create your first user.
2. **[Login to the Dashboard](/web-ui)** at `http://localhost:8080`.
3. **[Secure your installation](/security-hardening)** with TLS before going to production.
# What is Pullbase?
Source: https://docs.pullbase.io/introduction
GitOps for Linux servers — manage packages, services, and configuration files using Git as your source of truth.
**Pullbase** lets you manage Linux servers the same way developers manage code: through Git. Define what packages should be installed, which services should be running, and what configuration files should contain — then let Pullbase keep your servers in sync automatically.
## The problem Pullbase solves
Managing servers manually doesn't scale:
* SSH into each server to make changes? **Slow and error-prone.**
* Write shell scripts to push changes? **Hard to track what changed and when.**
* Use Ansible/Chef/Puppet? **Complex setup, push-based model, firewall headaches.**
Pullbase takes a different approach: **pull-based GitOps**.
You push to Git. Agents pull. Servers stay in sync.
## Who is Pullbase for?
Pullbase is built for teams managing **Linux servers** — VMs, bare-metal, or cloud instances:
Managing fleets of web servers, database hosts, or application servers
Who want GitOps benefits without Kubernetes complexity
Standardizing configuration across environments (dev, staging, prod)
Who need infrastructure automation without dedicated tooling expertise
## What Pullbase manages
Define your desired state in a simple `config.yaml`:
```yaml config.yaml theme={null}
packages:
- name: nginx
state: present
- name: curl
state: latest
services:
- name: nginx
state: running
enabled: true
files:
- path: /etc/nginx/nginx.conf
content: |
worker_processes auto;
events { worker_connections 1024; }
http {
server {
listen 80;
location / { return 200 'OK'; }
}
}
mode: "0644"
reloadService: nginx
```
The agent ensures:
* **Packages** are installed, upgraded, or removed (apt, yum, dnf, apk)
* **Services** are running, stopped, enabled, or disabled (systemd, supervisor, OpenRC)
* **Files** have the correct content and permissions
When something drifts from the desired state, Pullbase detects it and can automatically fix it.
## How it works
Create a `config.yaml` in a Git repository. This is your source of truth.
Install the Pullbase agent on each server. The agent authenticates with the central server.
Every 60 seconds, agents check for changes. When the Git repo updates, agents pull the new config and apply it.
See which servers are in sync, which have drifted, and review the history of changes.
## Key features
| Feature | Description |
| ------------------------ | ---------------------------------------------------------------------- |
| **Pull-based model** | Agents initiate connections outward — no inbound firewall rules needed |
| **Drift detection** | Agents detect when actual state differs from desired state |
| **Auto-reconciliation** | Optionally fix drift automatically, or review first |
| **Dry-run mode** | Preview what would change before enabling enforcement |
| **Environment grouping** | Organize servers into environments (prod, staging, dev) |
| **Rollback support** | Revert to a previous Git commit with one click |
| **Webhook integration** | Get notified on Slack, PagerDuty, or any webhook endpoint |
| **GitHub App support** | Secure access to private repositories |
## What Pullbase is NOT
To set clear expectations:
* **Not for Kubernetes.** If you're running containers on K8s, use ArgoCD, Flux, or similar tools designed for that ecosystem.
* **Not a CI/CD pipeline.** Pullbase manages runtime state, not build artifacts or deployments.
* **Not a container orchestrator.** It manages what's *on* your servers, not container scheduling.
* **Not a monitoring tool.** It reports status, but doesn't replace Prometheus, Grafana, or your APM.
## Pullbase vs. other tools
| | Pullbase | Ansible | Chef/Puppet |
| ------------------ | ------------------------ | ------------------- | ------------------ |
| **Model** | Pull (agent-initiated) | Push (control node) | Pull (agent) |
| **Firewall** | Agents connect out | Needs inbound SSH | Agents connect out |
| **Language** | YAML | YAML + Jinja | Ruby DSL |
| **State tracking** | Built-in drift detection | External or manual | Built-in |
| **Complexity** | Simple — one binary | Medium | High |
| **Learning curve** | Hours | Days | Weeks |
Pullbase is intentionally simpler. If you need complex orchestration, conditionals, or multi-step workflows, Ansible may be a better fit. If you want straightforward "this is what my server should look like" enforcement, Pullbase gets you there faster.
## Architecture overview
* **Server**: Central coordination, API, dashboard, Git monitoring
* **Agents**: Run on each managed server, pull config, apply state, report status
* **Database**: SQLite (default) or PostgreSQL — stores environments, servers, status history, audit logs
* **Git repository**: Your source of truth for desired state
## Next steps
Get Pullbase running and manage your first server in 5 minutes
Understand environments, servers, agents, and how they work together
Learn how the server, agents, and Git repository stay in sync
Full reference for config.yaml — packages, services, files, and more
**New to Pullbase?** Start with the [Quickstart](/quickstart) — you'll have a working setup in under 5 minutes.
# Operations & Troubleshooting
Source: https://docs.pullbase.io/operations-troubleshooting
Keep Pullbase healthy and resolve common issues quickly.
Operating Pullbase involves monitoring service health, responding to drift, and collecting diagnostics during incidents.
## Health checks
* **API health:** `curl http://localhost:8080/api/v1/healthz`
* **Database (SQLite):** Check the database file exists and is accessible
* **Database (PostgreSQL):** `docker compose exec db pg_isready -U pullbaseuser -d pullbasedb`
* **Web UI:** Sign in and confirm the dashboard loads recent activity.
* **Agents:** List servers and check `last_timestamp` in status history via CLI or UI.
## Routine operations checklist
* Review drift alerts from status history
* Check audit logs for unexpected actions
* Ensure webhooks (if enabled) are succeeding
* Rotate admin tokens or ensure expiration dates are set
* Back up the database (SQLite file or PostgreSQL dump)
* Verify TLS certificates (at reverse proxy) aren't approaching expiry
* Test agent token rotation on a sample server
* Exercise rollback on a staging environment
* Review GitHub App access scopes
* Run disaster recovery drill (bootstrap new server + restore DB)
* Patch base OS/images used for agents and central server hosts
## Troubleshooting guide
* Delete `bootstrap-admin-secret.txt` inside the config directory and restart to regenerate.
* Or provide a new secret via `PULLBASE_BOOTSTRAP_SECRET` environment variable.
* Create a new admin via CLI if another admin exists: `pullbasectl users create`
* Token revoked? Generate a new token and update the deployment.
* Clock skew? Ensure NTP is running—JWT validation is time-sensitive.
* Incorrect `SERVER_ID`? Confirm environment variable matches the server record.
* Check outbound network connectivity to the Git provider.
* For GitHub Apps, verify installation scopes and ensure the private key matches the app.
* Confirm agents trust the TLS certificate (`CACERT_PATH` points to the CA bundle).
* Override auto-detection by setting `system.serviceManager` in `config.yaml`.
* For Docker containers, use `supervisor` as the service manager.
* Ensure service manager commands are in the agent's PATH.
* Review container logs around startup for SQL errors.
* Migrations run automatically for both SQLite and PostgreSQL.
* Check for schema drift caused by manual database edits.
* Restore from backup if migrations fail due to corruption.
* Inspect container logs: `docker compose logs pullbase`
* Ensure the webhook secret in GitHub matches `PULLBASE_WEBHOOK_SECRET_KEY`.
* Confirm Pullbase is reachable from your Git provider (network ACLs, firewalls).
When TLS is enabled, the server attempts to generate self-signed certificates on startup. If you see:
```
failed to generate self-signed certificates: mkdir certs: permission denied
```
**Option 1: Disable TLS** (recommended if behind a reverse proxy or for testing):
```bash theme={null}
echo "PULLBASE_TLS_ENABLED=false" | sudo tee -a /etc/pullbase/pullbase.env
sudo systemctl restart pullbase
```
**Option 2: Create the certs directory with proper permissions**:
```bash theme={null}
sudo mkdir -p /var/lib/pullbase/certs
sudo chown pullbase:pullbase /var/lib/pullbase/certs
# Update env to use this path
sudo tee -a /etc/pullbase/pullbase.env > /dev/null <<'EOF'
PULLBASE_TLS_CERT_PATH=/var/lib/pullbase/certs/server.crt
PULLBASE_TLS_KEY_PATH=/var/lib/pullbase/certs/server.key
EOF
sudo systemctl restart pullbase
```
For production, use CA-signed certificates or terminate TLS at a reverse proxy.
If you see `Failed to execute /usr/local/bin/pullbase-server: Exec format error`:
You downloaded the wrong binary for your system architecture.
```bash theme={null}
# Check your architecture
uname -m
```
| Output | Binary to download |
| -------------------- | ----------------------------- |
| `x86_64` | `pullbase-server-linux-amd64` |
| `aarch64` or `arm64` | `pullbase-server-linux-arm64` |
Download the correct binary and reinstall:
```bash theme={null}
sudo systemctl stop pullbase
curl -fsSL -o pullbase-server "https://github.com/pullbase/pullbase/releases/latest/download/pullbase-server-linux-arm64"
sudo install -m 0755 pullbase-server /usr/local/bin/pullbase-server
sudo systemctl start pullbase
```
## Collect diagnostics
```bash theme={null}
# Pullbase server logs
docker compose logs pullbase --since 1h
# Database logs
docker compose logs db --since 1h
# Agent logs (container)
docker logs pullbase-agent --since 1h
# Agent logs (systemd)
journalctl -u pullbase-agent --since "1 hour ago"
```
```bash theme={null}
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:8080/api/v1/environments
```
```bash theme={null}
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://localhost:8080/api/v1/servers/web-01/status/history
```
**SQLite:**
```bash theme={null}
cp /data/pullbase.db pullbase-$(date +%F).db
```
**PostgreSQL:**
```bash theme={null}
docker compose exec db \
pg_dump -U pullbaseuser pullbasedb \
> pullbasedb-$(date +%F).sql
```
## Resetting the installation
If you need a clean slate (lab environments):
1. Stop services: `docker compose down`
2. Remove data **only if you accept data loss**:
* **SQLite:** `docker volume rm pullbase_pullbase_data` or delete the database file
* **PostgreSQL:** `docker volume rm pullbase_postgres_data`
3. Start again: `docker compose up -d`
Removing the database wipes environments, servers, tokens, and audit history. Always back up before destructive operations.
## Getting help
* Review [GitHub issues](https://github.com/pullbase/pullbase/issues) for known bugs.
* Collect the diagnostic bundle above before filing a bug report.
* Provide Pullbase version and deployment details.
Keep this playbook handy so you can respond quickly when drift occurs or when an agent experiences connectivity issues.
# Quickstart
Source: https://docs.pullbase.io/quickstart
Get Pullbase running and see your first servers come online in under 5 minutes.
This guide gets you from zero to a working Pullbase setup with agents reporting status. By the end, you'll have:
* A running Pullbase server with database
* An admin account
* Two demo agents reporting their status
**Time estimate:** 5 minutes
## Prerequisites
* **Docker** 24.0+ with the Compose plugin (`docker compose`)
* **Git** to clone the repository
* A terminal (macOS, Linux, or WSL)
**This quickstart is for evaluation only.** It uses Docker to run demo agents for testing purposes.
For production:
* Deploy the **central server** using Docker or [bare-metal](/deployment-bare-metal)
* Install **agents natively** on your Linux servers using the [install script](/agent-operations#one-liner-install-script-recommended) — never run production agents in containers
## Step 1: Clone and start Pullbase
```bash theme={null}
git clone https://github.com/pullbase/pullbase.git
cd pullbase
docker compose up -d
```
This starts:
* **PostgreSQL** database
* **Pullbase server** (API + dashboard)
* **Two demo agents** (test-server-1, test-server-2)
Wait for the stack to be healthy (about 60 seconds):
```bash theme={null}
docker compose ps
```
You should see all services with status `healthy` or `running`.
## Step 2: Verify the server is running
```bash theme={null}
curl http://localhost:8080/api/v1/healthz
```
Expected output:
```json theme={null}
{"status":"healthy","service":"pullbase-server"}
```
## Step 3: Bootstrap your admin account
Pullbase generates a one-time bootstrap secret on first start. Retrieve it and create your admin:
```bash theme={null}
# Get the bootstrap secret
docker compose exec central-server cat /app/secrets/bootstrap.secret
# Create admin (replace YOUR_SECRET with the value above)
docker compose exec central-server pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret "YOUR_SECRET" \
--username admin \
--password 'SecurePassword123!'
```
You should see:
```
Admin bootstrap completed successfully.
Username: admin
Access token (store securely):
eyJhbGciOiJIUzI1NiIs...
```
Save the access token! You'll need it for CLI commands. The bootstrap secret is invalidated after use.
## Step 4: Open the dashboard
Visit **[http://localhost:8080](http://localhost:8080)** in your browser and sign in with:
* Username: `admin`
* Password: `SecurePassword123!` (or whatever you chose)
You should see the Pullbase dashboard with:
* A sidebar showing "Environments" and "Servers"
* The demo environment and servers from the seeded data
## Step 5: Watch agents come online
Navigate to **Servers** in the sidebar. Within 60 seconds, you should see:
| Server | Status | Last Seen |
| ------------- | ------- | --------- |
| test-server-1 | Applied | Just now |
| test-server-2 | Applied | Just now |
The agents are pulling configuration from the demo environment and reporting their status.
**Congratulations!** You have a working Pullbase installation.
***
## Next: Add your own server
Now that you've seen Pullbase working, here's how to add a real server.
### Option A: Using the UI
1. Go to **Servers → Create Server**
2. Enter a server ID (e.g., `web-01`) and name
3. Select an environment
4. Click **Create**
5. Go to the server's **Tokens** tab and create a token
6. Copy the install command and run it on your target server
### Option B: Using the CLI
```bash theme={null}
# Set your server URL and token
export PULLBASE_URL=http://localhost:8080
export PULLBASE_TOKEN=
# Create a server
docker compose exec central-server pullbasectl servers create \
--server-url $PULLBASE_URL \
--admin-token $PULLBASE_TOKEN \
--id web-01 \
--name "Web Server 01" \
--environment-id 1
# Create an agent token
docker compose exec central-server pullbasectl tokens create \
--server-url $PULLBASE_URL \
--admin-token $PULLBASE_TOKEN \
--server-id web-01 \
--description "initial token"
```
The token output includes an install command you can run on your target server.
### Install the agent on your server
On your target Linux server (VM, bare-metal, or cloud instance):
```bash theme={null}
curl -fsSL "http://YOUR_PULLBASE_HOST:8080/api/v1/servers/web-01/install-script?token=YOUR_TOKEN" | sudo bash
```
For production, always use HTTPS. See [Security Hardening](/security-hardening) for TLS setup.
***
## What's next?
Group servers and link them to Git repositories
Define packages, services, and files to manage
Deploy Pullbase on real infrastructure with TLS
Master the CLI for automation and workflows
***
## Troubleshooting
* Ensure Docker is running: `docker info`
* Check if port 8080 is available: `lsof -i :8080`
* Check if port 5432 is available: `lsof -i :5432`
* View logs: `docker compose logs -f`
The secret file is created on first boot. Wait 30 seconds after `docker compose up` and try again:
```bash theme={null}
docker compose exec central-server cat /app/secrets/bootstrap.secret
```
* Check agent logs: `docker compose logs test-server-1`
* Agents report every 60 seconds — wait and refresh
* Verify the central-server is healthy: `curl http://localhost:8080/api/v1/healthz`
* Ensure the central-server is running: `docker compose ps`
* Check for errors: `docker compose logs central-server`
* Try the healthcheck: `docker compose exec central-server wget -qO- http://localhost:8080/api/v1/healthz`
## Clean up
To stop and remove all containers and data:
```bash theme={null}
docker compose down -v
```
This removes:
* All containers
* The PostgreSQL data volume
* Generated certificates
# API Reference
Source: https://docs.pullbase.io/reference/api
REST API endpoints for the Pullbase server.
Pullbase supports native TLS. Enable it with `PULLBASE_TLS_ENABLED=true` and provide certificate paths. Alternatively, use a reverse proxy for TLS termination. Admin and viewer accounts authenticate with bearer tokens, while agents use their agent token.
[https://pullbase.example.com/api/v1](https://pullbase.example.com/api/v1)
## Interactive API Documentation
Pullbase includes an interactive Swagger UI for exploring and testing the API.
### `GET /swagger/*`
Serves the interactive Swagger UI documentation. Access it at `https://pullbase.example.com/swagger/index.html`.
The Swagger UI is auto-generated from code annotations and always reflects the current API implementation.
## Public endpoints
These endpoints do not require authentication.
### `GET /healthz`
Kubernetes-style liveness probe. Returns healthy if the service is running and the database is reachable.
```json Success theme={null}
{
"status": "healthy",
"service": "pullbase-server",
"checks": {
"database": {
"status": "healthy",
"latency_ms": 2
}
}
}
```
```json Degraded (high latency) theme={null}
{
"status": "degraded",
"service": "pullbase-server",
"checks": {
"database": {
"status": "degraded",
"latency_ms": 1500
}
}
}
```
### `GET /readyz`
Kubernetes-style readiness probe. Returns healthy if the service is ready to accept traffic, including database connectivity and migration status.
```json Success theme={null}
{
"status": "healthy",
"service": "pullbase-server",
"checks": {
"database": {
"status": "healthy",
"latency_ms": 3
},
"migrations": {
"status": "healthy",
"version": 22
}
}
}
```
```json Unhealthy (dirty migration) theme={null}
{
"status": "unhealthy",
"service": "pullbase-server",
"checks": {
"database": {
"status": "healthy",
"latency_ms": 2
},
"migrations": {
"status": "unhealthy",
"version": 22,
"error": "migration is dirty (failed or incomplete)"
}
}
}
```
### `GET /serverinfo/{serverID}`
Returns Git configuration for a server. Used by agents during initial bootstrap to fetch repository details before authenticating.
Server identifier.
```json Success theme={null}
{
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"target_commit_hash": "8a9d3c...",
"auto_reconcile": true
}
```
### `GET /bootstrap/status`
Returns whether bootstrap is available (first admin not yet created).
```json Success theme={null}
{
"bootstrap_available": true
}
```
### `POST /bootstrap/admin`
Creates the first admin user using the bootstrap secret.
```bash cURL theme={null}
curl -X POST $BASE_URL/bootstrap/admin \
-H 'Content-Type: application/json' \
-d '{
"bootstrap_secret": "secret-from-file",
"username": "admin_user",
"password": "ChangeMeNow123!"
}'
```
```json Success theme={null}
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "admin_user",
"role": "admin"
}
}
```
## Authentication
### `POST /auth/login`
```bash cURL theme={null}
curl -X POST $BASE_URL/auth/login \
-H 'Content-Type: application/json' \
-d '{
"username": "admin_user",
"password": "ChangeMeNow123!"
}'
```
```json Success theme={null}
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 1,
"username": "admin_user",
"role": "admin"
}
}
```
### `GET /auth/me`
Returns the authenticated user. Requires `Authorization: Bearer `.
```json Success theme={null}
{
"id": 1,
"username": "admin_user",
"role": "admin"
}
```
## Environments
### `GET /environments`
```json theme={null}
[
{
"id": 5,
"name": "staging",
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"auto_reconcile": true,
"status": "active",
"deployed_commit": "8a9d3c...",
"notification_webhook_url": "https://hooks.slack.com/..."
}
]
```
### `POST /environments`
```bash cURL theme={null}
curl -X POST $BASE_URL/environments \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"name": "staging",
"description": "Staging web servers",
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"notification_webhook_url": "https://hooks.slack.com/...",
"github_app": {
"installation_id": 89968159,
"repository_id": 964854370,
"app_slug": "pullbase-config"
}
}'
```
```json Success theme={null}
{
"id": 5,
"name": "staging",
"auto_reconcile": true,
"created_at": "2025-01-15T12:34:56Z"
}
```
### `GET /environments/{environmentID}`
Returns a single environment by ID.
### `PUT /environments/{environmentID}`
Updates an environment. Supports updating `notification_webhook_url` for webhook notifications.
### `DELETE /environments/{environmentID}`
Deleting an environment cascades to servers in that environment. De-register servers first if you want a clean transition.
### `POST /environments/{environmentID}/toggle-auto-reconcile`
Toggles the auto-reconcile setting for an environment.
### `POST /environments/{id}/rollback`
Initiates a rollback to a previous commit.
```bash cURL theme={null}
curl -X POST $BASE_URL/environments/5/rollback \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"target_commit": "8a9d3cba2f...",
"reason": "Reverting faulty nginx config"
}'
```
### `GET /environments/{id}/rollbacks`
Lists rollback events for an environment.
Maximum number of rollbacks to return (1-100).
Pagination offset.
```json Success theme={null}
{
"rollbacks": [
{
"id": 42,
"environment_id": 5,
"from_commit": "f6e5d4c...",
"to_commit": "a1b2c3d...",
"initiated_by": "admin_user",
"status": "completed",
"reason": "Reverting broken nginx config",
"created_at": "2025-01-15T12:30:00Z",
"completed_at": "2025-01-15T12:31:00Z"
}
],
"limit": 20,
"offset": 0
}
```
### `GET /environments/{id}/commits`
Returns available commits for rollback selection.
Maximum number of commits to return (1-50).
```json Success theme={null}
{
"commits": [
{
"hash": "a1b2c3d...",
"applied_at": "2025-01-14T10:00:00Z",
"message": "Update nginx config for production"
},
{
"hash": "b2c3d4e...",
"applied_at": "2025-01-13T15:30:00Z",
"message": "Add rate limiting rules"
}
],
"limit": 20
}
```
### `GET /rollbacks/{id}`
Returns the status of a specific rollback operation.
Rollback event ID.
```json Success theme={null}
{
"id": 42,
"environment_id": 5,
"from_commit": "f6e5d4c...",
"to_commit": "a1b2c3d...",
"initiated_by": "admin_user",
"status": "completed",
"reason": "Reverting broken nginx config",
"created_at": "2025-01-15T12:30:00Z",
"completed_at": "2025-01-15T12:31:00Z"
}
```
```json In progress theme={null}
{
"id": 43,
"environment_id": 5,
"from_commit": "a1b2c3d...",
"to_commit": "c3d4e5f...",
"initiated_by": "api",
"status": "in_progress",
"reason": "Rolling back to stable release",
"created_at": "2025-01-15T14:00:00Z"
}
```
```json Failed theme={null}
{
"id": 44,
"environment_id": 5,
"from_commit": "x1y2z3a...",
"to_commit": "invalid...",
"initiated_by": "admin_user",
"status": "failed",
"reason": "Emergency rollback",
"created_at": "2025-01-15T16:00:00Z",
"completed_at": "2025-01-15T16:00:05Z",
"error_message": "Target commit not found in repository"
}
```
**Rollback statuses:**
* `pending` - Rollback created but not yet started
* `in_progress` - Agents are applying the rollback
* `completed` - All agents have applied the target commit
* `failed` - Rollback failed (check `error_message`)
### `POST /environments/{id}/test-webhook`
Sends a test webhook notification to verify the configured webhook URL.
```bash cURL theme={null}
curl -X POST $BASE_URL/environments/5/test-webhook \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
```json Success theme={null}
{
"success": true,
"message": "Test webhook sent successfully"
}
```
### `GET /environments/health`
Returns health status for all environments.
## Servers
### `POST /servers`
```bash cURL theme={null}
curl -X POST $BASE_URL/servers \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"id": "web-01",
"name": "staging-web-01",
"environment_id": 5
}'
```
```json Success theme={null}
{
"server": {
"id": "web-01",
"name": "staging-web-01",
"environment_id": 5
},
"token": "pbt_9BFz...",
"installation_info": {
"instructions": "Run the install script on your target server:",
"install_command": "curl -fsSL 'https://pullbase.example.com/api/v1/servers/web-01/install-script?token=pbt_9BFz...' | sudo bash"
}
}
```
### `GET /servers`
Lists all servers with their latest status.
Filter servers by environment ID.
### `GET /servers/{serverID}`
Returns a single server by ID, including last status, agent version, and drift state.
### `PUT /servers/{serverID}`
Updates a server.
### `DELETE /servers/{serverID}`
Deletes a server, revoking its tokens and clearing status history.
### `POST /servers/{serverID}/toggle-auto-reconcile`
Toggles auto-reconcile for a server.
### `GET /servers/{serverID}/status/history`
Returns chronological status entries including commit hash, drift flag, agent version, and timestamp.
### `GET /servers/{serverID}/drift`
Returns detailed drift information for a server, including which packages, services, or files have drifted from the desired state. The `drift_details` field follows the `DriftDetails` schema: `packages` / `services` / `files` arrays of `DriftItem` objects (`type`, `name`, `expected`, `actual`, `message`) plus an optional `summary`.
```json Drifted theme={null}
{
"server_id": "web-01",
"server_name": "Production Web 1",
"is_drifted": true,
"commit_hash": "a1b2c3d...",
"detected_at": "2025-01-15T12:30:00Z",
"drift_details": {
"packages": [
{"type": "package", "name": "nginx", "expected": "1.24.0", "actual": "1.22.0", "message": "version mismatch"}
],
"files": [
{"type": "file", "name": "/etc/nginx/nginx.conf", "expected": "managed", "actual": "modified", "message": "content drift"}
],
"services": [],
"summary": "nginx downgraded; config modified"
}
}
```
```json Not drifted theme={null}
{
"server_id": "web-01",
"server_name": "Production Web 1",
"is_drifted": false
}
```
### `GET /servers/{serverID}/install-script`
Returns a shell script for installing the agent on the target server.
Agent token for authentication.
Agent version to install. When omitted or set to `latest`, the parameter is not sent.
Base64-encoded CA certificate for custom TLS.
```bash cURL theme={null}
curl -fsSL "$BASE_URL/servers/web-01/install-script?token=pbt_xxx" | sudo bash
```
The script:
* Downloads the agent binary from GitHub releases
* Creates a `pullbase` service user
* Configures `/etc/pullbase/agent.env`
* Installs a hardened systemd service
* Starts the agent
### Token management
* `GET /servers/{serverID}/tokens` – list tokens
* `POST /servers/{serverID}/tokens` – create token (`description`, `expires_in`)
* `DELETE /servers/{serverID}/tokens/{tokenID}` – deactivate token
### `GET /servers/{serverID}/install`
Returns installation instructions for the agent.
## Token expiration
### `GET /tokens/expiring`
Returns tokens that are expiring within a specified number of days.
Number of days to look ahead (max 365).
```bash cURL theme={null}
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
"$BASE_URL/tokens/expiring?days=14"
```
```json Success theme={null}
{
"tokens": [
{
"token_id": 5,
"server_id": "web-01",
"server_name": "Production Web 1",
"environment_name": "production",
"description": "initial token",
"expires_at": "2025-01-20T12:00:00Z",
"days_until_expiry": 5
}
],
"count": 1
}
```
## Config validation
### `POST /validate-config`
Validates a `config.yaml` file before committing to Git.
```bash cURL theme={null}
curl -X POST $BASE_URL/validate-config \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: text/yaml" \
--data-binary @config.yaml
```
```json Valid config theme={null}
{
"valid": true,
"errors": []
}
```
```json Invalid config theme={null}
{
"valid": false,
"errors": [
{
"field": "packages[0].state",
"message": "Invalid package state 'installed'. Must be one of: present, latest, absent",
"line": 5,
"column": 12
}
]
}
```
Validates:
* YAML syntax with line/column error positions
* Package states: `present`, `latest`, `absent`
* Service states: `running`, `stopped`
* File modes: valid octal (e.g., `0644`, `0755`)
* Service managers: `systemd`, `supervisor`, `supervisord`, `docker-supervisor`, `openrc`
## User management
### `GET /users`
Supports pagination and role filtering.
Maximum number of users per page (1-500).
Pagination offset.
Filter by role (`admin`, `user`, `viewer`).
### `POST /users`
```bash cURL theme={null}
curl -X POST $BASE_URL/users \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"username": "ops_user",
"password": "StrongPassword!2024",
"role": "viewer"
}'
```
### `DELETE /users/{userID}`
```bash cURL theme={null}
curl -X DELETE $BASE_URL/users/7 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{ "confirm_username": "ops_user" }'
```
## Agent endpoints
These endpoints require agent token authentication (`Authorization: Bearer `).
### `GET /agent/serverinfo`
Returns Git configuration and target commit for the authenticated agent's server.
```json Success theme={null}
{
"repo_url": "https://github.com/your-org/configs.git",
"branch": "main",
"deploy_path": "environments/staging/config.yaml",
"target_commit_hash": "8a9d3c...",
"auto_reconcile": true
}
```
### `GET /agent/git-token`
Returns a short-lived GitHub installation token when the environment uses a GitHub App.
```json Success theme={null}
{
"token": "ghs_...",
"expires_at": "2025-01-15T13:34:56Z",
"repo_url": "https://github.com/your-org/configs.git",
"provider": "github",
"installation_id": 89968159,
"authentication": "github_app"
}
```
### `PUT /agent/status`
Reports agent status to the server.
```bash cURL theme={null}
curl -X PUT $BASE_URL/agent/status \
-H "Authorization: Bearer $AGENT_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"commit_hash": "8a9d3c...",
"is_drifted": false,
"status": "Applied",
"error_message": null,
"agent_version": "v1.0.0"
}'
```
The `agent_version` field is optional but recommended. It's displayed in the UI and helps track agent deployments.
## Webhooks
### `POST /webhooks/{provider}`
Receives webhooks from Git providers. Currently supports `github`.
The webhook payload is validated using HMAC signature verification with `PULLBASE_WEBHOOK_SECRET_KEY`.
## Notification webhooks
Pullbase can send webhook notifications when drift is detected or errors occur during reconciliation.
### Configuration
Set `notification_webhook_url` when creating or updating an environment:
```bash theme={null}
curl -X PUT $BASE_URL/environments/5 \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H 'Content-Type: application/json' \
-d '{
"notification_webhook_url": "https://hooks.slack.com/services/..."
}'
```
### Webhook payload
```json theme={null}
{
"event": "drift_detected",
"timestamp": "2025-01-15T10:30:00Z",
"environment_id": 5,
"environment_name": "production",
"server_id": "web-01",
"server_name": "Web Server 1",
"details": "File /etc/nginx/nginx.conf content differs from desired state",
"commit_hash": "abc123"
}
```
**Event types:**
* `drift_detected` - Agent detected configuration drift
* `apply_error` - Error occurred during reconciliation
* `test` - Test webhook from the UI or API
### Retry behavior
Failed webhook deliveries are retried up to 3 times with exponential backoff (1s, 2s, 4s delays).
### `GET /webhook-statuses`
Returns the current webhook delivery status for all environments.
```json Success theme={null}
{
"webhook_statuses": {
"5": {
"last_delivery": "2025-01-15T12:30:00Z",
"last_status": 200,
"consecutive_failures": 0
}
}
}
```
## Web UI
Pullbase includes an embedded web dashboard accessible at `/ui/`. The UI is a single-page application (SPA) that communicates with the API.
### Routes
| Path | Description |
| ------------------ | ---------------------------------------- |
| `/` | Redirects to `/ui/` |
| `/ui/` | Dashboard home (requires authentication) |
| `/ui/login` | Login page |
| `/ui/servers` | Server list and management |
| `/ui/environments` | Environment list and management |
| `/ui/assets/*` | Static assets (JS, CSS, fonts) |
### Authentication
The web UI uses cookie-based session authentication:
1. User submits credentials to `POST /ui/login`
2. Server validates and sets `session_token` cookie
3. Subsequent requests include the cookie automatically
4. Protected routes redirect to `/ui/login` if unauthenticated
The web UI uses the same API endpoints documented above. You can use the Swagger UI at `/swagger/` to explore and test endpoints interactively.
## Metrics
Pullbase provides metrics endpoints for monitoring drift events, reconciliation success rates, and agent connectivity.
### `GET /metrics/drift`
Returns drift event metrics over a specified time period.
Number of days to include (1-90).
```json Success theme={null}
{
"period": "7 days",
"total_events": 12,
"time_series": [
{"timestamp": "2025-01-09T00:00:00Z", "value": 2},
{"timestamp": "2025-01-10T00:00:00Z", "value": 0},
{"timestamp": "2025-01-11T00:00:00Z", "value": 3},
{"timestamp": "2025-01-12T00:00:00Z", "value": 1},
{"timestamp": "2025-01-13T00:00:00Z", "value": 4},
{"timestamp": "2025-01-14T00:00:00Z", "value": 0},
{"timestamp": "2025-01-15T00:00:00Z", "value": 2}
]
}
```
### `GET /metrics/reconciliation`
Returns reconciliation success/failure metrics over time.
Number of days to include (1-90).
```json Success theme={null}
{
"period": "7 days",
"total_applied": 145,
"total_failed": 3,
"success_rate": 97.97,
"time_series": [
{"timestamp": "2025-01-09T00:00:00Z", "value": 20},
{"timestamp": "2025-01-10T00:00:00Z", "value": 22},
{"timestamp": "2025-01-11T00:00:00Z", "value": 18},
{"timestamp": "2025-01-12T00:00:00Z", "value": 21},
{"timestamp": "2025-01-13T00:00:00Z", "value": 25},
{"timestamp": "2025-01-14T00:00:00Z", "value": 19},
{"timestamp": "2025-01-15T00:00:00Z", "value": 20}
]
}
```
### `GET /metrics/connectivity`
Returns agent online/offline status for all servers.
```json Success theme={null}
{
"total_agents": 5,
"online_agents": 4,
"offline_agents": 1,
"stale_threshold": "5m0s",
"agent_statuses": [
{
"server_id": "web-01",
"server_name": "Production Web 1",
"last_seen": "2025-01-15T12:34:00Z",
"is_online": true,
"status": "Applied"
},
{
"server_id": "api-01",
"server_name": "API Server",
"last_seen": "2025-01-15T12:00:00Z",
"is_online": false,
"status": "Applied"
}
]
}
```
## Error handling
* `400 Bad Request` – validation failure (details in the `error` field)
* `401 Unauthorized` – missing or invalid token
* `403 Forbidden` – caller lacks the required role (for example, viewer attempting an admin action)
* `404 Not Found` – resource does not exist or has been deleted
* `429 Too Many Requests` – rate limited (check `Retry-After` header)
* `500 Internal Server Error` – unexpected server error (check server logs)
For production, enable native TLS (`PULLBASE_TLS_ENABLED=true`) or use HTTPS via a reverse proxy.
# CLI Reference
Source: https://docs.pullbase.io/reference/cli
Command reference for the pullbasectl utility.
New to the CLI? Check out the [CLI Guide](/guides/pullbasectl) for workflows and examples.
`pullbasectl` ships with the Pullbase Docker image and is available inside the container at `/pullbasectl`. You can run it from the host by invoking the container or by copying the binary out of the image.
```bash theme={null}
# Run pullbasectl from the official image
alias pullbasectl='docker run --rm pullbaseio/pullbase:latest pullbasectl'
```
## Global options
These options are available on most commands:
| Flag | Description |
| ------------------------ | -------------------------------------------------------- |
| `--server-url` | Pullbase server base URL (e.g., `http://localhost:8080`) |
| `--admin-token` | Admin JWT token for authentication |
| `--username` | Admin username (alternative to `--admin-token`) |
| `--password` | Admin password (use with `--username`) |
| `--password-file` | Path to file containing admin password |
| `--ca-cert` | Path to CA certificate bundle for TLS verification |
| `--insecure-skip-verify` | Skip TLS certificate verification (not recommended) |
Set the environment variable `PULLBASE_ADMIN_TOKEN` to avoid repeating `--admin-token` on every command:
```bash theme={null}
export PULLBASE_ADMIN_TOKEN=$(pullbasectl auth login ... | jq -r '.access_token')
```
## Authentication commands
### `pullbasectl auth bootstrap-admin`
Initializes the first admin using the one-time bootstrap secret.
URL of the Pullbase API.
Secret value provided by the container. Mutually exclusive with `--bootstrap-secret-file`.
Path to a file containing the bootstrap secret. Use when running inside a container.
Username for the new admin (3-64 characters, alphanumeric with `.`, `_`, `-`).
Password for the new admin (minimum 12 characters).
```bash theme={null}
pullbasectl auth bootstrap-admin \
--server-url http://localhost:8080 \
--bootstrap-secret-file /app/secrets/bootstrap.secret \
--username admin_user \
--password 'ChangeMeNow123!'
```
```text theme={null}
Admin bootstrap completed successfully.
Username: admin_user
Access token (store securely):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```
### `pullbasectl auth login`
Exchange a username and password for an admin JWT.
```bash theme={null}
pullbasectl auth login \
--server-url http://localhost:8080 \
--username admin_user \
--password 'ChangeMeNow123!'
```
Use the returned token for subsequent CLI calls with `--admin-token`.
## Server commands
Manage servers (agents) registered with Pullbase.
### `pullbasectl servers list`
List all servers, optionally filtered by environment.
Filter servers by environment ID.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl servers list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT
```
```
ID NAME ENVIRONMENT STATUS DRIFTED AUTO-RECONCILE
web-01 Production Web production Applied no yes
api-01 API Server production Applied no yes
staging-01 Staging Server staging Drifted yes no
```
Filter by environment:
```bash theme={null}
pullbasectl servers list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--environment-id 1
```
### `pullbasectl servers create`
Register a new server.
Unique server identifier (e.g., `web-01`, `api-prod-1`).
Human-readable server name.
Environment ID to associate with this server.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl servers create \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id web-02 \
--name "Production Web 2" \
--environment-id 1
```
```text theme={null}
Server created successfully.
ID: web-02
Name: Production Web 2
Environment: 1
Auto-Reconcile: false
```
### `pullbasectl servers get`
Get detailed information about a specific server.
Server ID to retrieve.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl servers get \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id web-01
```
```text theme={null}
Server: web-01
Name: Production Web
Environment: production
Auto-Reconcile: true
Status: Applied
Commit: a1b2c3d
Drifted: no
Last Seen: 2025-01-15T12:30:00Z
Created: 2025-01-01T09:00:00Z
```
### `pullbasectl servers delete`
Delete a server registration.
Server ID to delete.
Skip confirmation prompt.
Non-interactive stdin (piped/CI) requires `--force`; otherwise deletion is blocked to prevent accidental removal.
```bash theme={null}
pullbasectl servers delete \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id web-02 \
--force
```
```text theme={null}
Server 'web-02' deleted successfully.
```
### `pullbasectl servers install-script`
Generate a one-liner install script for deploying the agent to a server.
Server ID.
Agent token for authentication (from `tokens create`).
Agent version to install. Omitted from the request when set to `latest`.
```bash theme={null}
pullbasectl servers install-script \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id web-01 \
--token pbt_9BFzQYxK...
```
The output is a shell script you can pipe directly to bash on the target server:
```bash theme={null}
pullbasectl servers install-script ... | ssh user@server 'sudo bash'
```
## Environment commands
Manage environments (Git repositories) that Pullbase tracks.
### `pullbasectl environments list`
List all environments.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl environments list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT
```
```
ID NAME REPO BRANCH STATUS AUTO-RECONCILE
1 production https://github.com/acme/infra-config main active yes
2 staging https://github.com/acme/infra-config staging active no
```
### `pullbasectl environments create`
Create a new environment.
Environment name.
Git repository URL.
Git branch to track.
Path within the repository where config files are located.
GitHub App installation ID (required for private repos).
Output format: `table` or `json`.
```bash theme={null}
pullbasectl environments create \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--name production \
--repo-url https://github.com/acme/infra-config \
--branch main \
--deploy-path configs/production
```
```text theme={null}
Environment created successfully.
ID: 1
Name: production
Repository: https://github.com/acme/infra-config
Branch: main
Deploy Path: configs/production
Status: pending
Auto-Reconcile: false
```
### `pullbasectl environments get`
Get detailed information about an environment.
Environment ID to retrieve.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl environments get \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id 1
```
```text theme={null}
Environment: production (ID: 1)
Repository: https://github.com/acme/infra-config
Branch: main
Deploy Path: configs/production
Provider: github
Installation: 12345678
Status: active
Auto-Reconcile: true
Deployed: a1b2c3d4e5f6
Last Webhook: 2025-01-15T12:00:00Z
Created: 2025-01-01T09:00:00Z
Updated: 2025-01-15T12:00:00Z
```
### `pullbasectl environments delete`
Delete an environment. This will affect all associated servers.
Environment ID to delete.
Skip confirmation prompt.
```bash theme={null}
pullbasectl environments delete \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id 2 \
--force
```
```text theme={null}
Environment 2 deleted successfully.
```
### `pullbasectl environments rollback`
Initiate a rollback to a previous commit.
Environment ID.
Target commit hash to rollback to.
Reason for the rollback (for audit purposes).
Output format: `table` or `json`.
```bash theme={null}
pullbasectl environments rollback \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id 1 \
--commit a1b2c3d \
--reason "Reverting broken nginx config"
```
```text theme={null}
Rollback initiated successfully.
ID: 42
Environment: 1
From: f6e5d4c
To: a1b2c3d
Status: pending
Reason: Reverting broken nginx config
```
### `pullbasectl environments rollback-list`
List rollback history for an environment.
Environment ID.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl environments rollback-list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--id 1
```
```
ID FROM TO STATUS CREATED
42 f6e5d4c a1b2c3d completed 2025-01-15T12:30:00Z
38 b2c3d4e f6e5d4c completed 2025-01-10T09:15:00Z
```
## Status commands
View fleet-wide or per-server status.
### `pullbasectl status`
Display server status. Requires one of `--server-id`, `--environment-id`, or `--all`.
Show status for a specific server.
Show status for all servers in an environment.
Show fleet-wide status overview.
Output format: `table` or `json`.
Continuously refresh status.
Refresh interval in seconds (with `--watch`).
**Single server status:**
```bash theme={null}
pullbasectl status \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--server-id web-01
```
**Environment status:**
```bash theme={null}
pullbasectl status \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--environment-id 1
```
**Fleet-wide status:**
```bash theme={null}
pullbasectl status \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--all
```
```
Fleet Status Summary
Total: 5 servers
Healthy: 4
Drifted: 1
Errors: 0
Unknown: 0
SERVER ENVIRONMENT STATUS DRIFTED COMMIT LAST SEEN
web-01 production Applied no a1b2c3d 2 minutes ago
web-02 production Applied no a1b2c3d 1 minute ago
api-01 production Applied no a1b2c3d 3 minutes ago
staging-01 staging Drifted yes b2c3d4e 5 minutes ago
dev-01 development Applied no c3d4e5f just now
```
**Watch mode (live refresh):**
```bash theme={null}
pullbasectl status \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--all \
--watch \
--interval 10
```
This clears the screen and refreshes status every 10 seconds. Press `Ctrl+C` to stop.
## Config validation
### `pullbasectl validate-config`
Validate a `config.yaml` file before deploying to your Git repository.
This command performs local validation only and does not require a connection to the Pullbase server. You can use it in CI pipelines or pre-commit hooks without authentication.
Path to the config.yaml file to validate.
Output format: `table` or `json`.
```bash theme={null}
pullbasectl validate-config --file ./config.yaml
```
```text theme={null}
✓ Config is valid
```
If validation fails:
```bash theme={null}
pullbasectl validate-config --file ./broken-config.yaml
```
```
✗ Config has 2 error(s):
FIELD LINE MESSAGE
files[0].path 5 path is required
files[1].content 12 content cannot be empty when source is not specified
```
Use `--output json` for programmatic parsing:
```bash theme={null}
pullbasectl validate-config --file ./config.yaml --output json
```
## GitHub App commands
### `pullbasectl github-app bootstrap`
Validates GitHub App credentials locally and, when combined with `--server-url`, registers an environment.
Key flags:
* `--app-id`, `--private-key`, `--installation-id`, `--repository-id`, `--app-slug`
* `--server-url`, `--admin-token` (optional) to create the environment in Pullbase
* `--environment-name`, `--repo-url`, `--branch`, `--deploy-path`
### `pullbasectl github-app status`
Retrieve the GitHub App status for an environment:
```bash theme={null}
pullbasectl github-app status \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--environment-id 5
```
## Agent token commands
### `pullbasectl tokens list`
List agent tokens for a server.
```bash theme={null}
pullbasectl tokens list \
--server-url http://localhost:8080 \
--server-id web-01 \
--admin-token $ADMIN_JWT
```
Output format:
```
ID Description Created Expires Last Used Active
1 initial 2025-01-15T12:00:00Z - 2025-01-15T12:30:00Z true
```
### `pullbasectl tokens create`
Create an agent token.
Server identifier.
Token description (e.g., "ansible integration").
Optional expiration in days.
```bash theme={null}
pullbasectl tokens create \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--server-id web-01 \
--description "blue-green deployment" \
--expires-in 30
```
```text theme={null}
Token created successfully. Store this value securely:
pbt_9BFzQYxK...
Installation instructions:
Configure AGENT_TOKEN with this value when deploying the agent.
```
### `pullbasectl tokens revoke`
Deactivate a token by ID.
Server identifier.
Token ID to revoke.
```bash theme={null}
pullbasectl tokens revoke \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--server-id web-01 \
--token-id 17
```
## User commands
### `pullbasectl users create`
Create a new user.
Username for the new user (3-64 characters).
Password for the new user (minimum 12 characters).
Path to file containing the new user password (alternative to `--new-password`).
Role for the new user (`admin`, `user`, or `viewer`).
```bash theme={null}
pullbasectl users create \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--new-username ops_user \
--new-password 'VerySecurePass!2024' \
--role viewer
```
### `pullbasectl users list`
Paginate through active users.
Filter by role (`admin`, `user`, or `viewer`).
Maximum users to return (1-500).
Pagination offset.
```bash theme={null}
pullbasectl users list \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--role admin \
--limit 50
```
### `pullbasectl users delete`
Delete a user account.
ID of the user to delete.
Username of the account to delete (used as confirmation).
Deletion is blocked if you attempt to delete the last active admin or your own account. The `--delete-acct-username` flag serves as confirmation and must match the username associated with the user ID.
```bash theme={null}
pullbasectl users delete \
--server-url http://localhost:8080 \
--admin-token $ADMIN_JWT \
--user-id 7 \
--delete-acct-username "SomeAccount123"
```
## Bootstrap wizard (interactive)
`pullbasectl bootstrap wizard` guides you through first-run setup, including admin creation and GitHub App configuration. The wizard prompts for values interactively and saves them to the server via the API.
```bash theme={null}
pullbasectl bootstrap wizard
```
# Security & Hardening
Source: https://docs.pullbase.io/security-hardening
Secure secrets, TLS, and operational access before running Pullbase in production.
Pullbase manages critical infrastructure. Follow these guidelines to reduce risk when operating at scale.
## Secret management
* Store `PULLBASE_JWT_SECRET`, `PULLBASE_WEBHOOK_SECRET_KEY`, and database credentials in your secret manager (AWS Secrets Manager, HashiCorp Vault, etc.). Inject them into Docker at runtime rather than committing them to disk.
* Mount GitHub App private keys from a read-only path (`/config/github-app.pem`) and restrict filesystem permissions so only the Pullbase container can read them.
* Rotate agent tokens regularly. Tokens are hashed at rest, but compromised tokens allow full configuration access for that server.
* Use unique credentials per environment when possible.
## TLS configuration
Pullbase supports two approaches for securing connections:
### Option 1: Native TLS
Pullbase can handle TLS directly without a reverse proxy:
```bash theme={null}
PULLBASE_TLS_ENABLED=true
PULLBASE_TLS_CERT_PATH=/etc/pullbase/certs/server.crt
PULLBASE_TLS_KEY_PATH=/etc/pullbase/certs/server.key
```
For development, you can rely on Pullbase to auto-generate self-signed ECDSA P-256 certificates when TLS is enabled and cert/key files are missing (or use the `--generate-dev-certs` flag). **Never use self-signed certificates in production.**
Native TLS is ideal for:
* Simple deployments without existing reverse proxy infrastructure
* Direct agent-to-server communication
* Reduced architectural complexity
### Option 2: Reverse proxy TLS termination
For production environments with existing infrastructure:
* Deploy a reverse proxy (NGINX, Traefik, Caddy, or cloud load balancer) in front of Pullbase.
* Terminate TLS at the reverse proxy using CA-signed certificates.
* Ensure agents connect to the HTTPS endpoint, not the internal HTTP port.
* Distribute the CA chain to agents if using internal PKI.
* Set `X-Forwarded-Proto: https` so secure cookies remain marked `Secure` even when TLS is terminated upstream.
```nginx theme={null}
# Example: NGINX TLS termination
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;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
location / {
proxy_pass http://localhost:8080;
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;
}
}
```
Never expose the internal HTTP port directly to the internet. Agent tokens and user credentials are transmitted in requests.
## Agent TLS verification
Agents validate the TLS certificate of the Pullbase server:
* Set `CACERT_PATH` to a CA bundle that trusts your certificate.
* Never enable `SKIP_TLS_VERIFY=true` in production—it disables certificate validation.
* Keep CA bundles up to date across your fleet.
## Access control
* Create dedicated viewer accounts for read-only dashboards and audits.
* Enforce strong passwords (12+ characters with complexity) and rotate admin credentials periodically.
* Restrict CLI usage to secure workstations. Store admin tokens in environment variables only temporarily.
* Monitor audit logs (stored in the `audit_log` table) for suspicious activity. Integrate with your SIEM by streaming logs from the container.
* **CORS policy:** In production, only explicitly configured origins (`PULLBASE_CORS_ORIGINS`) and same-origin requests are allowed. Avoid setting `PULLBASE_ENV=development` in production—it enables permissive localhost origins.
## Database hardening
Pullbase supports SQLite (default) and PostgreSQL. Choose hardening steps based on your deployment.
### SQLite (single-instance deployments)
* Store `pullbase.db` outside the container on a persistent volume with strict file permissions (`chmod 600`).
* Schedule file-level backups (e.g., `sqlite3 pullbase.db ".backup /backups/pullbase-$(date +%Y%m%d).db"`).
* Enable WAL mode for better concurrency: `PRAGMA journal_mode=WAL;` (applied automatically by Pullbase).
* Keep the database file on fast local storage; network-attached storage may introduce latency.
### PostgreSQL (high-availability deployments)
* Enable TLS connections between Pullbase and PostgreSQL. Set `PULLBASE_DB_SSLMODE=require` or `PULLBASE_DB_SSLMODE=verify-full`.
* Grant Pullbase a least-privilege Postgres role (ownership of the `pullbasedb` database only).
* Schedule automated backups using `pg_dump` and test restore procedures.
### General
* Without the database, you lose environment definitions, tokens, and drift history. Test restores regularly.
## Network segmentation
* Place Pullbase in a private subnet behind a load balancer. Expose only port 443 (TLS) via the reverse proxy.
* Allow outbound connections only to required destinations: your Git provider, webhook endpoints, and package repositories.
* Restrict inbound traffic between agents and Pullbase using security groups or firewall rules.
## Agent security
* Run agents with minimal privileges. Grant access only to files and services defined in `config.yaml`.
* For containerized agents, carefully consider host filesystem mounts—they effectively grant root access.
* Rotate agent tokens regularly. Create a new token, update the agent, then revoke the old token.
* Monitor agent status for unexpected disconnections or authentication failures.
## Webhook notification security
When configuring notification webhooks for environments:
* **Use HTTPS endpoints only.** Pullbase validates TLS certificates by default.
* **Validate webhook source.** If your receiving service supports it, verify requests come from your Pullbase server's IP.
* **Keep webhook URLs confidential.** Treat them like secrets; leaked URLs could be used for social engineering.
* **Monitor webhook failures.** Failed deliveries may indicate network issues or compromised endpoints.
Webhooks are retried 3 times with exponential backoff (1s, 2s, 4s). Configure alerts in your receiving service for gaps in expected notifications.
## Logging & monitoring
* Forward container logs to your log aggregation platform. Use `PULLBASE_LOG_FORMAT=json` for structured logging.
* Collect agent logs (especially when running as systemd units) for drift diagnostics.
* Monitor GitHub App rate limits and webhook delivery failures; increasing poll intervals or adjusting webhook retries can prevent throttling.
* Consider adding synthetic checks that call `/api/v1/healthz` to detect API regressions.
## Upgrades and patching
* Pin Pullbase and agent images (`pullbaseio/pullbase:vX.Y.Z`) to avoid unintended upgrades.
* Review release notes (GitHub Releases) and test upgrades in staging. Watch migration logs closely.
* Apply OS patches to hosts running agents—Pullbase focuses on desired state, not OS patch management.
Document your disaster recovery plan: bootstrap steps, database restore procedures, certificate replacement, and token rotation. Regular tabletop exercises help ensure you can rebuild Pullbase quickly if needed.
# Web UI Walkthrough
Source: https://docs.pullbase.io/web-ui
Explore the Pullbase web interface for day-to-day operations.
The Pullbase web UI offers a fast way to review environments, manage users, and issue agent tokens. Use it alongside the CLI and API for visibility and manual operations.
## Sign-in
* Visit your Pullbase URL (e.g., `http://localhost:8080` for development or `https://pullbase.example.com` for production).
* Enter the admin or user credentials created during bootstrapping.
* The session cookie stores a JWT; sign out via the avatar menu when finished.
## Dashboard
The landing page summarizes key metrics:
* Environment count and drift status
* Recent activity feed (status reports, token creation, rollbacks)
* Quick links to environments and servers
## Environments view
Navigate to **Environments** to manage Git metadata.
* Search or filter environments by name
* Inspect repository URL, branch, deploy path, and target commit hash
* Toggle auto-reconcile, edit metadata, or delete the environment
* Initiate rollbacks and view rollback history
## Servers View
Navigate to **Servers** to see all Pullbase managed servers.
* Search or filter servers by name
* Inspect environment association and status
* View last applied commit and drift status
## Server detail view
From an environment page, choose a server to see its status timeline.
* View last applied commit, drift flag, and timestamps
* Monitor reconciliation history and error messages
* Issue or revoke agent tokens directly from the server page
## Token management
* Use **Add Token** to generate a new agent token
* Tokens include description and optional expiration date
* Copy the token immediately; Pullbase never shows the secret again (tokens are hashed at rest)
Rotate tokens by creating a new one, updating the agent, and then revoking the old token. Avoid deleting active tokens before the agent refreshes.
## User management
Accessible via **Settings → Users** (admins only).
* Create, list, or delete users
* Set roles (`admin`, `user`, `viewer`)
* Deletion requires typing the username to confirm; Pullbase blocks deletion of the last active admin or your own account