Skip to content

Docker Installation

Marc Pope edited this page Apr 14, 2026 · 10 revisions

Docker Installation

Run BBS in a Docker container — no OS dependencies to install. This is the fastest way to get BBS running and is ideal for testing, home labs, and production environments where you prefer containerized deployments.

⚠️ Use a dedicated container

BBS runs its own MariaDB, ClickHouse, Apache, SSH daemon, and cron scheduler inside a single container. It is not designed to share a container with another application, share its data volume with another service, or mount its data directory inside another container. If you want to back up another server or container, install the BBS agent there — not the full server.

Prerequisites

  • Docker Engine 20.10+ and Docker Compose v2
  • 2 GB RAM minimum (4 GB+ recommended)
  • Two available ports: one for the web UI (default 8080) and one for SSH/borg connections (default 2222)

ARM note: BBS runs on both x86_64 and ARM64 (Apple Silicon, Raspberry Pi 4+, etc.). However, ClickHouse — used for the file-level search catalog — does not support older ARM chips (ARMv8.0). BBS works fine without ClickHouse; you just won't have file-level browsing on the Restore page (you can still restore by typing paths manually).

Quick Start

Download the compose file and environment template:

curl -sO https://raw.githubusercontent.com/marcpope/borgbackupserver/main/docker-compose.yml
curl -sO https://raw.githubusercontent.com/marcpope/borgbackupserver/main/.env.example
cp .env.example .env

Edit .env with your server's IP or hostname:

APP_URL=http://192.168.1.50:8080
WEB_PORT=8080
SSH_PORT=2222

Start the container:

docker compose up -d

The pre-built image (marcpope/borgbackupserver) is pulled automatically from Docker Hub.

Get admin credentials from the container logs:

docker compose logs bbs

Look for the credentials block:

========================================
  ADMIN CREDENTIALS (SAVE THESE!)
========================================
  URL:      http://192.168.1.50:8080
  Username: admin
  Password: <random password>
========================================

Open your server's URL and log in.

Docker Desktop (Without Compose)

If you pull the image directly from Docker Desktop without using docker-compose.yml, a first-run setup modal appears on your first admin login. It prompts you to enter your server hostname, web port, and SSH port so agents can connect. Set these to match the port mappings you configured when creating the container.

Configuration

All configuration is done through a .env file (or environment variables). The docker-compose.yml reads variables from .env automatically — ports are defined once and used in both port mappings and container environment.

Variable Default Description
APP_URL http://localhost:8080 Public URL used by browsers and agents to reach BBS
WEB_PORT 8080 Host port for the web UI (mapped to container port 80)
SSH_PORT 2222 Host port for SSH/borg connections (mapped to container port 22)
ADMIN_PASS (auto-generated) Admin password — only used on first run. Omit to auto-generate (shown in logs)

Important: APP_URL must use your server's real IP or hostname — not localhost — for agents on other machines to connect.

Setting a Custom Admin Password

To set a specific admin password, add it to your .env file:

ADMIN_PASS=your-strong-password-here

This is only applied on first run when the database is initialized. Changing it later has no effect — use the web UI to change passwords after initial setup.

Production Configuration

For a production server with a real hostname and HTTPS (via a reverse proxy):

APP_URL=https://backups.example.com
WEB_PORT=443
SSH_PORT=2222

You would place an HTTPS reverse proxy (nginx, Caddy, Traefik) in front of the container to handle SSL termination. The container itself serves HTTP on port 80.

Ports

BBS needs two ports: HTTP for the web UI and API, and SSH for borg backup data transfer. Both are controlled by variables in your .env file:

WEB_PORT=8080    # Web UI (HTTP) — mapped to container port 80
SSH_PORT=2222    # SSH for borg agent connections — mapped to container port 22

The docker-compose.yml uses these variables in both port mappings and the container environment, so they stay in sync automatically. To change ports, just edit .env — no need to touch docker-compose.yml.

For example, to use standard ports:

APP_URL=https://backups.example.com
WEB_PORT=443
SSH_PORT=22

Data & Storage

Docker Volume (Default)

All persistent data lives in a single Docker volume (bbs-data) mounted at /var/bbs inside the container:

