How to Deploy a Work Board Using Docker and PostgreSQL
A production-ready work board on Docker and PostgreSQL requires a single docker-compose.yml file with three services: the application container, a PostgreSQL database, and a reverse proxy for TLS termination. FrankBoard ships with exactly this architecture, using official images and environment-driven configuration that keeps secrets out of your repository.
How to Deploy a Work Board Using Docker and PostgreSQL
What You'll Need Before Starting
Before running any containers, confirm your host meets the baseline requirements. You need a Linux server with Docker Engine 20.10 or newer and Docker Compose plugin installed, plus a domain name pointed at your server's public IP if you want HTTPS. FrankBoard consumes approximately 512 MB RAM at idle and scales comfortably on a 2 GB VPS for teams under fifty users. PostgreSQL 15 or 16 is recommended; the application handles migrations automatically on first boot.
Storage planning matters more than CPU for most Kanban workloads. Plan for at least 20 GB of persistent volume space, with PostgreSQL data and application file uploads stored on named volumes or bind mounts. Never store database files inside the container layer—this is the most common failure mode in self-hosted deployments.
The Complete Docker Compose Configuration
FrankBoard distributes a reference docker-compose.yml that requires no templating engines or external configuration files. The setup uses three services: the FrankBoard application, PostgreSQL, and Traefik as an automatic HTTPS reverse proxy.
Core Services Architecture
The application container exposes port 8080 internally and relies on environment variables for all runtime configuration. It connects to PostgreSQL via a dedicated Docker network, with no host port exposure required for the database. Traefik listens on ports 80 and 443, handles ACME certificate negotiation with Let's Encrypt, and routes traffic based on your domain label.
Here is the complete, production-ready configuration:
version: "3.8"
networks:
frankboard:
driver: bridge
volumes:
postgres_data:
frankboard_data:
traefik_certs:
services:
traefik:
image: traefik:v3.0
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
- traefik_certs:/letsencrypt
networks:
- frankboard
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_DB: frankboard
POSTGRES_USER: frankboard
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?POSTGRES_PASSWORD required}
volumes:
- postgres_data:/var/lib/postgresql/data
networks:
- frankboard
healthcheck:
test: ["CMD-SHELL", "pg_isready -U frankboard -d frankboard"]
interval: 5s
timeout: 5s
retries: 5
frankboard:
image: ghcr.io/frankboard/frankboard:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://frankboard:${POSTGRES_PASSWORD}@postgres:5432/frankboard
FRANKBOARD_URL: https://board.yourdomain.com
FRANKBOARD_SECRET_KEY: ${SECRET_KEY:?SECRET_KEY required}
FRANKBOARD_PLUGIN_DIR: /data/plugins
volumes:
- frankboard_data:/data
networks:
- frankboard
labels:
- "traefik.enable=true"
- "traefik.http.routers.frankboard.rule=Host(`board.yourdomain.com`)"
- "traefik.http.routers.frankboard.entrypoints=websecure"
- "traefik.http.routers.frankboard.tls.certresolver=letsencrypt"
- "traefik.http.services.frankboard.loadbalancer.server.port=8080"
Required Environment Variables
Create a .env file in the same directory as your compose file. Two values are mandatory and validated at startup:
POSTGRES_PASSWORD=your-very-strong-random-password-here
SECRET_KEY=$(openssl rand -hex 32)
The SECRET_KEY signs session cookies and must remain stable across container restarts. Rotating it invalidates all active sessions. Generate it once with the OpenSSL command above and back it up in your password manager.
Step-by-Step Deployment Process
1. Prepare Your Host
Update your system packages and install Docker if absent. Add your user to the docker group to avoid root privileges for compose commands. Create the deployment directory:
mkdir -p ~/frankboard && cd ~/frankboard
2. Write Configuration Files
Paste the compose contents above into docker-compose.yml. Create the .env file with your generated secrets. No additional configuration files, build steps, or database initialization scripts are required.
3. Launch the Stack
Run the standard Docker Compose sequence:
docker compose pull
docker compose up -d
The depends_on condition with healthcheck ensures FrankBoard waits for PostgreSQL to accept connections before starting. First boot performs automatic schema migration, which typically completes in under ten seconds for fresh databases.
4. Verify and Create Initial User
Check service health:
docker compose ps
docker compose logs -f frankboard
Once logs show Application startup complete, navigate to your configured domain. The first visitor receives an admin account creation prompt. Complete this immediately—there is no default password, and the prompt disappears once any account exists.
Database Connection and Performance Tuning
FrankBoard uses SQLAlchemy with psycopg2 for PostgreSQL connectivity. The DATABASE_URL format follows standard RFC 3986 URI syntax. For advanced deployments, append connection pool parameters:
postgresql://user:pass@host:5432/dbname?sslmode=require&pool_size=10&max_overflow=20
PostgreSQL tuning for small VPS instances (2-4 GB RAM) benefits from these postgresql.conf adjustments, applied via a custom config bind mount or by extending the image:
shared_buffers = 512MB
effective_cache_size = 1536MB
maintenance_work_mem = 128MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1
effective_io_concurrency = 200
work_mem = 2621kB
min_wal_size = 1GB
max_wal_size = 4GB
These values assume a dedicated database server. For shared hosts running multiple services, reduce shared_buffers proportionally.
Backup Strategy and Disaster Recovery
A production deployment requires automated, tested backups of both PostgreSQL data and application file uploads. FrankBoard stores uploaded attachments in /data/uploads inside the container, mapped to the frankboard_data volume.
Automated PostgreSQL Backups
Add a fourth service to your compose file for continuous archiving:
pgbackrest:
image: pgbackrest/pgbackrest:latest
restart: unless-stopped
volumes:
- postgres_data:/var/lib/postgresql/data:ro
- ./backups:/backups
environment:
PGBACKREST_STANZA: frankboard
PGBACKREST_REPO1_PATH: /backups
PGBACKREST_REPO1_RETENTION_FULL: 7
command: >
bash -c "
pgbackrest stanza-create --no-online || true &&
while true; do
pgbackrest backup --type=incr --no-resume;
pgbackrest expire;
sleep 86400;
done
"
networks:
- frankboard
For simpler setups without pgBackRest, a cron job on the host suffices:
0 3 * * * docker exec frankboard-postgres-1 pg_dump -U frankboard frankboard | gzip > /opt/backups/frankboard-$(date +\%Y\%m\%d).sql.gz
Application Data Backup
The frankboard_data volume contains plugins, uploaded files, and configuration exports. Back this up with:
docker run --rm -v frankboard_data:/data -v /opt/backups:/backups alpine tar czf /backups/frankboard-files-$(date +%Y%m%d).tar.gz -C /data .
Test restoration quarterly. Untested backups are unacknowledged risks.
Security Hardening Checkments
Beyond TLS encryption, several practices harden a self-hosted work board against common attack patterns.
Network Isolation
The compose configuration above uses an internal bridge network with no PostgreSQL port exposed to the host. Never map port 5432 to localhost unless you operate a separate backup host requiring direct access—and even then, restrict via firewall rules to specific source IPs.
Secret Management
The .env file contains credentials in plain text. For production environments, migrate to Docker secrets or an external vault:
secrets:
postgres_password:
file: ./secrets/postgres_password.txt
secret_key:
file: ./secrets/secret_key.txt
services:
frankboard:
secrets:
- secret_key
environment:
SECRET_KEY_FILE: /run/secrets/secret_key
FrankBoard reads *_FILE variants for all sensitive variables, following the Docker secrets convention.
Container Runtime Security
Run with read-only root filesystems where possible. FrankBoard supports this with a tmpfs mount for ephemeral files:
frankboard:
read_only: true
tmpfs:
- /tmp:noexec,nosuid,size=100m
Monitoring and Observability
Production deployments need visibility into application health. FrankBoard exposes a Prometheus metrics endpoint at /metrics when FRANKBOARD_METRICS_ENABLED=1 is set. Configure scraping in your monitoring stack, or add a simple health check service:
uptime-kuma:
image: louislam/uptime-kuma:latest
restart: unless-stopped
volumes:
- uptime_data:/app/data
networks:
- frankboard
labels:
- "traefik.enable=true"
- "traefik.http.routers.uptime.rule=Host(`status.yourdomain.com`)"
- "traefik.http.routers.uptime.entrypoints=websecure"
- "traefik.http.routers.uptime.tls.certresolver=letsencrypt"
Migration from Existing Kanboard Instances
Teams running vanilla Kanboard with SQLite or PostgreSQL can migrate to FrankBoard preserving all projects, tasks, and user relationships. The process requires exporting Kanboard's database and importing into the FrankBoard PostgreSQL instance, followed by a schema migration command. Kanboard Ecosystem Compatibility: Plugin and API Mapping covers the technical mapping between database schemas. For a complete walkthrough, see Deploy FrankBoard with Docker and PostgreSQL.
Key Takeaways
- FrankBoard deploys in minutes with a single
docker-compose.ymlrequiring only two environment variables:POSTGRES_PASSWORDandSECRET_KEY - PostgreSQL 16 with healthcheck-dependent startup eliminates race conditions and ensures reliable first-boot migrations
- Traefik handles automatic HTTPS via Let's Encrypt with zero manual certificate management
- Back up both the PostgreSQL database and the
frankboard_datavolume; neither alone captures complete system state - Security hardening options include Docker secrets, read-only containers, and internal bridge networking with no exposed database ports
- The architecture scales vertically on small VPS instances and requires no Kubernetes or orchestration complexity for teams under fifty users