Managing Task Assignment in Agentic Workflows · FrankBoard

Deploy a Work Board with Docker and PostgreSQL: A Complete Developer Guide

Running a self-hosted work board with Docker and PostgreSQL takes about ten minutes if you have a compose file that pins the right images and volumes. FrankBoard ships exactly that: a single docker-compose.yml that pulls its own container, wires it to an official PostgreSQL image, and persists data outside the container lifecycle so updates never risk your tasks.

Deploy a Work Board with Docker and PostgreSQL: A Complete Developer Guide

What You Need Before Starting

A server or local machine with Docker Engine 20.10+ and Docker Compose v2. Any Linux VPS with 1 vCPU, 1 GB RAM, and 10 GB storage handles a small team comfortably. You also need a domain or static IP if you want HTTPS, plus ports 80 and 443 open for Traefik or another reverse proxy.

FrankBoard distributes a ready-made Docker Compose configuration that eliminates manual database setup. You only edit environment variables and run docker compose up.

Why PostgreSQL Over SQLite for Production

SQLite works for solo testing but locks under concurrent writes. PostgreSQL handles multiple team members moving cards simultaneously without corruption. It also enables proper backups through pg_dump, point-in-time recovery, and replication if you scale later.

FrankBoard defaults to PostgreSQL in its production compose file because the Kanboard heritage stores every card movement, comment, and attachment reference in relational tables. Those tables grow. PostgreSQL's query planner keeps board loads fast even after thousands of tasks.

The Complete docker-compose.yml

Save this as docker-compose.yml in a dedicated directory:

version: "3.8"

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

  app:
    image: ghcr.io/frankboard/frankboard:latest
    container_name: frankboard-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://frankboard:${DB_PASSWORD}@db:5432/frankboard
      FRANKBOARD_URL: https://board.yourdomain.com
    volumes:
      - frankboard_data:/var/www/app/data
      - frankboard_plugins:/var/www/app/plugins
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.frankboard.rule=Host(`board.yourdomain.com`)"
      - "traefik.http.routers.frankboard.tls.certresolver=letsencrypt"

  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    command:
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.letsencrypt.acme.tlschallenge=true"
      - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com"
      - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - letsencrypt:/letsencrypt

volumes:
  postgres_data:
  frankboard_data:
  frankboard_plugins:
  letsencrypt:

Create a .env file in the same directory:

DB_PASSWORD=$(openssl rand -base64 32)

Then launch:

docker compose up -d

The depends_on with condition: service_healthy prevents FrankBoard from connecting before PostgreSQL accepts queries. This eliminates the race conditions that plague simpler compose setups.

Step-by-Step Deployment Walkthrough

1. Prepare Your Host

Update packages and install Docker:

sudo apt update && sudo apt upgrade -y
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker

2. Create the Project Directory

mkdir ~/frankboard && cd ~/frankboard

3. Configure Environment Variables

Generate a strong database password and store it:

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

4. Launch the Stack

docker compose up -d

Watch logs to confirm healthy startup:

docker compose logs -f app

You should see Connected to PostgreSQL followed by migration completion.

5. Create the First Admin Account

docker compose exec app ./cli user:create --username admin --role admin

The CLI prompts for a password. This avoids default credentials entirely.

6. Verify HTTPS

Browse to your domain. Traefik automatically provisions a Let's Encrypt certificate. If you prefer local testing first, remove the Traefik labels and expose port 80 directly:

    ports:
      - "8080:80"

Then access http://your-ip:8080.

Volume Strategy and Data Persistence

Three named volumes matter here. postgres_data holds the database files. frankboard_data stores uploaded attachments and local configuration. frankboard_plugins preserves any extensions you add.

Never mount these as bind mounts to host paths unless you understand POSIX permissions. Named volumes let Docker handle ownership, which prevents the common failure where the container user cannot write to ./data.

For backup purposes, copy the volume contents:

docker run --rm -v frankboard_postgres_data:/source -v ~/backup:/backup alpine tar czf /backup/postgres.tar.gz -C /source .

Or use PostgreSQL's native tools:

docker compose exec db pg_dump -U frankboard frankboard > backup.sql

Database Connection Troubleshooting

If FrankBoard exits with database errors, check three things. First, verify the DATABASE_URL format matches postgres://user:password@db:5432/dbname. Second, ensure the db service hostname resolves inside the app container:

docker compose exec app nslookup db

Third, inspect PostgreSQL logs for authentication failures:

docker compose logs db | grep -i "password\|auth"

The most common mistake is special characters in DB_PASSWORD that aren't URL-encoded. Avoid @, :, and / in passwords, or percent-encode them.

Updating Without Downtime

FrankBoard releases update the container image. Pull and restart:

docker compose pull
docker compose up -d

The unless-stopped restart policy and volume mounts mean your data survives the image swap. PostgreSQL stays running during the app container replacement, so active sessions barely hiccup.

For zero-downtime updates, run a second compose stack on a different port, migrate traffic with your reverse proxy, then retire the old containers. Most small teams do not need this complexity.

Security Hardening

Beyond the compose file, implement these practices:

FrankBoard's self-hosted architecture keeps all data under your control, which satisfies compliance requirements that cloud SaaS cannot.

Scaling Considerations

The compose above serves ten users easily. For fifty-plus, add:

FrankBoard remains stateless except for its volumes, so horizontal scaling only requires shared storage for attachments and a consistent FRANKBOARD_URL environment variable.

Key Takeaways

Original resource: Visit the source site