Self Hosting Project Management Systems · FrankBoard

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

A production-ready project board on a VPS takes about ten minutes to deploy with Docker Compose, assuming a clean Ubuntu 22.04 LTS server with root access and a domain pointed at the instance. FrankBoard ships as a single-container image with no external dependencies beyond a reverse proxy, making it among the fastest self-hosted Kanban solutions to bring online securely.

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

What You'll Need Before Starting

Any VPS with 1 vCPU, 1 GB RAM, and 20 GB SSD storage handles a small team comfortably. FrankBoard's image builds on Alpine Linux and consumes roughly 80–120 MB of RAM at idle, leaving headroom for the OS and a web server. You'll need:

DigitalOcean, Hetzner, Linode, or any provider works identically. The instructions below target Ubuntu; Debian requires only package name adjustments.

Initial Server Hardening

Skip this and you'll regret it within weeks. Automated scanners probe fresh VPS instances within hours of creation.

First, update the system and install fail2ban:

sudo apt update && sudo apt upgrade -y
sudo apt install -y fail2ban ufw

Configure UFW to allow SSH, HTTP, and HTTPS, then enable it:

sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Install Docker through the official repository, not Ubuntu's outdated package:

sudo apt install -y ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo \
  "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Add your user to the docker group to avoid sudo for every command:

sudo usermod -aG docker $USER
newgrp docker

Choosing Your Database Backend

FrankBoard supports SQLite for single-node deployments and PostgreSQL for teams that anticipate concurrent users or want automated backups. SQLite requires zero configuration and persists data through a mounted volume. PostgreSQL adds roughly 200 MB of memory overhead but enables point-in-time recovery and external tooling compatibility.

For teams under five people, SQLite eliminates an entire failure domain. For larger deployments or those integrating with existing infrastructure, PostgreSQL is the prudent choice. The Docker Compose configurations below cover both paths.

Deploying with SQLite (Fastest Path)

Create a directory structure and pull the image:

mkdir -p ~/frankboard/{data,nginx}
cd ~/frankboard

Create docker-compose.yml:

version: "3.8"

services:
  frankboard:
    image: ghcr.io/frankboard/frankboard:latest
    container_name: frankboard
    restart: unless-stopped
    environment:
      - FRANKBOARD_URL=https://board.yourdomain.com
      - FRANKBOARD_DB_DRIVER=sqlite
      - FRANKBOARD_DB_PATH=/data/frankboard.db
    volumes:
      - ./data:/data
    networks:
      - frankboard-net

  nginx:
    image: nginx:alpine
    container_name: frankboard-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - frankboard
    networks:
      - frankboard-net

networks:
  frankboard-net:
    driver: bridge

Generate a basic Nginx configuration in nginx/nginx.conf:

events {
    worker_connections 1024;
}