Path Contents
/var/bbs/home/ Borg repositories — one directory per client
/var/bbs/mysql/ MariaDB database files
/var/bbs/clickhouse/ ClickHouse catalog database (file-level search index)
/var/bbs/config/ Application .env file (contains encryption key — do not lose)
/var/bbs/backups/ Server self-backup archives
/var/bbs/cache/ Borg cache (speeds up backup operations)
/var/bbs/.ssh-host-keys/ SSH host keys (persisted so agents don't see host key warnings after updates)

Each registered client gets its own SSH user and home directory under /var/bbs/home/. Borg repositories are created inside each client's home directory. This is all handled automatically when you add a client through the web UI.

Important: The .env file at /var/bbs/config/.env contains the APP_KEY which encrypts SSH keys and S3 credentials stored in the database. If this key is lost, all encrypted data becomes unrecoverable. This file is created on first run and stored on the persistent volume.

Bind Mount (Specific Disk)

Named volumes are strongly recommended. Bind mounts require careful per-subdirectory ownership because BBS runs multiple services as different UIDs inside the container (MariaDB, ClickHouse, Apache, root). Named volumes avoid this entirely.

To store data on a specific disk or partition instead of a Docker volume:

volumes:
  - /path/to/your/storage:/var/bbs

Remove the volumes: section at the bottom of the compose file (the bbs-data: volume definition) when using a bind mount.

Ensure the parent directory exists before starting the container. The entrypoint creates the required subdirectories with the correct ownership on first run.

Bind Mount Troubleshooting

If the container exits immediately after "Starting MariaDB..." or you see Permission denied errors touching /var/bbs/*, the host directory is blocking the entrypoint's chown calls. This commonly happens with:

  • Rootless Docker / Podman — the container "root" is mapped to a subuid on the host. Use a host path owned by the host user running Docker, or add :U (Podman) to the volume to auto-remap ownership.
  • SELinux-enforcing hosts — add :Z or :z to the volume mount: - /path/to/storage:/var/bbs:Z
  • Bind-mounted network filesystems (NFS, SMB) — may silently reject chown. Use a local disk for the /var/bbs mount and add network shares only as additional storage locations (see below).

Required subdirectory ownership

BBS creates these subdirectories inside /var/bbs on first start, each owned by the specific service that uses it:

Path Owner (inside container) UID
/var/bbs/mysql/ mysql 100
/var/bbs/clickhouse/ clickhouse 999
/var/bbs/config/ root 0
/var/bbs/home/ www-data 33
/var/bbs/cache/ www-data 33
/var/bbs/backups/ www-data 33
/var/bbs/tmp/ www-data 33 (mode 1777)
/var/bbs/.ssh-host-keys/ root 0

You normally do not need to set these manually — the entrypoint chowns them on first run. Only intervene if chowns are being blocked by your host setup.

Additional Storage (NFS / Multiple Disks)

BBS supports multiple storage locations, letting you spread repositories across different disks or NFS mounts. The default location (/var/bbs/home) stores client home directories. Additional locations are used for repository storage only.

To add a second disk or NFS share in Docker:

  1. Add the volume mount to docker-compose.yml:

    services:
      bbs:
        volumes:
          - bbs-data:/var/bbs                    # existing default volume
          - /mnt/disk2:/mnt/disk2                # additional local disk

    For NFS, you can use a Docker NFS volume:

    services:
      bbs:
        volumes:
          - bbs-data:/var/bbs
          - nfs-storage:/mnt/nfs-backup
    
    volumes:
      bbs-data:
      nfs-storage:
        driver: local
        driver_opts:
          type: nfs
          o: addr=192.168.1.100,rw,nfsvers=3,nolock
          device: ":/volume1/bbs-storage"

    Replace 192.168.1.100 and /volume1/bbs-storage with your NFS server IP and export path.

  2. Restart the container:

    docker compose up -d
  3. Add the storage location in BBS:

    • Go to Storage in the sidebar
    • Click Add Location
    • Enter a label and the path (e.g., /mnt/disk2 or /mnt/nfs-backup — must match the path inside the container)
    • Click Create
  4. Create repositories on the new location:

    • Go to any client, click Add Repository
    • Select the new storage location from the dropdown

Important for NFS: The NFS server must allow full read/write access from the Docker container. If using a Synology NAS, set the NFS Squash to "Map all users to admin". See Storage Setup for detailed NFS configuration instructions and troubleshooting.

Remote SSH Storage

You don't have to store all repositories locally. BBS supports creating borg repositories on remote SSH hosts like rsync.net, BorgBase, and Hetzner Storage Box. With remote storage, backup data flows directly from agents to the remote host — no local disk is used for repository data, only catalog metadata is stored in the BBS database.

This is especially useful with Docker deployments where local storage may be limited. See Remote Storage for setup details.

What Happens on Startup

The application code and all dependencies are baked into the Docker image at build time — no downloads occur at runtime. The container's entrypoint script handles runtime setup on each start:

  1. Restores SSH host keys — from the persistent volume (creates them on first run)
  2. Starts MariaDB — initializes the data directory on first run
  3. Starts ClickHouse — catalog engine for file-level search (optional, skipped if not available)
  4. Creates .env configuration — auto-generated on first run, persisted on the data volume
  5. Imports database schema — on fresh installs only
  6. Runs migrations — applies any pending database migrations
  7. Recreates SSH users — restores client SSH users from the database and persistent volume
  8. Starts SSH server — for agent borg connections
  9. Starts cron scheduler — runs the backup job queue every minute
  10. Starts Apache — serves the web UI

SSH Host Key Persistence

SSH host keys are stored on the persistent volume (/var/bbs/.ssh-host-keys/). This ensures agents don't see "host key changed" errors after a container rebuild or update. The keys are created on first run and restored on subsequent starts.

Updates

Pull the latest image and recreate the container:

docker compose pull
docker compose up -d

The new image includes the updated application code. On startup, any pending database migrations are applied automatically. Your data is stored on the Docker volume and is preserved across updates.

You can check for available updates from the web UI at Settings > Updates.

Pinning a Version

To pin to a specific release instead of always getting the latest:

image: marcpope/borgbackupserver:v2.15.0

Reverse Proxy Setup

For production deployments with HTTPS, place a reverse proxy in front of the BBS container.

Nginx Example

server {
    listen 443 ssl;
    server_name backups.example.com;

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

    client_max_body_size 100M;

    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;
    }
}

Caddy Example

backups.example.com {
    reverse_proxy localhost:8080
}

Set APP_URL=https://backups.example.com in your docker-compose.yml so agents and the web UI use the correct URL.

Backup & Restore of BBS Itself

Volume-Level Backup

The Docker volume contains everything — database, repositories, SSH keys, and configuration. To back up the entire volume:

docker compose stop
docker run --rm -v bbs-data:/data -v $(pwd):/backup alpine tar czf /backup/bbs-backup.tar.gz -C /data .
docker compose start

To restore a volume backup:

docker compose stop
docker run --rm -v bbs-data:/data -v $(pwd):/backup alpine sh -c "rm -rf /data/* && tar xzf /backup/bbs-backup.tar.gz -C /data"
docker compose start

Restoring from S3

If you have S3 credentials configured with "Sync server backups" enabled, you can restore a fresh Docker container directly from S3 through the web UI:

  1. Start a fresh container
  2. Log in and go to Storage > S3 Sync Settings
  3. Enter the same S3 credentials used by your previous server
  4. Scroll down to Restore from S3 Backup and click List Available Backups
  5. Click Restore on the backup you want
  6. New admin credentials are displayed — use them to log in

This restores the database, configuration, SSH keys, and recreates all Unix users. Repository data can then be restored per-repo using the S3 Restore feature on each client. See Server Backup and Restore for full details.

Building from Source

To build the Docker image locally instead of pulling from Docker Hub:

git clone https://github.com/marcpope/borgbackupserver.git
cd borgbackupserver
docker compose up -d --build

Or uncomment the build line in docker-compose.yml and comment out the image line:

# image: marcpope/borgbackupserver:latest
build: .

Podman

BBS also runs in rootless Podman. The first-run setup modal detects both Docker and Podman containers.

Quadlet Example (systemd)

Create ~/.config/containers/systemd/borgbackupserver.container:

[Unit]
Description=Borg Backup Server Container
After=network-online.target

[Container]
Image=docker.io/marcpope/borgbackupserver:latest
ContainerName=borgbackupserver
PublishPort=2222:22
PublishPort=8080:80
AutoUpdate=registry
Volume=/path/to/storage:/var/bbs:Z

[Service]
Restart=always

[Install]
WantedBy=multi-user.target default.target

The :Z flag on the volume is required for SELinux (RHEL, Alma, Rocky, Fedora).

Enable auto-start without login:

loginctl enable-linger
systemctl --user daemon-reload
systemctl --user start borgbackupserver

Thanks to @sainf for testing and sharing the Podman setup.

Troubleshooting

Container Won't Start

Check logs:

docker compose logs bbs

Common causes:

  • Port conflict — another service is using port 8080 or 2222
  • Insufficient disk space for the Docker volume

Agent Can't Connect via SSH

Verify the SSH port is accessible:

ssh -p 2222 bbs-testclient@your-server-ip

Common causes:

  • SSH_PORT doesn't match the host-side port mapping
  • Firewall blocking the SSH port
  • Agent configured with wrong server hostname or port

Database Issues After Update

Migrations run automatically on container start. If you encounter errors:

docker compose exec bbs bash
cd /var/www/bbs
for f in migrations/*.sql; do mysql -u bbs -pbbs bbs < "$f" 2>/dev/null; done

NFS Permission Denied

If repositories on NFS storage fail with "Permission denied", check the NFS server's user mapping settings. See Storage Setup#NFS Troubleshooting for detailed solutions.

Starting Fresh

To completely reset BBS and start over:

docker compose down -v    # removes container AND volume
docker compose up -d      # fresh start

Warning: This deletes all data including backups, configuration, and client registrations.


Next: Getting Started | Linux Agent Setup | Remote Storage | Storage Setup

Clone this wiki locally