Skip to Content

Production Guide: Deploy Planka with Docker Compose + Caddy + PostgreSQL on Ubuntu

Self-host a fast, open-source Trello-alternative kanban board with automatic TLS, PostgreSQL persistence, and production-ready secrets handling.

Anyone who has tried to manage a software project with scattered sticky notes, email threads, and shared spreadsheets knows how quickly clarity disappears. Planka is a fast, open-source Trello-alternative kanban board designed for self-hosters who need real-time collaborative project management without sending every card, comment, and attachment to a third-party SaaS. It supports unlimited boards, lists, cards, labels, due dates, file attachments, member assignments, and a clean REST API — all backed by a PostgreSQL database. This guide walks through a complete production deployment of Planka using Docker Compose, Caddy as the reverse proxy with automatic TLS, and PostgreSQL as the persistent store, all on Ubuntu 22.04 or 24.04.

Architecture and flow overview

The stack consists of three services managed by Docker Compose:

  • Planka (app container) — the Node.js application server exposing the kanban UI and REST API on internal port 1337
  • PostgreSQL — the relational database storing all boards, cards, comments, attachments, and user records; required by Planka (no SQLite option)
  • Caddy — the reverse proxy handling inbound HTTPS, automatic certificate provisioning via ACME/Let's Encrypt, and forwarding traffic to the Planka container over the internal Docker network

Caddy listens on ports 80 and 443, terminates TLS, and forwards requests to planka:1337 over the internal bridge. PostgreSQL is never exposed externally. Docker volumes persist both the database data directory and Planka's uploaded file attachments across restarts and upgrades.

Prerequisites

  • Ubuntu 22.04 or 24.04 server with at least 1 vCPU and 1 GB RAM (2 GB recommended for active teams)
  • Docker Engine 24+ and Docker Compose v2 installed (docker compose version should return v2.x)
  • A domain name with an A record pointing to the server's public IP
  • Ports 80 and 443 open in the firewall (ufw allow 80 && ufw allow 443)
  • A valid email address for Let's Encrypt certificate notifications

Step-by-step deployment

1. Create the project directory

mkdir -p /opt/planka && cd /opt/planka

2. Generate a secure secret key

Planka requires a long random SECRET_KEY for session signing. Generate one with:

openssl rand -hex 64

Copy the output — you will paste it into the environment file in the next step.

3. Create the environment file

cat > /opt/planka/.env << 'EOF'
# Planka app
BASE_URL=https://planka.example.com
SECRET_KEY=PASTE_YOUR_64_BYTE_HEX_SECRET_HERE
[email protected]
DEFAULT_ADMIN_PASSWORD=ChangeThisPassword123!
DEFAULT_ADMIN_NAME=Admin
DEFAULT_ADMIN_USERNAME=admin

# PostgreSQL
POSTGRES_DB=planka
POSTGRES_USER=planka
POSTGRES_PASSWORD=ChangeThisDatabasePassword!
DATABASE_URL=postgresql://planka:ChangeThisDatabasePassword!@db/planka
EOF

4. Write the Docker Compose file

cat > /opt/planka/docker-compose.yml << 'EOF'
version: "3.8"

services:
  db:
    image: postgres:15-alpine
    restart: unless-stopped
    env_file: .env
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal

  planka:
    image: ghcr.io/plankanban/planka:latest
    restart: unless-stopped
    env_file: .env
    depends_on:
      - db
    volumes:
      - planka_uploads:/app/public/user-avatars
      - planka_attachments:/app/private/attachments
    networks:
      - internal
      - proxy

  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    networks:
      - proxy
    depends_on:
      - planka

volumes:
  postgres_data:
  planka_uploads:
  planka_attachments:
  caddy_data:
  caddy_config:

networks:
  internal:
  proxy:
EOF

5. Write the Caddyfile

cat > /opt/planka/Caddyfile << 'EOF'
planka.example.com {
    reverse_proxy planka:1337
}
EOF

Replace planka.example.com with your actual domain. Caddy automatically provisions and renews the TLS certificate.

6. Start the stack

cd /opt/planka
docker compose up -d
docker compose logs -f --tail=40

Wait until you see Server is running on port 1337 in the Planka logs before proceeding.

Configuration and secrets handling

All sensitive values live in /opt/planka/.env, which is never committed to version control. Follow these practices:

  • SECRET_KEY rotation — changing SECRET_KEY invalidates all active sessions. Rotate only during a planned maintenance window and inform users in advance.
  • Database password rotation — update POSTGRES_PASSWORD and DATABASE_URL in .env, then run docker compose exec db psql -U planka -c "ALTER USER planka PASSWORD 'new_password';" before restarting the stack.
  • File permissions — restrict access to the environment file: chmod 600 /opt/planka/.env.
  • SMTP for notifications — Planka supports email notifications. Add SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, SMTP_FROM, and SMTP_SECURE to .env. Without SMTP configured, password-reset emails will not be delivered.
  • Backup — schedule nightly dumps with docker compose exec db pg_dump -U planka planka > /backups/planka_$(date +%F).sql and sync to an offsite location. Also back up planka_attachments and planka_uploads Docker volumes.

