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
- A 2 vCPU / 4 GB RAM VPS with Ubuntu 22.04 LTS provides sufficient foundation for teams up to 20 active users
- Docker Compose with healthchecks and persistent volumes eliminates the "it works on my machine" problem for production deployments
- PostgreSQL via container offers superior reliability over file-based SQLite for multi-user concurrent access
- Traefik automates TLS provisioning and renewal, removing certificate management from manual workflows
- FrankBoard packages Kanboard's mature backend with a modern interface, giving migrating teams familiar functionality without dated UX friction
- Automated
pg_dumpbackups with off-site replication and quarterly restoration testing protect against data loss scenarios - Self-hosting a professional project board costs under $150 annually in infrastructure versus $500+ for equivalent SaaS seats, with full data sovereignty as intrinsic benefit