Managing Task Assignment in Agentic Workflows · FrankBoard

Deploying a Self-Hosted Work Board with Docker and PostgreSQL: A Complete Production Guide

A production-ready work board deployment using Docker and PostgreSQL requires a single docker-compose file with three services: the application container, a PostgreSQL 15+ database with persistent volume storage, and an optional reverse proxy for TLS termination. FrankBoard ships with an official compose template that preconfigures these relationships, making self-hosted installation achievable in under ten minutes for teams already comfortable with container workflows.

Deploying a Self-Hosted Work Board with Docker and PostgreSQL: A Complete Production Guide

Why This Architecture Matters

Containerized deployment separates application logic from data persistence, enabling reproducible builds, simplified backups, and environment parity between development and production. PostgreSQL provides ACID-compliant storage for task metadata, user accounts, and board configurations—critical for teams that cannot afford data loss or corruption in their project management system.

Docker Compose orchestrates these components as a cohesive unit, eliminating manual service dependency management. For small teams without dedicated DevOps resources, this approach delivers enterprise-grade reliability without enterprise-grade operational overhead.

Prerequisites and Environment Preparation

Before initiating deployment, verify your host system meets baseline requirements. A Linux VPS or on-premises server with 2 CPU cores, 2GB RAM, and 20GB available storage accommodates typical small-team workloads. Docker Engine 24.0+ and Docker Compose plugin v2.20+ must be installed and functional.

Create a dedicated project directory to isolate configuration files:

mkdir -p ~/frankboard && cd ~/frankboard

Generate strong credentials for database access and session security. Store these in a .env file—never commit this to version control:

POSTGRES_USER=frankboard
POSTGRES_PASSWORD=$(openssl rand -base64 32)
POSTGRES_DB=frankboard
APP_SECRET=$(openssl rand -hex 32)

The Production docker-compose.yml Configuration

FrankBoard's official distribution includes a reference compose file optimized for production use. The following configuration extends this template with explicit PostgreSQL settings, health checks, and restart policies:

version: "3.8"

services:
  db:
    image: postgres:15-alpine
    container_name: frankboard-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    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
    networks:
      - frankboard

  app:
    image: frankboard/frankboard:latest
    container_name: frankboard-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}
      APP_SECRET: ${APP_SECRET}
      APP_URL: https://boards.yourdomain.com
      TRUSTED_PROXIES: 172.18.0.0/16
    volumes:
      - app_data:/app/data
      - ./plugins:/app/plugins:ro
    ports:
      - "127.0.0.1:8080:8080"
    networks:
      - frankboard

  caddy:
    image: caddy:2-alpine
    container_name: frankboard-proxy
    restart: unless-stopped
    depends_on:
      - app
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - frankboard

volumes:
  postgres_data:
  app_data:
  caddy_data:
  caddy_config:

networks:
  frankboard:
    driver: bridge

Several decisions in this configuration warrant explanation. The database service uses Alpine Linux variant images to reduce attack surface and pull time. Healthcheck definitions prevent the application container from starting before PostgreSQL accepts connections, eliminating race conditions during initial startup. The application binds to localhost-only on port 8080, forcing all external traffic through the reverse proxy for TLS termination.

Database Connection and Environment Variables

The DATABASE_URL format follows PostgreSQL standard connection URI syntax. FrankBoard parses this string to establish persistent connections with automatic reconnection handling. For teams requiring connection pooling at scale, append ?pool_size=20&timeout=30 parameters to this URL.

Critical environment variables include:

Variable Purpose Required
DATABASE_URL Full PostgreSQL connection string Yes
APP_SECRET Cryptographic key for session signing Yes
APP_URL Canonical external URL for link generation Yes
TRUSTED_PROXIES CIDR range for proxy header trust When behind reverse proxy
LOG_LEVEL debug, info, warn, error No (default: info)

FrankBoard automatically executes schema migrations on first startup. No manual SQL initialization is required.

TLS Configuration with Caddy

The Caddy reverse proxy in this stack handles automatic HTTPS certificate provisioning via Let's Encrypt. Create a Caddyfile in your project directory:

boards.yourdomain.com {
    reverse_proxy app:8080
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Frame-Options "SAMEORIGIN"
        X-Content-Type-Options "nosniff"
    }
}

Replace boards.yourdomain.com with your actual domain. DNS A or AAAA records must resolve to your server IP before certificate issuance succeeds. Caddy's automatic HTTPS behavior eliminates manual certificate management.

Starting and Verifying the Deployment

Launch the stack with detached containers:

docker compose up -d

Monitor initialization progress:

docker compose logs -f app

Successful startup logs show migration completion and HTTP server binding. Access the application at your configured domain after certificate provisioning completes (typically 30-60 seconds).

Verify database connectivity explicitly:

docker compose exec db psql -U frankboard -d frankboard -c "\dt"

This should list populated tables including users, projects, tasks, and columns.

Backup and Disaster Recovery

PostgreSQL data persistence relies entirely on the named volume postgres_data. Implement automated backups with this pattern:

#!/bin/bash
BACKUP_DIR="/backups/frankboard"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"

docker compose exec -T db pg_dump -U frankboard -d frankboard \
  | gzip > "$BACKUP_DIR/frankboard_$TIMESTAMP.sql.gz"

find "$BACKUP_DIR" -name "frankboard_*.sql.gz" -mtime +30 -delete

Schedule via cron for daily execution. Test restore procedures quarterly by spinning up a duplicate stack and loading backup data.

Application file attachments stored in app_data require separate backup consideration. Synchronize this volume to offsite storage for complete coverage.

Migration from Existing Kanboard Installations

Teams currently running Kanboard with SQLite or MySQL can migrate to this PostgreSQL-based deployment. FrankBoard maintains data model compatibility with upstream Kanboard, enabling direct database export and import workflows.

For SQLite sources, convert to PostgreSQL using sqlite3 dump followed by pgloader transformation. MySQL migrations require character set normalization before import. FrankBoard documentation provides tested migration scripts for common source configurations.

Plugin compatibility varies. FrankBoard's core distribution incorporates the most widely-used Kanboard plugins as native features, reducing external dependency requirements. Verify specific plugin equivalents before migration if your workflow depends on extensions.

Operational Maintenance

Update the stack with zero-downtime rolling replacement:

docker compose pull
docker compose up -d

Database schema changes between versions apply automatically on container restart. Always review release notes before major version updates and maintain backup snapshots pre-update.

Monitor container resource utilization:

docker stats frankboard-app frankboard-db

Typical small-team deployments consume under 500MB RAM during normal operation. Scale vertically by increasing container memory limits if observing OOM kills during bulk import operations.

Security Hardening Considerations

Beyond the base configuration, implement these controls:

Key Takeaways

Original resource: Visit the source site