Managing Task Assignment in Agentic Workflows · FrankBoard

How to Deploy FrankBoard Using Docker and PostgreSQL: A Step-by-Step Guide

Deploying FrankBoard with Docker and PostgreSQL gives you a production-ready, self-hosted Kanban board in under 15 minutes. A properly configured docker-compose.yml with named volumes and health checks ensures your data survives container restarts and your database connection remains stable. This guide covers the complete setup from initial configuration to first login.

How to Deploy FrankBoard Using Docker and PostgreSQL: A Step-by-Step Guide

Prerequisites and System Requirements

Before starting, you need a server or local machine running Docker Engine 20.10+ and Docker Compose v2. FrankBoard distributes as a multi-architecture container image supporting AMD64 and ARM64, so it runs equally well on x86 VPS instances, Raspberry Pi clusters, or Apple Silicon development machines.

Your target environment needs at least 512MB RAM for the application container plus 256MB dedicated to PostgreSQL. For teams exceeding 10 active users, allocate 1GB RAM total and enable swap as a buffer. Storage requirements remain minimal—expect 2-5GB for the application, database, and logs combined during normal operation.

You'll also need a domain or subdomain pointed at your server if you plan to serve FrankBoard behind a reverse proxy with TLS. Local deployments for testing can skip DNS configuration.

Why PostgreSQL Over SQLite for Production Deployments

FrankBoard's Docker image ships with SQLite support enabled by default for zero-configuration testing. For any deployment handling multiple concurrent users or expected to persist data long-term, PostgreSQL eliminates the locking contention and corruption risks inherent to file-based databases under containerized workloads.

PostgreSQL handles concurrent board updates, real-time filtering, and plugin operations without the "database is locked" errors common in SQLite under Docker's layered filesystem. The performance difference becomes noticeable above three simultaneous users or when running automated integrations that hit the API repeatedly.

This guide configures PostgreSQL 15 or newer with persistent storage through Docker named volumes, ensuring your task data, user accounts, and board configurations survive container recreation during updates.

Step 1: Create the Docker Compose Configuration

Create a dedicated directory for your deployment and add a docker-compose.yml file with this production-optimized structure:

version: "3.8"

services:
  db:
    image: postgres:15-alpine
    container_name: frankboard-db
    restart: unless-stopped
    environment:
      POSTGRES_USER: frankboard
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: frankboard
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U frankboard -d frankboard"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - frankboard-net

  app:
    image: frankboard/frankboard:latest
    container_name: frankboard-app
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://frankboard:${DB_PASSWORD}@db:5432/frankboard
      FRANKBOARD_URL: ${FRANKBOARD_URL}
      FRANKBOARD_SECRET: ${FRANKBOARD_SECRET}
    volumes:
      - frankboard_data:/var/www/app/data
      - frankboard_plugins:/var/www/app/plugins
    ports:
      - "8080:80"
    networks:
      - frankboard-net

volumes:
  postgres_data:
  frankboard_data:
  frankboard_plugins:

networks:
  frankboard-net:
    driver: bridge

The depends_on condition with service_healthy prevents the application from starting before PostgreSQL accepts connections—a common source of failed first deployments. Named volumes isolate your data from the container lifecycle, making updates and migrations straightforward.

Step 2: Configure Environment Secrets

Create a .env file in the same directory. Never commit this file to version control:

DB_PASSWORD=your-secure-random-password-here-min-32-chars
FRANKBOARD_URL=https://board.yourdomain.com
FRANKBOARD_SECRET=another-long-random-string-for-session-signing

Generate cryptographically secure values with openssl rand -base64 48. The FRANKBOARD_SECRET protects session cookies and must remain stable across container restarts or users will be logged out. Rotate it only during planned maintenance windows.

For local testing, use FRANKBOARD_URL=http://localhost:8080 and skip reverse proxy configuration until ready for production exposure.

Step 3: Launch and Verify the Stack

From your deployment directory, run:

docker compose up -d
docker compose logs -f app

The first pull downloads the FrankBoard application image and PostgreSQL base layer. Initial database migrations run automatically on first startup; expect 10-30 seconds before the application responds to HTTP requests.

Watch logs for the line indicating successful migration completion: "Database schema is up to date." If the app container restarts repeatedly, check that your .env values load correctly with docker compose config.

Verify database connectivity independently:

