Self-Host Vikunja
What Is Vikunja?
Vikunja is an open-source task and project management platform written in Go with a Vue.js frontend. It covers the feature set you would expect from Todoist, Trello, or Microsoft To Do — task lists, Kanban boards, Gantt charts, CalDAV sync, reminders, labels, priorities, assignees, file attachments, and more — running entirely on your own hardware with no cloud dependency.
Since v0.22, Vikunja ships as a single Docker container (vikunja/vikunja) that bundles the API and the web frontend. It uses SQLite by default and optionally supports PostgreSQL or MySQL for larger deployments. For anyone looking for a self-hosted task manager that does not compromise on features, Vikunja is the strongest option available.
Prerequisites
- A Linux server (Ubuntu 22.04+ recommended)
- Docker Engine and Docker Compose v2 installed (guide)
- 512 MB RAM minimum (1 GB recommended with PostgreSQL)
- 1 GB free disk space for the application and database
- A domain name (optional, but required for CalDAV and mobile apps over the internet)
Docker Compose Configuration
This setup runs Vikunja with PostgreSQL for reliable, production-grade storage. Vikunja exposes its web UI and API on port 3456.
Create a project directory and prepare the files volume:
mkdir -p /opt/vikunja/files /opt/vikunja/db
chown 1000 /opt/vikunja/files
Vikunja runs as UID 1000 inside the container. The files directory must be writable by that user — this is where task attachments and project backgrounds are stored.
Create a .env file with your secrets:
# /opt/vikunja/.env
# Public URL where users will access Vikunja.
# MUST include the protocol and trailing slash.
# Used for CORS, email links, and CalDAV discovery.
VIKUNJA_PUBLIC_URL=https://tasks.example.com/
# JWT secret — used to sign authentication tokens.
# Generate one: openssl rand -base64 32
VIKUNJA_JWT_SECRET=CHANGE_ME_GENERATE_A_RANDOM_SECRET
# PostgreSQL credentials — must match between Vikunja and the database.
POSTGRES_USER=vikunja
POSTGRES_PASSWORD=CHANGE_ME_USE_A_STRONG_PASSWORD
POSTGRES_DB=vikunja
Create docker-compose.yml:
# /opt/vikunja/docker-compose.yml
services:
vikunja:
image: vikunja/vikunja:v1.1.0
container_name: vikunja
environment:
# Service configuration
VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL}
VIKUNJA_SERVICE_JWTSECRET: ${VIKUNJA_JWT_SECRET}
# Database configuration
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_HOST: vikunja-db
VIKUNJA_DATABASE_USER: ${POSTGRES_USER}
VIKUNJA_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
VIKUNJA_DATABASE_DATABASE: ${POSTGRES_DB}
# Timezone — affects reminder delivery and timestamps
TZ: Etc/UTC
ports:
- "3456:3456"
volumes:
# Task attachments, project backgrounds, and uploaded files
- ./files:/app/vikunja/files
depends_on:
vikunja-db:
condition: service_healthy
restart: unless-stopped
vikunja-db:
image: postgres:16-alpine
container_name: vikunja-db
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DB}
volumes:
# PostgreSQL data directory
- ./db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
interval: 5s
timeout: 5s
retries: 5
start_period: 30s
restart: unless-stopped
Start the stack:
cd /opt/vikunja
docker compose up -d
Vikunja will be available at http://your-server-ip:3456 once both containers are healthy. First startup takes 10-20 seconds while the database schema is created.
Initial Setup
Open http://your-server-ip:3456 in a browser. You will see the Vikunja login page.
- Create an account. Click “Register” and create your first user. The first registered user gets admin privileges automatically.
- Create a project. After login, click the plus icon to create your first project. Choose between a list view (default), Kanban board, or Gantt chart.
- Add tasks. Type a task title and press Enter. Vikunja supports natural language parsing — type “Buy groceries tomorrow at 5pm” and it will set the due date and time automatically.
- Disable open registration (recommended). Once all your users have accounts, go to Settings > General and disable public registration to prevent strangers from signing up.
Configuration
CalDAV Sync
Vikunja exposes a CalDAV endpoint at /dav for syncing tasks with calendar and task apps.
The CalDAV URL for your instance is:
https://tasks.example.com/dav/principals/YOUR_USERNAME/
To connect a CalDAV client:
- Use your Vikunja instance URL with the
/davpath as the server address - Authenticate with your Vikunja username and password
- Your projects appear as separate task lists
Compatible clients: DAVx5 + OpenTasks (Android), Evolution (Linux), KOrganizer (KDE). Note that Thunderbird and iOS native CalDAV have known compatibility issues — use a dedicated task app instead.
Email Notifications
Add these environment variables to the vikunja service to enable email reminders and notifications:
VIKUNJA_MAILER_ENABLED: "true"
VIKUNJA_MAILER_HOST: smtp.example.com
VIKUNJA_MAILER_PORT: "587"
VIKUNJA_MAILER_USERNAME: your-smtp-user
VIKUNJA_MAILER_PASSWORD: your-smtp-password
VIKUNJA_MAILER_FROMEMAIL: [email protected]
With email enabled, Vikunja sends task reminders, due date notifications, and team assignment alerts.
OpenID Connect / SSO
OIDC configuration requires a config file rather than environment variables. Mount a config file into the container:
- Create
/opt/vikunja/config.yml:
auth:
openid:
enabled: true
providers:
my-provider:
name: "Authentik"
authurl: https://auth.example.com/application/o/vikunja/
clientid: your-client-id
clientsecret: your-client-secret
scopes:
- openid
- profile
- email
- Add the volume mount to the
vikunjaservice indocker-compose.yml:
volumes:
- ./files:/app/vikunja/files
- ./config.yml:/app/vikunja/config.yml:ro
Vikunja supports any standard OIDC provider including Authentik, Keycloak, and Authelia. New users are created automatically on first OIDC login.
File Uploads
Vikunja stores uploaded files (task attachments, project backgrounds) in the /app/vikunja/files volume by default. You can configure S3-compatible storage instead:
VIKUNJA_FILES_TYPE: s3
VIKUNJA_FILES_S3_ENDPOINT: s3.amazonaws.com
VIKUNJA_FILES_S3_BUCKET: vikunja-files
VIKUNJA_FILES_S3_REGION: us-east-1
The maximum file upload size defaults to 20 MB. Change it with VIKUNJA_FILES_MAXSIZE (value in bytes).
Mobile Apps
Vikunja has native apps for both platforms:
- Android: Available on F-Droid and the Google Play Store. Search “Vikunja”.
- iOS: Available on the Apple App Store.
To connect the mobile app, enter your instance URL (e.g., https://tasks.example.com) and log in with your credentials. The app requires HTTPS with a valid certificate for remote connections — a reverse proxy with Let’s Encrypt handles this.
CalDAV sync through DAVx5 + OpenTasks on Android is the alternative if you prefer not to use the native app.
Reverse Proxy
Running Vikunja behind a reverse proxy gives you HTTPS, a clean domain name, and removes the port number from the URL. Make sure VIKUNJA_SERVICE_PUBLICURL in your .env matches the public domain.
Caddy (simplest option):
tasks.example.com {
reverse_proxy localhost:3456
}
Nginx:
server {
listen 80;
server_name tasks.example.com;
location / {
proxy_pass http://localhost:3456;
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;
client_max_body_size 20M;
}
}
If you increase the max upload size in Vikunja’s settings, you must also update client_max_body_size in Nginx to match.
Nginx Proxy Manager: Create a new Proxy Host pointing to vikunja on port 3456. Enable “Websockets Support”. Request an SSL certificate through the built-in Let’s Encrypt integration.
For the full reverse proxy setup including SSL, see Reverse Proxy Setup.
Backup
Vikunja stores data in two places that need backing up:
- PostgreSQL database — contains all projects, tasks, users, labels, and settings.
- Files directory — contains task attachments and project background images.
Database Backup
docker exec vikunja-db pg_dumpall -U vikunja > /opt/vikunja/backups/vikunja-db-$(date +%Y%m%d).sql
Files Backup
tar -czf /opt/vikunja/backups/vikunja-files-$(date +%Y%m%d).tar.gz -C /opt/vikunja files/
Restore
To restore the database:
docker exec -i vikunja-db psql -U vikunja < /opt/vikunja/backups/vikunja-db-YYYYMMDD.sql
To restore files, extract the tarball back to /opt/vikunja/files/ and ensure UID 1000 ownership with chown -R 1000 /opt/vikunja/files.
Schedule daily backups with cron and keep copies off-server. See Backup Strategy for the full 3-2-1 backup approach.
Troubleshooting
Permission Denied on Files Directory
Symptom: Vikunja logs show permission errors when uploading attachments. The UI shows a generic upload failure.
Fix: The container runs as UID 1000. Set ownership on the host:
chown -R 1000 /opt/vikunja/files
Vikunja uses a scratch-based image with no shell, so you cannot exec into the container. All permission fixes happen on the host.
CORS Errors in Browser Console
Symptom: The web UI loads but API requests fail with CORS errors. Tasks do not appear.
Fix: VIKUNJA_SERVICE_PUBLICURL must exactly match the URL you access Vikunja from, including protocol and trailing slash:
VIKUNJA_SERVICE_PUBLICURL: https://tasks.example.com/
If the URL is wrong or missing, Vikunja’s CORS headers will not match browser requests. Restart the container after changing this value.
Database Connection Refused on Startup
Symptom: Vikunja exits immediately with “connection refused” pointing to the database host.
Fix: Verify the depends_on condition is set to service_healthy. The healthcheck ensures PostgreSQL is ready before Vikunja tries to connect. If you removed the healthcheck, add it back — Vikunja does not retry database connections on its own.
Also check that VIKUNJA_DATABASE_HOST matches the database service name in your Compose file (e.g., vikunja-db).
CalDAV Not Discovering Projects
Symptom: CalDAV client connects but shows no task lists.
Fix: Use the full principal URL format: https://tasks.example.com/dav/principals/YOUR_USERNAME/. Some clients need the trailing slash. If your reverse proxy strips paths, make sure /dav is proxied correctly without rewriting the path.
Migration From SQLite to PostgreSQL
Symptom: You started with the default SQLite setup and want to move to PostgreSQL for better performance.
Fix: Vikunja does not include a built-in migration tool. Export your data using the Vikunja API (/api/v1/tasks and /api/v1/projects), set up a fresh PostgreSQL instance with the Docker Compose above, create your account, and re-import. For small instances, recreating the data manually may be faster.
Resource Requirements
- RAM: ~50 MB idle (Vikunja only), ~200 MB total with PostgreSQL under light use
- CPU: Very low. The Go backend is efficient — a single core handles hundreds of users
- Disk: ~100 MB for the application image, plus space for file attachments and the database
Vikunja is one of the lightest self-hosted project management tools available. It runs comfortably on a Raspberry Pi 4 or a $5/month VPS.
Verdict
Vikunja is the best self-hosted task management tool for people who want a single app that handles lists, Kanban boards, Gantt charts, and CalDAV sync. It replaced Todoist for me, and the single-container Docker deployment makes it trivial to run.
The native mobile apps are functional if basic — they get the job done for capturing tasks on the go. CalDAV sync through DAVx5 on Android is the more powerful option if you want tasks integrated into your existing calendar workflow.
Where Vikunja falls short: the CalDAV implementation is still maturing and has compatibility issues with some clients (notably iOS and Thunderbird). If CalDAV is your primary interface, test it with your specific client before committing. The web UI and mobile apps work without any issues.
For teams, Vikunja supports shared projects, assignees, and labels — enough for small teams. If you need full-blown project management with time tracking and complex workflows, look at Planka or Focalboard. For personal task management, Vikunja is the clear winner.
Related
Get self-hosting tips in your inbox
New guides, comparisons, and setup tutorials — delivered weekly. No spam.