Verification

After the stack is up, run these checks:

# Confirm all containers are running
docker compose ps

# Check Caddy got a certificate
docker compose logs caddy | grep -i "certificate\|tls\|obtained"

# Test HTTP-to-HTTPS redirect
curl -sI http://planka.example.com | head -5

# Test HTTPS response
curl -sI https://planka.example.com | head -5

You should see HTTP 301 from port 80 and HTTP 200 from port 443. Open https://planka.example.com in a browser and log in with the admin credentials you set in .env. Create a test board, add a card, and verify attachment uploads work.

Common issues and fixes

  • Planka container restarts immediately — the most common cause is a missing or invalid DATABASE_URL. Check docker compose logs planka for connection refused. Make sure the db service is healthy before Planka starts; add a healthcheck to the db service if needed.
  • Caddy fails to provision certificate — ensure ports 80 and 443 are open and the domain's A record has propagated. Run docker compose logs caddy to see the ACME error. Cloudflare proxy (orange cloud) must be paused during initial certificate issuance if using HTTP-01 challenge.
  • 502 Bad Gateway from Caddy — Planka may still be initialising or the proxy network is misconfigured. Confirm Caddy and Planka are on the same network: docker network inspect planka_proxy. The reverse_proxy directive must use the service name (planka:1337), not localhost.
  • File attachment uploads fail — check that the planka_attachments and planka_uploads volumes are mounted correctly and the container user has write permissions.
  • Forgot admin password — reset via the database: docker compose exec db psql -U planka -c "UPDATE users SET password_hash = crypt('NewPassword!', gen_salt('bf')) WHERE email = '[email protected]';" (requires the pgcrypto extension, which Planka's migrations install automatically).

FAQ

Can I import boards from Trello?

Yes. Planka supports importing Trello JSON exports directly. From the board picker screen, click Import from Trello, upload your exported .json file, and Planka reconstructs lists, cards, labels, due dates, and checklists. Attachments are not migrated because Trello exports contain only attachment URLs, not the binaries.

How many users and boards can Planka handle?

There is no hard limit enforced by Planka itself. Practical limits depend on the database host. A single PostgreSQL instance on a 2-core, 4 GB server comfortably supports 50–100 active users with hundreds of boards. For larger teams, move PostgreSQL to a dedicated managed instance (e.g., RDS or Cloud SQL) and scale the Planka container horizontally behind a load balancer.

Does Planka support SSO or LDAP?

Planka supports OpenID Connect (OIDC) single sign-on as of recent releases. Set OIDC_ISSUER, OIDC_CLIENT_ID, and OIDC_CLIENT_SECRET in your .env to enable it. LDAP is not natively supported, but pairing Planka with an OIDC provider like Authentik or Keycloak (both deployable on the same Ubuntu host) bridges LDAP directories into Planka via OIDC.

How do I upgrade Planka to a new version?

Planka uses rolling Docker image tags. Pull the latest image, recreate the container, and let migrations run automatically:

cd /opt/planka
docker compose pull
docker compose up -d --remove-orphans

Always back up the database before upgrading: docker compose exec db pg_dump -U planka planka > /backups/pre_upgrade_$(date +%F).sql.

Can I run Planka without exposing it to the internet?

Yes. Replace the Caddyfile domain block with an internal IP or hostname and skip ACME provisioning by using tls internal in the Caddyfile. For LAN-only deployments, you can also skip Caddy entirely and bind Planka's port directly: add ports: ["127.0.0.1:1337:1337"] to the planka service and access it over a VPN or SSH tunnel.

How do I back up and restore the Planka database?

Back up: docker compose exec db pg_dump -U planka planka | gzip > /backups/planka_$(date +%F).sql.gz

Restore: gunzip -c /backups/planka_2024-01-15.sql.gz | docker compose exec -T db psql -U planka planka

Also copy the planka_attachments volume directory (docker volume inspect planka_planka_attachments shows the path) to your backup destination.

What happens to data if I update the PostgreSQL image version?

Minor version bumps (e.g., 15.4 → 15.6) are safe and apply automatically. Major version upgrades (e.g., 15 → 16) require a pg_upgrade process. Pin a specific major version in docker-compose.yml (postgres:15-alpine) and upgrade deliberately by dumping, re-initialising, and restoring rather than relying on in-place migration.

Internal links

Talk to us

If you want this deployed with hardened access controls, monitoring standards, and production runbooks tailored to your environment, our team can help end-to-end.

Contact Us

Production Guide: Deploy Linkding with Docker Compose + Caddy + PostgreSQL on Ubuntu
Self-host a fast, private bookmark manager with automatic HTTPS and a production-grade PostgreSQL backend