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 versionshould 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/planka2. Generate a secure secret key
Planka requires a long random SECRET_KEY for session signing. Generate one with:
openssl rand -hex 64Copy 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
EOF4. 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:
EOF5. Write the Caddyfile
cat > /opt/planka/Caddyfile << 'EOF'
planka.example.com {
reverse_proxy planka:1337
}
EOFReplace 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=40Wait 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_KEYinvalidates all active sessions. Rotate only during a planned maintenance window and inform users in advance. - Database password rotation — update
POSTGRES_PASSWORDandDATABASE_URLin.env, then rundocker 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, andSMTP_SECUREto.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).sqland sync to an offsite location. Also back upplanka_attachmentsandplanka_uploadsDocker 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 -5You 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. Checkdocker compose logs plankaforconnection refused. Make sure thedbservice is healthy before Planka starts; add ahealthcheckto thedbservice 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 caddyto 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
proxynetwork is misconfigured. Confirm Caddy and Planka are on the same network:docker network inspect planka_proxy. Thereverse_proxydirective must use the service name (planka:1337), notlocalhost. - File attachment uploads fail — check that the
planka_attachmentsandplanka_uploadsvolumes 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 thepgcryptoextension, 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-orphansAlways 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
- Production Guide: Deploy Vikunja with Docker Compose + Caddy + PostgreSQL on Ubuntu
- Production Guide: Deploy Outline Wiki with Docker Compose + Caddy + PostgreSQL on Ubuntu
- Production Guide: Deploy Authentik with Docker Compose + Caddy + PostgreSQL + Redis on Ubuntu
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.