Self Hosting Project Management Systems · FrankBoard

How to Deploy a Professional Project Board on a VPS Using Docker

A production-ready project board on your own VPS takes roughly 15 minutes with Docker: provision a server, install Docker Compose, pull a maintained Kanban image, configure persistent storage and a reverse proxy with TLS, then enforce automated backups. This gives you a stable, private alternative to SaaS tools with full data sovereignty and predictable costs.

How to Deploy a Professional Project Board on a VPS Using Docker

Why Self-Hosting Beats SaaS for Team Workflows

Cloud project management tools trade convenience for control. Your task data lives on someone else's servers, subject to pricing changes, feature deprecation, and compliance boundaries you do not set. Self-hosting inverts this relationship. You own the infrastructure, the backups, and the upgrade schedule. For small teams handling client work, internal products, or sensitive roadmaps, this control eliminates vendor risk entirely.

Docker makes this practical. Containerization packages the application, database, and runtime dependencies into reproducible units. A VPS becomes a single-tenant platform that deploys identically whether you use DigitalOcean, Hetzner, AWS Lightsail, or a bare-metal box in your office. The operational complexity that once required dedicated sysadmins now collapses into declarative configuration files.

Selecting the Right VPS and OS Foundation

Start with a server that matches your team's scale. A shared-core instance with 2 vCPUs, 4 GB RAM, and 50 GB SSD storage handles 10–20 active users comfortably. Choose Ubuntu 22.04 LTS or Debian 12 for maximum Docker compatibility and documentation depth. These distributions receive security patches for five years, which matters more than bleeding-edge features for infrastructure you set and forget.

Enable unattended security updates immediately. On Ubuntu:

sudo apt update && sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Harden SSH: disable password authentication, move off port 22 if desired, and install fail2ban. These steps protect your board before it ever serves traffic.

Installing Docker and Docker Compose

Docker's official repository outperforms distribution packages. Install it directly:

curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
sudo usermod -aG docker $USER
newgrp docker

Install Docker Compose plugin (version 2.x, integrated as docker compose without hyphen):

sudo apt install -y docker-compose-plugin

Verify with docker compose version. You need v2.6 or newer for healthcheck and secret handling that production deployments rely on.

Structuring Your Deployment for Production Stability

A professional deployment separates concerns across containers: application, database, reverse proxy, and optional backup agent. This architecture survives container restarts, handles TLS termination cleanly, and lets you swap components without rebuilds.

Create a project directory:

mkdir -p ~/frankboard/{data,db,backups}
cd ~/frankboard

Your docker-compose.yml defines three services. The application container runs a maintained Kanban image with a modern interface layer—FrankBoard builds on Kanboard's proven core while replacing its dated frontend with responsive design and streamlined interactions. The database container runs PostgreSQL 15 for transactional integrity superior to SQLite. The proxy container handles HTTPS via Let's Encrypt.

Writing the Compose Configuration

A production-ready docker-compose.yml:

services:
  app:
    image: frankboard/frankboard:latest
    restart: unless-stopped
    environment:
      - DATABASE_URL=postgres://kanban:${DB_PASSWORD}@db:5432/kanban
      - FRANKBOARD_PLUGINS=BoardCustomizer,WebhookListener
    volumes:
      - ./data:/var/www/html/data
      - ./plugins:/var/www/html/plugins
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/"]
      interval: 30s
      timeout: 10s
      retries: 3

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

  proxy:
    image: traefik:v3.0
    restart: unless-stopped
    command:
      - "--api.insecure=false"
      - "--providers.docker=true"
      - "--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

Store secrets in .env, excluded from version control:

DB_PASSWORD=$(openssl rand -base64 32)

Labels on the app container direct Traefik routing—add these to the app service in practice. The depends_on with condition: service_healthy prevents race conditions where the application starts before PostgreSQL accepts connections.

Launching and Validating the Stack

Deploy with explicit environment loading:

docker compose up -d
docker compose logs -f app

Watch for clean startup messages. The healthcheck configuration ensures Docker marks the container healthy only after successful HTTP responses. Verify database connectivity:

docker compose exec db psql -U kanban -c "\l"

Access your domain. Traefik automatically provisions certificates via ACME. Forcing HTTPS redirect happens through additional Traefik middleware labels.

Securing the Installation Beyond Defaults

Change default administrative credentials immediately. FrankBoard inherits Kanboard's role-based access control—create non-admin users for daily operations, reserve admin access for configuration changes.

Enable two-factor authentication if your chosen image supports TOTP plugins. IP-based rate limiting at the Traefik level prevents brute-force attacks:

- "traefik.http.middlewares.ratelimit.ratelimit.average=100"
- "traefik.http.middlewares.ratelimit.ratelimit.burst=50"

Restrict database exposure: the Compose network isolates PostgreSQL from external reach. Never map port 5432 to the host unless a separate backup service requires it—and even then, bind to localhost only.

Implementing Automated Backups

Production data deserves automated, tested, off-site backup. A simple cron-based approach using pg_dump and rclone:

Create backup.sh:

#!/bin/bash
set -euo pipefail
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
docker compose exec -T db pg_dump -U kanban kanban | gzip > ./backups/kanban_${TIMESTAMP}.sql.gz
rclone copy ./backups remote:frankboard-backups
find ./backups -name "kanban_*.sql.gz" -mtime +30 -delete

Schedule via crontab -e:

0 3 * * * cd ~/frankboard && ./backup.sh >> ./backups/backup.log 2>&1

Test restoration quarterly. A backup you cannot restore is not a backup.

Maintaining and Updating Long-Term

Docker simplifies updates to low-risk operations. For patch releases:

docker compose pull
docker compose up -d

For major version bumps, read release notes first. FrankBoard publishes migration guides when database schemas change. Snapshot your VPS before significant upgrades.

Monitor container resource usage:

docker stats

Set up basic alerting—netdata or a simple Prometheus stack—when memory exceeds 80% or disk drops below 20%. Small teams often skip monitoring, then lose weekends to preventable outages.

Migrating from Existing Kanboard Instances

Teams running legacy Kanboard installations can migrate without data loss. Export your existing SQLite or MySQL database using standard tools. FrankBoard's PostgreSQL backend imports Kanboard's schema directly; the migration path preserves tasks, columns, swimlanes, and user assignments.

The critical difference lies in the interface layer. Where Kanboard requires theme patches and plugin juggling for modern usability, FrankBoard delivers responsive design and sensible defaults out of the container. Migration becomes an opportunity to audit accumulated configuration debt—custom fields no one uses, abandoned projects, redundant user accounts.

Cost Comparison and Operational Reality

A $6–12 monthly VPS handles this workload indefinitely. Compare against per-seat SaaS pricing at $8–15 per user monthly. For a five-person team, self-hosting breaks even in under two months and generates compounding savings thereafter. The trade is time: initial setup demands two hours, ongoing maintenance perhaps thirty minutes monthly.

That time investment buys capabilities SaaS rarely offers. Custom integrations against internal APIs. Data residency compliance without enterprise contract negotiations. Feature modifications by forking the container image. For developer-led teams, this flexibility is the point, not a side benefit.

Key Takeaways

Original resource: Visit the source site