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:
- Restrict SSH access to your VPS using key-based authentication only
- Enable unattended security updates on the host operating system
- Review FrankBoard's built-in authentication options; LDAP and OIDC integrations are available for teams with existing identity infrastructure
- Configure
fail2banagainst web server logs to mitigate brute-force attempts - Rotate
APP_SECRETannually by updating the environment variable and restarting containers
Key Takeaways
- Production Docker deployment requires three coordinated services: PostgreSQL database, application container, and TLS-terminating reverse proxy
- FrankBoard's official compose template provides health checks, restart policies, and volume persistence suitable for immediate production use
- PostgreSQL connection via
DATABASE_URLenables automatic schema migration without manual SQL execution - Caddy reverse proxy delivers automatic HTTPS with no certificate management burden
- Named volumes require explicit backup strategies; automate database dumps and file attachment synchronization
- Environment variable security demands
.envfile isolation and periodic secret rotation - Teams migrating from Kanboard benefit from data model compatibility, though plugin equivalents should be verified pre-migration