Managing Task Assignment in Agentic Workflows · FrankBoard

How to Deploy a Work Board Using Docker and PostgreSQL

A production-ready work board on a VPS requires Docker, PostgreSQL, and a reverse proxy with TLS termination. FrankBoard ships with an official Compose stack that handles database initialization, persistent volumes, and health checks in a single file. This guide walks through the complete deployment from bare server to working board.

How to Deploy a Work Board Using Docker and PostgreSQL

Prerequisites and Server Setup

Start with a VPS running Ubuntu 22.04 LTS or Debian 12. FrankBoard runs comfortably on a 2 vCPU, 2 GB RAM instance for teams under 20 users. Reserve at least 20 GB of SSD storage for the database, uploaded files, and Docker overhead.

Install Docker Engine and Docker Compose plugin:

sudo apt update && sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Create a dedicated directory and user:

sudo mkdir -p /opt/frankboard
sudo useradd -r -s /bin/false frankboard
sudo chown -R frankboard:frankboard /opt/frankboard
cd /opt/frankboard

The Docker Compose Configuration

FrankBoard distributes a reference docker-compose.yml that orchestrates three services: the application container, a PostgreSQL 16 database, and an optional Redis cache for session storage and rate limiting. Place this file at /opt/frankboard/docker-compose.yml:

version: "3.8"

services:
  app:
    image: ghcr.io/frankboard/frankboard:latest
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://frankboard:${DB_PASSWORD}@db:5432/frankboard?sslmode=disable
      REDIS_URL: redis://redis:6379/0
      FRANKBOARD_URL: https://board.yourdomain.com
      FRANKBOARD_SECRET_KEY: ${SECRET_KEY}
    volumes:
      - ./data/files:/var/www/html/data/files
      - ./data/plugins:/var/www/html/plugins
      - ./data/config:/var/www/html/config
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - frankboard

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: frankboard
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: frankboard
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U frankboard -d frankboard"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - frankboard

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - ./data/redis:/data
    networks:
      - frankboard

networks:
  frankboard:
    driver: bridge

Create the environment file at /opt/frankboard/.env:

DB_PASSWORD=$(openssl rand -base64 32)
SECRET_KEY=$(openssl rand -base64 48)
echo "DB_PASSWORD=${DB_PASSWORD}" > .env
echo "SECRET_KEY=${SECRET_KEY}" >> .env
chmod 600 .env

The DATABASE_URL follows PostgreSQL's standard connection URI format. FrankBoard uses this single variable to derive host, port, credentials, and database name. The sslmode=disable parameter is safe inside the Docker network; TLS between containers adds no security benefit and complicates certificate management.

Initial Deployment and Verification

Launch the stack:

sudo docker compose up -d
sudo docker compose logs -f app

Wait for the database health check to pass and migrations to complete. The first startup prints migration progress, then settles into a ready state. Verify with:

sudo docker compose ps
sudo docker compose exec db pg_isready -U frankboard

Both services should report healthy. The application exposes port 8080 by default inside the container network; external access routes through the reverse proxy configured next.

Reverse Proxy and TLS with Caddy

Caddy handles automatic HTTPS certificates and provides a cleaner configuration than raw Nginx for single-site deployments. Install Caddy:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update && sudo apt install caddy

Create /etc/caddy/Caddyfile:

board.yourdomain.com {
    reverse_proxy localhost:8080
    encode gzip
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
        Referrer-Policy "strict-origin-when-cross-origin"
    }
}

Reload Caddy and verify certificate issuance:

sudo systemctl reload caddy
sudo caddy validate --config /etc/caddy/Caddyfile

Backup Strategy

PostgreSQL backups require dumping the database while the application remains running. FrankBoard's state splits into two categories: the relational database and the file volume containing attachments, plugin code, and configuration overrides.

Create /opt/frankboard/backup.sh:

#!/bin/bash
set -euo pipefail

BACKUP_DIR="/opt/frankboard/backups"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "${BACKUP_DIR}"

# Database dump
sudo docker compose exec -T db pg_dump -U frankboard frankboard | gzip > "${BACKUP_DIR}/db_${TIMESTAMP}.sql.gz"

# File archive
sudo tar czf "${BACKUP_DIR}/files_${TIMESTAMP}.tar.gz" -C /opt/frankboard data/files data/plugins data/config

# Retention: keep 14 days
find "${BACKUP_DIR}" -name "*.sql.gz" -mtime +14 -delete
find "${BACKUP_DIR}" -name "*.tar.gz" -mtime +14 -delete

echo "Backup completed: ${TIMESTAMP}"
chmod +x /opt/frankboard/backup.sh

Schedule daily execution:

sudo ln -s /opt/frankboard/backup.sh /etc/cron.daily/frankboard-backup

Restore from backup by stopping the stack, wiping the PostgreSQL volume, recreating it, then replaying the dump:

sudo docker compose down
sudo rm -rf /opt/frankboard/data/postgres/*
sudo docker compose up -d db
sleep 5
zcat /opt/frankboard/backups/db_20240115_030000.sql.gz | sudo docker compose exec -T db psql -U frankboard frankboard
sudo docker compose up -d

Performance Tuning

PostgreSQL benefits from modest tuning on small VPS instances. Create /opt/frankboard/data/postgres/postgresql.conf with these overrides appended after container initialization:

# Append to postgresql.conf after first start
shared_buffers = 512MB
effective_cache_size = 1536MB
maintenance_work_mem = 128MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 5242kB
min_wal_size = 1GB
max_wal_size = 4GB

Restart the database container to apply changes. For teams exceeding 15 active users, scale the application horizontally by increasing work_mem and shared_buffers proportionally to available RAM.

Security Hardening

FrankBoard inherits Kanboard's plugin architecture, which means third-party extensions run with full application privileges. Isolate the deployment with these practices:

For teams evaluating self-hosted versus cloud alternatives, this deployment model keeps data under direct control. No third party processes task contents, attachment names, or user metadata.

Migration from Existing Kanboard Installations

Teams running vanilla Kanboard can migrate by reusing the same PostgreSQL schema. FrankBoard maintains database compatibility with upstream Kanboard releases through version 1.2.35. The migration path:

  1. Back up the existing Kanboard database using pg_dump or the built-in JSON export.
  2. Deploy FrankBoard with a fresh database container.
  3. Restore the dump into the new PostgreSQL instance.
  4. Run docker compose exec app ./cli migrate to apply any FrankBoard-specific schema additions.
  5. Copy data/files and data/plugins from the old installation to the new volume mounts.

Plugin compatibility varies. FrankBoard ships with a curated set of modern UI extensions pre-installed; legacy Kanboard plugins may conflict with these replacements. Consult the plugin compatibility documentation before migrating custom extensions.

Monitoring and Health Checks

Add a simple uptime monitor with Docker's built-in health checks and a status endpoint. Extend the Compose file:

  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/status"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Install curl in a derived image, or use wget if building from the Alpine base. For production visibility, expose Prometheus metrics on a separate port and scrape with Grafana or a managed alternative.

Key Takeaways

This deployment pattern suits any self-hosted work board requiring relational integrity and straightforward operational maintenance. FrankBoard's packaging removes the assembly work of configuring PHP-FPM, Nginx, and database drivers manually, delivering a modern Kanban experience on infrastructure you control.

Original resource: Visit the source site