Vikunja is a self-hosted task manager with lists, kanban boards, gantt charts, priorities, labels, due dates, and a CalDAV endpoint. It has native apps for iOS and Android and a clean API — which makes it a much better Trello replacement than anything else on the same tier.
What it is
Tasks with a proper hierarchy, so a project holds lists which hold tasks. Multiple views of the same data: list, gantt, table, kanban. Teams, labels, reminders via email, quick-add syntax, and an API good enough to script against.
Before you start
- Single-user installs can use SQLite and skip the database container entirely. Multi-user installs should use Postgres — concurrent writes on SQLite eventually produce lock errors.
- Public registration is on by default. Turn it off in the admin settings or you'll get signups from anyone who finds the URL.
- The mobile apps need the URL typed exactly as configured, including scheme. A trailing slash mismatch shows as an unhelpful network error.
1 — Create the directories
mkdir -p ~/services/vikunja/{data,pgdata}
cd ~/services/vikunja
openssl rand -hex 32
2 — Write the compose file
services:
db:
image: postgres:16-alpine
container_name: vikunja-db
restart: unless-stopped
environment:
POSTGRES_USER: vikunja
POSTGRES_PASSWORD: CHANGE_ME
POSTGRES_DB: vikunja
volumes:
- ./pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U vikunja"]
interval: 10s
timeout: 5s
retries: 5
api:
image: vikunja/vikunja:stable
container_name: vikunja
restart: unless-stopped
depends_on:
db:
condition: service_healthy
ports:
- "127.0.0.1:3456:3456"
environment:
VIKUNJA_SERVICE: api
VIKUNJA_DB_TYPE: postgres
VIKUNJA_DB_HOST: db
VIKUNJA_DB_PORT: 5432
VIKUNJA_DB_NAME: vikunja
VIKUNJA_DB_USER: vikunja
VIKUNJA_DB_PASSWORD: CHANGE_ME
VIKUNJA_URL: https://tasks.example.com/
VIKUNJA_PUBLICURL: https://tasks.example.com/
VIKUNJA_SERVICE_ENABLE_REGISTRATION: "true"
VIKUNJA_SERVICE_ENABLEPROXY: "true"
VIKUNJA_CALDAV_ENABLED: "true"
VIKUNJA_JWT_SECRET: CHANGE_ME
TZ: UTC
volumes:
- ./data:/vikunja/data
healthcheck:
test: ["CMD", "wget", "--spider", "-q", "http://localhost:3456/api/v1/info"]
interval: 30s
timeout: 5s
retries: 3
web:
image: vikunja/vikunja:stable
container_name: vikunja-web
restart: unless-stopped
depends_on:
- api
ports:
- "127.0.0.1:4173:80"
environment:
VIKUNJA_SERVICE: web
VIKUNJA_URL: https://tasks.example.com/
VIKUNJA_API_URL: http://api:3456/
api and web services run the same image with a different VIKUNJA_SERVICE. The API never serves HTML and the web container never touches the database — that separation is what lets you put the web frontend behind a cache or a different network later.3 — Start it
docker compose up -d
docker compose logs -f api | grep -i "listening\|migrat\|error"
4 — First-run setup
- Register the first user at
http://yourhost:4173— that account gets admin rights. - Go to Admin → Settings and switch off public registration. Then create a team and invite the people you actually want sharing tasks with.
- Enable CalDAV, then point your phone's calendar or a CalDAV client at
https://tasks.example.com/dav/with your Vikunja credentials. Due dates then sync both ways. - Install the mobile app and point it at the same URL. Test offline: add a task on the train, reconnect, confirm it syncs without a manual "Sync now".
5 — Back it up
docker exec -u postgres vikunja-db pg_dump -U vikunja vikunja | gzip > vikunja-$(date +%F).sql.gz
tar -czf vikunja-files.tgz ./data # attachments