Vikunja Todo Setup: Manage Tasks with Kanban and Gantt Charts in Docker
Organize your workflow. Deploy Vikunja, a powerful open-source to-do and project management tool, using Docker Compose on a VPS.
Vikunja Todo Setup: Manage Tasks with Kanban and Gantt Charts in Docker
Self-hosting a collaborative project management tool requires balancing application performance, feature completeness, and architectural simplicity. While options like Nextcloud offer task tracking, they are heavy PHP monoliths that struggle with real-time UI responsiveness. Vikunja offers a modern, high-performance alternative. Written in Go for the API and Vue 3 for the frontend SPA, it features fast page loads, real-time sync, and native support for complex views like Kanban boards, Gantt charts, table views, and CalDAV syncing.
This technical guide provides a step-by-step walkthrough for deploying Vikunja on a Virtual Private Server (VPS) using Docker Compose, configuring PostgreSQL for data persistence, Redis for caching/websockets, and Caddy as an automatic TLS reverse proxy.
1. Vikunja's Architecture: Decoupled vs. Unified
Understanding Vikunja's system design is key to configuring it correctly. Vikunja consists of two core layers: 1. The Backend API: A stateless Go daemon (vikunja-api) that exposes a REST API, communicates with the database and Redis cache, handles CalDAV sync requests, issues JWTs, sends mail alerts, and dispatches webhooks. 2. The Frontend SPA: A client-side Single Page Application (vikunja-frontend) built with Vue 3 and TypeScript. The frontend executes entirely in the client's browser and interacts with the API exclusively via HTTP requests.
graph TD
User([Browser Client]) -->|HTTPS / WSS| ReverseProxy[Caddy / Nginx Reverse Proxy]
ReverseProxy -->|Serve Static Assets| Frontend[Vue 3 SPA Assets]
ReverseProxy -->|API Requests: 3456/api/v1| API[Go API Daemon]
API -->|TCP 5432| DB[(PostgreSQL)]
API -->|TCP 6379| Cache[(Redis Broker)]
Historically, self-hosting required deploying separate containers for vikunja-api and vikunja-frontend, and configuring a web server to serve the frontend assets while routing /api/v1 traffic to the backend API.
Modern deployments of Vikunja utilize the unified image (vikunja/vikunja). This image packages both the Go backend and the compiled Vue 3 static assets into a single container. Internally, the Go binary serves the frontend assets directly for root web requests and handles API requests on /api/v1 and CalDAV requests on /dav. This simplifies the Docker Compose configuration by removing the need for internal routing rules between frontend and backend containers.
2. Database Design & Task Modeling
Before writing configuration files, it is useful to look at how Vikunja models project structures in PostgreSQL:
- Projects (
projects): The root containers for tasks. Every project belongs to an owner or group, and can be nested hierarchically. - Tasks (
tasks): The primary units of work, storing attributes such astitle,description,due_date,start_date, andpriority. - Buckets (
buckets): Represent the vertical columns on a Kanban board. - Task Buckets (
task_buckets): A join table mappingtaskstobuckets. It contains apositionfloat field. Vikunja uses a double-precision float index to order tasks within a column. When dragging and dropping a task, the frontend calculates a position value halfway between the task above it and the task below it (e.g., placing a task between position1.0and2.0sets it to1.5). This allows instant updates without re-indexing the entire column in the database. - Task Relations (
task_relations): Vikunja supports task dependencies (e.g.,blocking,blocked-by,subtask,parent-task,predecessor,successor). These relationships are verified at the API database layer to prevent circular dependencies, and they populate the connections rendered in the Gantt chart view.
3. Production docker-compose.yml Configuration
Create a dedicated directory on your VPS, such as /opt/vikunja, and structure your deployment files inside it:
mkdir -p /opt/vikunja/data
cd /opt/vikunja
touch docker-compose.yml .env
Here is a production-grade docker-compose.yml file. It configures the unified Vikunja app, a PostgreSQL database, a Redis cache, and Caddy to handle automatic Let's Encrypt SSL/TLS certificates.
version: '3.8'
services:
vikunja:
image: vikunja/vikunja:latest
container_name: vikunja_app
restart: unless-stopped
ports:
- "3456:3456"
environment:
# Database connection parameters
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: vikunja_db
VIKUNJA_DATABASE_PORT: 5432
VIKUNJA_DATABASE_USER: ${POSTGRES_USER}
VIKUNJA_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
VIKUNJA_DATABASE_DATABASE: ${POSTGRES_DB}
# Core application settings
VIKUNJA_SERVICE_JWTSECRET: ${JWT_SECRET}
VIKUNJA_SERVICE_PUBLICURL: https://${VIKUNJA_DOMAIN}/
VIKUNJA_SERVICE_ENABLEREGISTRATION: "false" # Set to true to allow user sign-ups
# Redis integration for websockets and performance caching
VIKUNJA_REDIS_ENABLED: "true"
VIKUNJA_REDIS_HOST: vikunja_redis:6379
# Email notifications (SMTP Configuration)
VIKUNJA_MAIL_ENABLED: "true"
VIKUNJA_MAIL_HOST: ${SMTP_HOST}
VIKUNJA_MAIL_PORT: ${SMTP_PORT}
VIKUNJA_MAIL_USERNAME: ${SMTP_USER}
VIKUNJA_MAIL_PASSWORD: ${SMTP_PASSWORD}
VIKUNJA_MAIL_FROMADDRESS: ${SMTP_FROM_EMAIL}
VIKUNJA_MAIL_SKIPTLSVERIFY: "false"
# Files and attachments limits
VIKUNJA_FILES_MAXSIZE: "50MB"
VIKUNJA_FILES_PATH: /app/vikunja/files
volumes:
- ./data/files:/app/vikunja/files
depends_on:
vikunja_db:
condition: service_healthy
vikunja_redis:
condition: service_started
networks:
- vikunja_net
vikunja_db:
image: postgres:15-alpine
container_name: vikunja_db
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- vikunja_net
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
vikunja_redis:
image: redis:7-alpine
container_name: vikunja_redis
restart: unless-stopped
command: redis-server --appendonly yes
volumes:
- ./data/redis:/data
networks:
- vikunja_net
caddy:
image: caddy:2-alpine
container_name: vikunja_proxy
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./data/caddy_data:/data
- ./data/caddy_config:/config
depends_on:
- vikunja
networks:
- vikunja_net
networks:
vikunja_net:
driver: bridge
4. Configuring Environment Variables & Caddy
The Environment File (.env)
Generate a cryptographically secure random string for your JWT_SECRET key to secure API authentication tokens:
openssl rand -hex 32
Use the output to populate your .env file, substituting the placeholder values below with your specific setup:
# Domain configuration
VIKUNJA_DOMAIN=tasks.example.com
# PostgreSQL Credentials
POSTGRES_USER=vikunjapg
POSTGRES_PASSWORD=Use_A_Secure_Password_Here
POSTGRES_DB=vikunja
# JWT Encryption Secret
JWT_SECRET=Insert_Output_From_OpenSSL_Here
# SMTP Mail Server Configuration
SMTP_HOST=smtp.mailgun.org
SMTP_PORT=587
SMTP_USER=postmaster@example.com
SMTP_PASSWORD=YourSmtpPassword
SMTP_FROM_EMAIL=vikunja@example.com
The Caddyfile
Caddy acts as the front-facing web server, handling SSL generation via Let's Encrypt and proxying client requests to the unified Vikunja application container.
Create a file named Caddyfile in the /opt/vikunja directory:
tasks.example.com {
# Reverse proxy all requests to the Vikunja container
reverse_proxy vikunja:3456
# Optional headers to enhance security
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-XSS-Protection "1; mode=block"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
Referrer-Policy "no-referrer-when-downgrade"
}
# Enable Gzip compression to optimize asset loading speeds
encode gzip
}
Replace tasks.example.com with the fully qualified domain name (FQDN) pointed at your VPS public IP address.
5. Deployment Step-by-Step Walkthrough
Follow these commands to deploy the stack:
Step 1: Set Volume Directory Ownership
Before launching, ensure the local mount directories exist and have permissions that match Docker container users. PostgreSQL and Redis handle their own directory permissions, but Vikunja needs read/write access to its local file uploads folder:
mkdir -p data/files data/postgres data/redis data/caddy_data data/caddy_config
chown -R 1000:1000 data/files
Step 2: Spin Up the Containers
Run the Docker Compose command to build and launch the services in the background:
docker compose up -d
Step 3: Inspect Logs and Migration Process
Vikunja features an automatic database migration system that runs on startup. Check the initialization logs to verify that the table structures were generated without error:
docker compose logs -f vikunja
Look for lines indicating database migrations were executed:
2026-05-30T08:50:00Z: INFO ▶ [Database] Migration 20230305141322 succeeded
2026-05-30T08:50:01Z: INFO ▶ [Database] All migrations run successfully.
2026-05-30T08:50:01Z: INFO ▶ [Server] Web server started on port :3456
Step 4: Admin Account Setup
Because we set VIKUNJA_SERVICE_ENABLEREGISTRATION: "false" in our compose file for security, you can register the initial admin account via the Vikunja Command Line Interface (CLI) inside the running application container:
docker compose exec vikunja /app/vikunja/vikunja user create --username admin --email admin@example.com --password "SecureAdminPassword"
Once completed, navigate to https://tasks.example.com in your browser. Log in with the credentials generated above.
6. Verification: Testing Kanban, Gantt, and CalDAV
Kanban Board View
To verify drag-and-drop mechanics: 1. Create a new Project. 2. In the project's view options, select Kanban. 3. Create columns (e.g., "To Do", "In Progress", "Done"). 4. Add a task and drag it across columns. 5. Open your browser's developer console (F12) to inspect network payloads. You will observe POST and PUT payloads targeting /api/v1/projects/:id/views/kanban detailing the position changes.
Gantt Chart View
To verify relationship mapping: 1. Select the Gantt view option. 2. Create two tasks: "Setup Server" and "Install App". 3. Edit the details of "Install App", select Relations, and set it as blocked by "Setup Server". 4. Set a start and end date for both tasks. 5. You will see a visual relation line connecting the tasks in the Gantt grid, reading from the task_relations table.
CalDAV Integration
To sync Vikunja tasks with desktop or mobile calendar clients (e.g., Thunderbird, Apple Reminders): 1. Navigate to user settings, click CalDAV, and copy the principal URL. 2. In Thunderbird (with the Lightning extension) or Apple Calendar, create a new network calendar. 3. Choose the CalDAV protocol, input the principal URL, and authenticate using your Vikunja username and password (or generated API token). 4. Tasks added in Vikunja will now populate your external calendar applications in real time.
7. Operational Maintenance & Backups
To secure production data, schedule nightly database dumps and file volume backups. Below is an automated backup script /opt/vikunja/backup.sh:
#!/bin/bash
BACKUP_DIR="/opt/vikunja/backups"
TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
mkdir -p "$BACKUP_DIR"
# Dump PostgreSQL database
docker compose exec -t vikunja_db pg_dump -U vikunjapg -d vikunja > "$BACKUP_DIR/db_backup_$TIMESTAMP.sql"
# Archive user uploaded files
tar -czf "$BACKUP_DIR/files_backup_$TIMESTAMP.tar.gz" -C /opt/vikunja/data files
# Keep only the last 7 days of backups
find "$BACKUP_DIR" -type f -mtime +7 -delete
Make it executable and register a cron job to execute it nightly:
chmod +x /opt/vikunja/backup.sh
(crontab -l 2>/dev/null; echo "0 2 * * * /opt/vikunja/backup.sh") | crontab -
This ensures database snapshots and file attachments are stored safely, minimizing data-loss risks in production setups.