docker compose exec db pg_isready -U frankboard
# Should return: /var/run/postgresql:5432 - accepting connections

Step 4: Complete First-Time Setup

Navigate to http://your-server-ip:8080 (or your configured domain). The initial setup wizard creates the administrator account. This step requires the database to be fully initialized; if you see a connection error, wait 15 seconds and refresh.

After creating the admin user, immediately navigate to Settings → Application and confirm the database driver shows "Postgres" rather than "SQLite." This verification step catches misconfigurations before you invest time in board creation.

For teams migrating from an existing Kanboard instance, How to Migrate from Kanboard to a Modern UI Without Losing Data or Vendor Lock-in covers database export/import procedures that preserve task history, comments, and file attachments.

Step 5: Production Hardening

Reverse Proxy with Automatic TLS

Expose FrankBoard through nginx, Traefik, or Caddy rather than directly on port 8080. A minimal Caddy configuration:

board.yourdomain.com {
    reverse_proxy localhost:8080
}

Caddy automatically provisions and renews Let's Encrypt certificates. For Traefik users, add container labels instead of port mappings:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.frankboard.rule=Host(`board.yourdomain.com`)"
  - "traefik.http.routers.frankboard.tls.certresolver=letsencrypt"

Remove the ports: section when using a reverse proxy network.

Database Backup Automation

Add a backup service to your compose file for point-in-time recovery:

  backup:
    image: postgres:15-alpine
    container_name: frankboard-backup
    restart: unless-stopped
    environment:
      PGPASSWORD: ${DB_PASSWORD}
    volumes:
      - /opt/backups/frankboard:/backups
    command: >
      sh -c "while true; do
        pg_dump -h db -U frankboard frankboard > /backups/frankboard_$(date +%Y%m%d_%H%M%S).sql;
        find /backups -name '*.sql' -mtime +7 -delete;
        sleep 86400;
      done"
    networks:
      - frankboard-net

This creates daily dumps retaining seven days of history. Mount /opt/backups/frankboard to your host's backup destination or S3-syncing location.

Resource Constraints

Prevent runaway memory usage under unexpected load by adding limits to the app service:

    deploy:
      resources:
        limits:
          memory: 512M
        reservations:
          memory: 256M

Step 6: Update Without Downtime

FrankBoard releases follow semantic versioning. Update with zero data loss:

docker compose pull
docker compose up -d

The pull fetches the latest compatible image; the up command recreates containers while preserving named volumes. PostgreSQL data, uploaded files, and installed plugins remain intact.

For cautious updates, especially with custom plugins or API integrations, test on a staging clone first. FrankBoard and Kanboard Plugin Compatibility identifies which extensions maintain compatibility across releases.

Performance Optimization for Small VPS Instances

FrankBoard's architecture remains lightweight by design, but several Docker-specific adjustments improve responsiveness on resource-constrained hosts common in self-hosted deployments.

Enable PostgreSQL's shared_buffers to 25% of available RAM by extending the database service command:

    command: >
      postgres
        -c shared_buffers=128MB
        -c effective_cache_size=384MB
        -c maintenance_work_mem=64MB

For boards with hundreds of tasks across dozens of columns, mount the frankboard_data volume on an SSD rather than network-attached storage. Latency to the SQLite fallback cache (used for session storage even with PostgreSQL) directly impacts board loading times.

Troubleshooting Common Deployment Issues

Container fails to start with "connection refused" to database: The healthcheck interval may need extension on slower hosts. Add start_period: 30s to the database healthcheck configuration.

Persistent login failures after restore from backup: The FRANKBOARD_SECRET value changed. Restore the original secret from your backup .env or invalidate all sessions through database truncation.

Plugin uploads disappear after recreation: Verify the frankboard_plugins volume mounts to /var/www/app/plugins, not a host path that gets overwritten on image update.

High memory usage over time: FrankBoard's PHP-FPM worker pool defaults suit small teams. On 1GB instances, add PHP_MEMORY_LIMIT=256M to the app environment to prevent individual requests from exhausting available RAM.

Key Takeaways

For teams evaluating whether self-hosting aligns with their operational constraints, Self-Hosted vs. Cloud Kanban Boards: A Privacy-Focused Comparison examines the tradeoffs in depth. Those seeking the broader context of FrankBoard's positioning among alternatives will find The Best Self-Hosted Kanban Board for Small Teams: A Complete Guide directly relevant.

Original resource: Visit the source site