http {
    upstream frankboard {
        server frankboard:8080;
    }

    server {
        listen 80;
        server_name board.yourdomain.com;
        return 301 https://$server_name$request_uri;
    }

    server {
        listen 443 ssl http2;
        server_name board.yourdomain.com;

        ssl_certificate /etc/nginx/ssl/cert.pem;
        ssl_certificate_key /etc/nginx/ssl/key.pem;
        ssl_protocols TLSv1.2 TLSv1.3;

        location / {
            proxy_pass http://frankboard;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

For TLS certificates, use Let's Encrypt via certbot on the host, then copy the fullchain and key into nginx/ssl/. Alternatively, deploy Traefik or Caddy as a companion container for automatic certificate management.

Launch the stack:

docker compose up -d

The application becomes available at your configured domain after the first request triggers database initialization.

Deploying with PostgreSQL (Production-Grade)

For teams needing durability guarantees, replace the SQLite service with a dedicated database container. This configuration matches what many teams running the best self-hosted Kanban board for small teams eventually migrate toward as their usage matures.

Update docker-compose.yml:

version: "3.8"

services:
  postgres:
    image: postgres:16-alpine
    container_name: frankboard-db
    restart: unless-stopped
    environment:
      - POSTGRES_USER=frankboard
      - POSTGRES_PASSWORD=REPLACE_WITH_STRONG_PASSWORD
      - POSTGRES_DB=frankboard
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - frankboard-net

  frankboard:
    image: ghcr.io/frankboard/frankboard:latest
    container_name: frankboard
    restart: unless-stopped
    environment:
      - FRANKBOARD_URL=https://board.yourdomain.com
      - FRANKBOARD_DB_DRIVER=postgres
      - FRANKBOARD_DB_HOST=postgres
      - FRANKBOARD_DB_PORT=5432
      - FRANKBOARD_DB_NAME=frankboard
      - FRANKBOARD_DB_USER=frankboard
      - FRANKBOARD_DB_PASSWORD=REPLACE_WITH_STRONG_PASSWORD
    depends_on:
      - postgres
    networks:
      - frankboard-net

  nginx:
    image: nginx:alpine
    container_name: frankboard-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
    depends_on:
      - frankboard
    networks:
      - frankboard-net

volumes:
  postgres_data:

networks:
  frankboard-net:
    driver: bridge

The FrankBoard container waits for PostgreSQL to accept connections before starting, eliminating race conditions during initial startup.

Securing the Deployment

TLS termination at the reverse proxy is non-negotiable. Beyond certificates, implement these hardening measures:

Fail2ban for SSH protection — already installed, now activate it:

sudo systemctl enable --now fail2ban

Docker daemon hardening — create /etc/docker/daemon.json:

{
  "live-restore": true,
  "no-new-privileges": true,
  "userland-proxy": false,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Restart Docker: sudo systemctl restart docker

Automated security updates:

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

Backup strategy — for SQLite, cp data/frankboard.db to an offsite location daily. For PostgreSQL, use pg_dump in a cron job:

0 3 * * * docker exec frankboard-db pg_dump -U frankboard frankboard | gzip > /backups/frankboard-$(date +\%Y\%m\%d).sql.gz

Teams evaluating how to avoid vendor lock-in for project management should note that these backups restore to any standard PostgreSQL instance, not just FrankBoard's container.

Reverse Proxy Alternatives

Nginx is battle-tested and universally documented. Two alternatives merit consideration:

Caddy handles HTTPS automatically with zero configuration. Replace the Nginx service with Caddy's official image and a two-line Caddyfile. This eliminates certificate management entirely.

Traefik excels in multi-service environments, discovering containers through Docker labels. Overkill for a single application, but valuable if FrankBoard shares the VPS with other tools.

Verifying and Troubleshooting

Check container health:

docker compose ps
docker compose logs -f frankboard

Common issues:

Access the initial setup at https://board.yourdomain.com/setup to create the administrative account. This endpoint self-destructs after first configuration.

Ongoing Maintenance

Update with zero downtime using Docker's rolling restart:

docker compose pull
docker compose up -d

The unless-stopped restart policy ensures containers resume after host reboots. Monitor disk usage; SQLite databases grow linearly with attachment volume, while PostgreSQL requires periodic VACUUM operations.

For teams comparing resource footprints, self-hosted Kanban benchmarks demonstrate that FrankBoard's memory usage stays flat under load, unlike PHP-based alternatives that spawn worker processes per request.

Migration from Existing Kanboard Instances

Teams currently running vanilla Kanboard can migrate projects, tasks, and users without manual CSV exports. FrankBoard maintains database schema compatibility with upstream Kanboard, enabling direct migration paths documented in how to migrate from Kanboard to a modern UI without data loss. The Docker deployment simplifies this further: mount the existing SQLite file as a read-only volume, run the migration utility inside a temporary container, then switch to the new volume.

Key Takeaways

Self-hosting project management eliminates subscription costs and data residency concerns, but only if the deployment remains maintainable. FrankBoard's Docker packaging treats operational simplicity as a first-class feature, not an afterthought.

Original resource: Visit the source site