Vikunja with Docker: self-hosted to-do and task manager with CalDAV (Linux, Windows, Mac)
Before you start
- 21 min read
Boxes marked Run in terminal are what you type; plain text explains why. Check what you need first

What you get in one minute#
Vikunja is a free, self-hosted to-do and task manager: lists, Kanban boards, Gantt charts and reminders in a web app that runs from one Docker container on your own server, and it can sync tasks to phone and calendar apps over CalDAV.
Logo: the official Vikunja project (github.com/go-vikunja/vikunja)
The project's tagline is "the task manager you actually own". It suits a household or small team that wants a Todoist or Trello style tool without a cloud account. The web app and the API ship in one container, and the default database is a single file.
| What | Value (official docs, read November 2026) |
|---|---|
| Current version | Vikunja 2.7.1, released 20 October 2026 (latest stable) |
| Image to pin | vikunja/vikunja:2.7.1 (tag checked on Docker Hub) |
| Time needed | About 20 minutes (our estimate, not measured) |
| Size and RAM | Docker Hub lists the 2.7.1 tag at about 55 MB. The docs publish no RAM or CPU figures |
| Port | TCP 3456 inside the container |
| Licence | AGPL-3.0-or-later (README and LICENSE at the release tag) |
| Official docs | vikunja.io/docs and the repository |
At a glance:
- What it is: one container with the web app, the API and a CalDAV endpoint. SQLite is built in; PostgreSQL and MariaDB are supported.
- Is it alive? Yes: not archived, not renamed. Version 1.0 came out in January 2026, 2.7.1 in October.
- Two settings matter most: the public URL and a fixed secret (Step 2).
- Close registration once your accounts exist (Step 4). CalDAV is on by default (Step 5).
- Update promptly: 2.7.1 alone carries 41 security fixes.
- Platforms: Docker on Linux, Windows or Mac. The desktop app exists for Linux and Windows only.
How it fits together#
Browsers and calendar apps reach the same address. An optional proxy adds HTTPS, and Vikunja writes to two folders on the host.
Drawn for this guide from the official docs. The proxy (dashed) is optional for a local test and recommended for anything else.
Before you start#
| System | What you need |
|---|---|
| Linux | Docker Engine, the Compose plugin, curl, openssl |
| Windows | Docker Desktop, PowerShell (curl.exe ships with Windows 10 and 11) |
| Mac | Docker Desktop, Terminal (curl and openssl are built in) |
The project publishes no minimum RAM or disk figures. Check Docker on any system:
docker --version docker compose version
Checklist:
- [ ] Docker and Docker Compose work (commands above)
- [ ] Port 3456 is free on the host (
ss -ltn | grep 3456on Linux) - [ ] You decided how you will open Vikunja:
http://localhost:3456/for a test, or a domain name for real use - [ ] For the HTTPS section only: a domain with a DNS record for the server, and ports 80 and 443 free
Tested by DIY Lab Hub: not yet. The commands come from the official docs and release files read on 5 November 2026. We did not run them.
Step 1: Create the folders#
Vikunja keeps tasks in a database file and uploads in a files folder. We use ~/docker_volumes/vikunja, as all DIY Lab Hub guides do. The container runs as user 1000 and cannot change folder ownership itself, so on Linux you set the owner before the first start.
Linux#
mkdir -p ~/docker_volumes/vikunja/{files,db,backups}
sudo chown 1000 ~/docker_volumes/vikunja/files ~/docker_volumes/vikunja/db
cd ~/docker_volumes/vikunja
Windows (PowerShell)#
The docs describe the owner step only for Linux and macOS, so there is no chown here. We did not test Windows bind mounts.
New-Item -ItemType Directory -Force "$HOME\docker_volumes\vikunja\files","$HOME\docker_volumes\vikunja\db","$HOME\docker_volumes\vikunja\backups" Set-Location "$HOME\docker_volumes\vikunja"
Mac (Terminal)#
The docs say Docker Desktop handles bind-mount permissions on macOS, and that chown 1000 fails there because the default Mac user is 501. Skip it.
mkdir -p ~/docker_volumes/vikunja/{files,db,backups}
cd ~/docker_volumes/vikunja
Check it worked. ls shows backups, db and files.
Tip: On Linux the number is the container's user ID, not yours.
PUIDandPGIDvariables from other images do nothing here; the docs say to use Docker'suser:option if you must change the user.
Step 2: Set the secret and the public URL#
Two values go into a .env file next to the Compose file. The secret signs login tokens. If you leave it empty, Vikunja makes a new random one at every start, and everybody is logged out after each restart. The public URL is the address you type in the browser; Vikunja uses it for CORS and for links in emails.
Linux#
cd ~/docker_volumes/vikunja printf 'VIKUNJA_SECRET=%s\n' "$(openssl rand -hex 32)" > .env printf 'VIKUNJA_PUBLIC_URL=http://localhost:3456/\n' >> .env printf 'VIKUNJA_REGISTRATION=true\n' >> .env chmod 600 .env
Windows (PowerShell)#
The openssl rand -hex 32 command comes from the Vikunja security page. This PowerShell version makes the same kind of value with .NET (our adaptation, not from the docs).
Set-Location "$HOME\docker_volumes\vikunja"
$b = New-Object byte[] 32
[Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($b)
$secret = ($b | ForEach-Object { $_.ToString('x2') }) -join ''
Set-Content -Path .env -Encoding ascii -Value "VIKUNJA_SECRET=$secret","VIKUNJA_PUBLIC_URL=http://localhost:3456/","VIKUNJA_REGISTRATION=true"
Mac (Terminal)#
Same commands as Linux:
cd ~/docker_volumes/vikunja printf 'VIKUNJA_SECRET=%s\n' "$(openssl rand -hex 32)" > .env printf 'VIKUNJA_PUBLIC_URL=http://localhost:3456/\n' >> .env printf 'VIKUNJA_REGISTRATION=true\n' >> .env chmod 600 .env
Check it worked. The file has three lines, and the first holds 64 characters after the equals sign. Never paste its content into a chat or an issue.
Tip: The public URL must include
http://orhttps://, the port if it is not 80 or 443, and the trailing slash. The docs warn that a missing port gives an "unauthorized" error when you create your account.
Step 3: Write the Compose file and start Vikunja#
This follows the official SQLite example, with the image pinned to 2.7.1, secrets read from .env, and the port published on 127.0.0.1 only. The official files use vikunja/vikunja with no tag, which means latest.
Linux#
cd ~/docker_volumes/vikunja
cat > docker-compose.yml << 'EOF'
services:
vikunja:
image: vikunja/vikunja:2.7.1
container_name: vikunja
restart: unless-stopped
environment:
VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL}
VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SECRET}
VIKUNJA_SERVICE_ENABLEREGISTRATION: ${VIKUNJA_REGISTRATION}
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_DATABASE_PATH: /db/vikunja.db
ports:
- "127.0.0.1:3456:3456"
volumes:
- ./files:/app/vikunja/files
- ./db:/db
EOF
docker compose up -d
docker compose ps
docker compose logs --tail=30 vikunja
Windows (PowerShell)#
Set-Location "$HOME\docker_volumes\vikunja"
@'
services:
vikunja:
image: vikunja/vikunja:2.7.1
container_name: vikunja
restart: unless-stopped
environment:
VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL}
VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SECRET}
VIKUNJA_SERVICE_ENABLEREGISTRATION: ${VIKUNJA_REGISTRATION}
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_DATABASE_PATH: /db/vikunja.db
ports:
- "127.0.0.1:3456:3456"
volumes:
- ./files:/app/vikunja/files
- ./db:/db
'@ | Set-Content -Path docker-compose.yml -Encoding ascii
docker compose up -d
docker compose ps
docker compose logs --tail=30 vikunja
Mac (Terminal)#
Same commands as Linux.
Check it worked. docker compose ps shows vikunja running. The docs show a log line like http server started on [::]:3456 once the setup finished (their example is from an older version). Then ask the server for its health and info:
curl -s http://127.0.0.1:3456/health curl -s http://127.0.0.1:3456/api/v1/info
On Windows use curl.exe. Expected, per the docs: /health answers OK, and /api/v1/info returns JSON that includes "version" and "registration_enabled": true.
Tip: The image is built from
scratch, so it has no shell.docker exec -it vikunja shfails. Read problems withdocker logs vikunjaand run commands by the full path, for exampledocker exec vikunja /app/vikunja/vikunja doctor.
Step 4: Create your account, then close registration#
Open http://localhost:3456/ in a browser. Register the first account. Everyone who needs one registers now, because the next step turns sign-up off.
Linux#
Edit ~/docker_volumes/vikunja/.env, change VIKUNJA_REGISTRATION=true to false, then recreate the container and check:
cd ~/docker_volumes/vikunja docker compose up -d curl -s http://127.0.0.1:3456/api/v1/info | grep -o '"registration_enabled":[a-z]*'
To add a person later without opening sign-up, use the CLI. It asks for the password:
docker exec -it vikunja /app/vikunja/vikunja user create -u <username> -e <email>
Windows (PowerShell)#
Edit $HOME\docker_volumes\vikunja\.env in a text editor and set VIKUNJA_REGISTRATION=false. Then:
Set-Location "$HOME\docker_volumes\vikunja" docker compose up -d curl.exe -s http://127.0.0.1:3456/api/v1/info | Select-String registration_enabled docker exec -it vikunja /app/vikunja/vikunja user create -u <username> -e <email>
The last line is only for adding a person later.
Mac (Terminal)#
Same as Linux. Edit .env in any editor (macOS sed -i differs from Linux, so we avoid it here).
Check it worked. The info output shows "registration_enabled":false, and the login page no longer offers sign-up.
Tip: Vikunja asks you to do this itself: the docs say "after registering all your users, you might also want to disable the user registration". On a server that anyone can reach, leave it open only while you register.
Step 5: Connect a CalDAV app#
CalDAV is on by default (service.enablecaldav). Vikunja serves tasks (VTODO) through it, and each project becomes a task list. In the web app open your name at the top right, then Settings, CalDAV. The tab shows your CalDAV URL and username.
For the password, your account password works unless two-factor authentication is on or you sign in through an external identity provider. A dedicated token is safer: click Create a CalDAV token and copy it, because it is shown once. An API token with the CalDAV permission group also works (2.3.0 and later).
Phones need an HTTPS address, so do the HTTPS section first. For iPhone and iPad (Vikunja 2.2.0 or later) the docs list:
- Open Settings, then Apps, Calendar, Calendar Accounts (iOS 17 and earlier: Calendar, Accounts).
- Tap Add Account, Other, Add CalDAV Account.
- Server: the full URL, for example
https://tasks.example.com/api/v1/dav/principals/<username>/(withhttps://and the trailing slash). User name: your Vikunja username. Password: the CalDAV token. - Tap Next, turn on Reminders, tap Save.
Working clients per the docs: Evolution, OpenTasks with DAVx5, Tasks (Android), KOrganizer and Apple Reminders on iOS.
Linux#
The docs list Evolution and KOrganizer as working (we did not test them). Add a CalDAV account with the URL, your username and the token. To check the endpoint, ask without credentials:
curl -i http://127.0.0.1:3456/dav/
Expected, per the docs (requests without credentials only get an authentication challenge): a 401 that asks for a login.
Windows (PowerShell)#
The docs name no tested Windows CalDAV client. Use the web app or the desktop app, and let your phone sync over CalDAV. The same check:
curl.exe -i http://127.0.0.1:3456/dav/
Mac (Terminal)#
The docs name no tested macOS client and no Mac desktop app. Use the web app, and set up an iPhone or iPad with the steps above. The check:
curl -i http://127.0.0.1:3456/dav/
Check it worked. The endpoint asks for a login, and after you add the account a task list named after your project appears in the app.
Tip: Failed CalDAV logins are limited to 10 per minute per IP address (Vikunja 2.6.0 and later). If a client stops working after a few wrong passwords, fix its saved login and wait a minute before you try again.
Put it behind HTTPS (with or without Traefik)#
Beyond a test on your own computer, use HTTPS so logins and tokens do not cross the network in plain text. Phones need it for CalDAV. Both routes need a domain with a DNS record pointing to your server. Local-only users can skip this section.
In both routes, set .env to the HTTPS address (VIKUNJA_PUBLIC_URL=https://tasks.example.com/). The Compose file loses its host port, joins the proxy network and sets IPEXTRACTIONMETHOD to xff, so Vikunja sees the real client address. The docs say it then trusts X-Forwarded-For from private addresses, so nobody on your internal network may reach port 3456 directly.
With Traefik#
On a server that already runs Traefik v3 with the Docker provider, add labels and remove the host port.
This assumes an external Docker network called proxy and an entrypoint called https (the official example uses https too). Create the network once if it is missing:
docker network create proxy
Replace docker-compose.yml with this file:
services:
vikunja:
image: vikunja/vikunja:2.7.1
container_name: vikunja
restart: unless-stopped
environment:
VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL}
VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SECRET}
VIKUNJA_SERVICE_ENABLEREGISTRATION: ${VIKUNJA_REGISTRATION}
VIKUNJA_SERVICE_IPEXTRACTIONMETHOD: xff
VIKUNJA_DATABASE_TYPE: sqlite
VIKUNJA_DATABASE_PATH: /db/vikunja.db
volumes:
- ./files:/app/vikunja/files
- ./db:/db
networks:
- proxy
labels:
- "traefik.enable=true"
- "traefik.docker.network=proxy"
- "traefik.http.routers.vikunja.rule=Host(`tasks.<your-domain>`)"
- "traefik.http.routers.vikunja.entrypoints=https"
- "traefik.http.routers.vikunja.tls=true"
- "traefik.http.services.vikunja.loadbalancer.server.port=3456"
networks:
proxy:
external: true
If your Traefik issues certificates through a named resolver, add traefik.http.routers.vikunja.tls.certresolver=<your-resolver> (the official example calls it acme). Then run docker compose up -d in ~/docker_volumes/vikunja.
Check it worked. curl -I https://tasks.<your-domain>/ returns an HTTP status line over HTTPS, and the browser shows a valid certificate.
Without Traefik: Caddy#
Choose this if you have no Traefik. Caddy gets and renews its own certificates. For a public domain it uses Let's Encrypt. At home, with no public domain, tls internal makes Caddy sign a local certificate. Our Caddy guide covers it in depth.
Create the Caddy folders and the proxy network, and use the Vikunja file from the Traefik example without its labels: block:
docker network create proxy
mkdir -p ~/docker_volumes/caddy/{conf,data,config}
cd ~/docker_volumes/caddy
On Windows use New-Item -ItemType Directory -Force "$HOME\docker_volumes\caddy\conf","$HOME\docker_volumes\caddy\data","$HOME\docker_volumes\caddy\config". Save this as ~/docker_volumes/caddy/docker-compose.yml. It mounts the conf folder, not a single file, because Caddy's Docker docs warn that single-file mounts can break reloads. 2.11.8 is the newest Caddy image tag we found in November 2026:
services:
caddy:
image: caddy:2.11.8
container_name: caddy
restart: unless-stopped
ports:
- "80:80/tcp"
- "443:443/tcp"
- "443:443/udp"
volumes:
- ./conf:/etc/caddy
- ./data:/data
- ./config:/config
networks:
- proxy
networks:
proxy:
external: true
Save one of these as ~/docker_volumes/caddy/conf/Caddyfile. The first is for a public domain:
tasks.example.com {
reverse_proxy vikunja:3456
}
The second is for a home-only name. .home.arpa is reserved for home networks, so point it at your server in your router, your DNS or a hosts file:
tasks.home.arpa {
tls internal
reverse_proxy vikunja:3456
}
Start both stacks:
cd ~/docker_volumes/caddy && docker compose up -d cd ~/docker_volumes/vikunja && docker compose up -d
For tls internal, each device must trust Caddy's root certificate. Run the copy on the server, then install the file on each device you own:
Linux#
cd ~/docker_volumes/caddy docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/caddy-root.crt sudo cp /tmp/caddy-root.crt /usr/local/share/ca-certificates/caddy-local-root.crt sudo update-ca-certificates
Windows (PowerShell)#
Run PowerShell as administrator:
Set-Location "$HOME\docker_volumes\caddy" docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt "$env:TEMP\caddy-root.crt" certutil -addstore -f "ROOT" "$env:TEMP\caddy-root.crt"
Mac (Terminal)#
cd ~/docker_volumes/caddy docker compose cp caddy:/data/caddy/pki/authorities/local/root.crt /tmp/caddy-root.crt sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain /tmp/caddy-root.crt
Firefox and Chrome may keep their own certificate store, so import root.crt there too. A phone must trust the root as well; a public domain avoids that. Trust it only on devices you own.
Check it worked. curl -I https://tasks.example.com/ returns a status line over HTTPS. For a home name, the browser shows a padlock once the root is trusted. Open the site by the name in the Caddyfile, not by IP address.
Tip: If the page loads but the app cannot reach the API,
VIKUNJA_PUBLIC_URLand the browser address differ (for examplehttpagainsthttps). Make them equal and rundocker compose up -d.
Security and testing#
Vikunja ships many security fixes (eighteen in 2.6.0, forty-one in 2.7.1). That shows active auditing. It is also a reason to update often, and to keep the server off the open internet if you only need it at home.
| Control | How |
|---|---|
| Secret | Fixed VIKUNJA_SERVICE_SECRET from openssl rand -hex 32 |
| Registration | Off after setup (Step 4) |
| Port | 127.0.0.1:3456, or no host port behind a proxy. Never forward 3456 on your router |
| TLS | Vikunja serves plain HTTP; end TLS at a reverse proxy |
| Two-factor | Local accounts can turn on TOTP in their settings; it is not enforced. CalDAV then needs a token |
| Single sign-on | Optional OpenID Connect or LDAP. Our authentik guide shows one provider |
| Rate limits | Logins, sign-up and password resets: 10 per minute per IP, always on |
| User | The image runs as UID 1000. The docs say not to override it with root |
| Dumps and backups | A vikunja dump holds your config and VIKUNJA_* variables in plain text. Keep it private |
Remote access without opening ports is possible with a private VPN such as our wg-easy guide.
Tests after each change:
cd ~/docker_volumes/vikunja docker compose ps docker compose logs --tail=100 vikunja docker exec vikunja /app/vikunja/vikunja doctor curl -I https://tasks.<your-domain>/
doctor is the project's own diagnostic: version, config, public URL, secret, CORS, database and folders. It exits with 0 when all pass. Use curl.exe on Windows.
What leaves the machine: webhooks, avatar downloads, importers and OpenID sign-in make outgoing requests; Sentry error reporting is off by default (docs). Caddy or Traefik contact Let's Encrypt for a public domain. We did not capture traffic.
Use PostgreSQL instead of SQLite#
SQLite suits one person or a family. The docs recommend MySQL or PostgreSQL for "more than a handful of users", and give no tool to move from SQLite to PostgreSQL, so decide at the start. This is the official example, pinned. Add DB_PASSWORD to .env as another random hex value:
services:
vikunja:
image: vikunja/vikunja:2.7.1
container_name: vikunja
restart: unless-stopped
environment:
VIKUNJA_SERVICE_PUBLICURL: ${VIKUNJA_PUBLIC_URL}
VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SECRET}
VIKUNJA_SERVICE_ENABLEREGISTRATION: ${VIKUNJA_REGISTRATION}
VIKUNJA_DATABASE_HOST: db
VIKUNJA_DATABASE_TYPE: postgres
VIKUNJA_DATABASE_USER: vikunja
VIKUNJA_DATABASE_PASSWORD: ${DB_PASSWORD}
VIKUNJA_DATABASE_DATABASE: vikunja
ports:
- "127.0.0.1:3456:3456"
volumes:
- ./files:/app/vikunja/files
depends_on:
db:
condition: service_healthy
db:
image: postgres:18
restart: unless-stopped
environment:
POSTGRES_USER: vikunja
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- ./db:/var/lib/postgresql
healthcheck:
test: ["CMD-SHELL", "pg_isready -h localhost -U $$POSTGRES_USER"]
interval: 30s
start_period: 15s
start_interval: 5s
Use a hex password: in 2.7.0 a PostgreSQL password containing @ stopped Vikunja from starting (fixed in 2.7.1). The official PostgreSQL example only prepares the files folder with chown 1000. Back up with the pg_dump command in the backup section.
Common mistakes#
These come from the official docs, the Vikunja forum and common AI assistant answers.
| Mistake | Why it happens | Do this instead |
|---|---|---|
| No secret, so everyone is logged out after each restart | The default secret is random "at each startup" (docs); the maintainer named it in a forum logout thread | Set VIKUNJA_SERVICE_SECRET once in .env |
| Wrong or missing public URL | The log says service.publicurl is required when cors.enable is true; forum threads show a wrong variable name, no port or no exposed port | Use VIKUNJA_SERVICE_PUBLICURL with scheme, host, port and trailing slash |
| Files folder not owned by user 1000 | The image has no tools to fix ownership; uploads fail with open /app/vikunja/files/1: permission denied | sudo chown -R 1000 on the folder before starting; do not use PUID/PGID |
Page over HTTPS but public URL still http:// | Browsers block an http API call from an https page; the app shows AxiosError: Network Error | Set the public URL to the https address and recreate the container |
| Leaving sign-up open on a reachable server | service.enableregistration defaults to true | Register your users, then set it to false (Step 4) |
| Running a shell command in the container | The image is built from scratch and has no shell | docker exec vikunja /app/vikunja/vikunja <command>, or docker logs vikunja |
Pointing the database elsewhere in config.yml | The image presets VIKUNJA_DATABASE_PATH, and environment variables beat the config file | Change the host side of the volume (./db), not the inside path |
| Using the tagless image from the official examples | No tag means latest. 2.7.1 renamed API token permission keys and added import and bucket limits | Pin 2.7.1, read the release notes, change the tag on purpose |
AI assistants: VIKUNJA_SERVICE_JWTSECRET, PUID/PGID, or two containers for API and front end | Older tutorials show them. The docs now call JWTSecret deprecated, say PUID/PGID have no effect, and say one container serves both | Use VIKUNJA_SERVICE_SECRET, the user: option, and one vikunja/vikunja service |
AI assistants: a Compose healthcheck that runs curl inside the Vikunja container | The image has no curl and no shell, so the check always fails | Check from the host: curl -s http://127.0.0.1:3456/health |
Tip: Run
docker exec vikunja /app/vikunja/vikunja doctorafter every config change. It reports the public URL, the secret, CORS origins and folder permissions in one go.
Tip: Deleting a project in Vikunja is permanent. The docs say there is no trash or undo, so take a backup before you tidy up (see Updates and backups).
Troubleshooting#
| Problem | Solution |
|---|---|
service.publicurl is required when cors.enable is true | Set VIKUNJA_SERVICE_PUBLICURL, then docker compose up -d. Check with docker exec vikunja /app/vikunja/vikunja doctor |
open /app/vikunja/files/1: permission denied | sudo chown -R 1000 ~/docker_volumes/vikunja/files, then restart. Rootless Docker needs user: "0:0" in Compose; doctor detects it |
Data export does nothing, log shows open /tmp/vikunja-export-....zip | /tmp must be writable. The docs add - /tmp:/tmp under volumes: as a fix |
Migration failed: dial tcp ...: connect: connection refused | PostgreSQL was not ready at first start. restart: unless-stopped retries; read the log after a minute |
lookup db on 127.0.0.11:53: server misbehaving | VIKUNJA_DATABASE_HOST must equal the database service name in Compose |
hostname resolving error: lookup @db: no such host | Vikunja 2.7.0 with @ in the PostgreSQL password. Update to 2.7.1 |
AxiosError: Network Error or "check that the API address is valid" | Public URL and browser address differ, often http against https. Make them match |
CalDAV answers 429 Too Many Requests | Too many failed logins from your IP. Fix the saved credentials and wait a minute |
Diagnostics to run first: docker compose ps, docker logs vikunja --tail 100, docker inspect vikunja and docker exec vikunja /app/vikunja/vikunja doctor.
Known bugs and limits (November 2026)#
- Thunderbird 68 is listed in the docs as not working with Vikunja CalDAV.
- Not synced over CalDAV: attachments, location and comments (docs list); recurrence travels from Vikunja to the client only.
- Password reset link: an October 2026 forum report says the link signs the user in without a new-password form. Unanswered when we read it.
- Project deletion is permanent, with no trash.
Updates and backups#
Update. Vikunja releases often, many with security fixes. Read the release notes first, back up, change the tag in docker-compose.yml, then:
cd ~/docker_volumes/vikunja docker compose pull docker compose up -d docker compose logs --tail=50 vikunja
The docs say migrations run automatically at start. They describe no downgrade, so if an update goes wrong, restore the backup instead of switching the tag back.
Back up. The docs name two things: the database and the attachment files. For SQLite the docs say to copy the database file; we stop the container first so the file is not in the middle of a write. Run this script from the Vikunja folder. The archive includes .env with your secret, so it is private:
cd ~/docker_volumes/vikunja docker compose stop vikunja tar -czf "backups/vikunja-$(date +%F).tar.gz" db files .env docker-compose.yml chmod 600 backups/vikunja-*.tar.gz docker compose start vikunja
For PostgreSQL, the docs use pg_dump. Here it runs inside the database container (save the files folder too):
docker compose exec -T db pg_dump -U vikunja vikunja > "backups/vikunja-$(date +%F).sql" tar -czf "backups/vikunja-files-$(date +%F).tar.gz" files
Vikunja also has vikunja dump (one zip with config, files and database) and vikunja restore. Its zip holds your config and secret in plain text, so we use it only if you encrypt the result.
Linux#
Save the commands above as backup.sh in the Vikunja folder, make it executable, and schedule it at 03:00 with crontab -e:
0 3 * * * <your-home>/docker_volumes/vikunja/backup.sh
Windows (PowerShell)#
Save this as backup.ps1 in the Vikunja folder (tar.exe ships with Windows 10 and 11), then add a daily 03:00 task:
Set-Location "$HOME\docker_volumes\vikunja" docker compose stop vikunja tar -czf "backups\vikunja-$(Get-Date -Format yyyy-MM-dd).tar.gz" db files .env docker-compose.yml docker compose start vikunja
schtasks /Create /SC DAILY /ST 03:00 /TN "Vikunja backup" /TR "powershell.exe -NoProfile -File `"$HOME\docker_volumes\vikunja\backup.ps1`""
Mac (Terminal)#
Use the same script as Linux and schedule it with crontab -e. Keep a copy of the archives on another machine.
Restore test. Unpack an archive into a new empty folder, never over the live one, and check that the database file and the files folder are there:
mkdir -p /tmp/vikunja-restore-test tar -xzf backups/vikunja-<date>.tar.gz -C /tmp/vikunja-restore-test ls -R /tmp/vikunja-restore-test/db
To restore for real: docker compose down, put the db and files folders back, and docker compose up -d.
Performance and resources#
The docs publish no memory, CPU or disk numbers for Vikunja, so this guide gives none. Measure your own server:
- Resources:
docker stats vikunjashows live CPU and memory. - Health:
curl -s http://127.0.0.1:3456/healthanswersOKwhen the database is reachable. - Database: SQLite is bundled and needs nothing else. For more than a handful of users the docs recommend PostgreSQL or MySQL.
- Cache and search: Redis is optional and not needed for a few users. ParadeDB (a PostgreSQL extension) improves full-text search; Typesense support was removed in 2.0.0.
FAQ#
Is Vikunja free to use? Yes, under AGPL-3.0-or-later. A paid Vikunja Pro adds an admin panel, audit logs and time tracking; the "Vikunja stays open" post says paid tiers ship under the same licence.
Does it need a GPU? No. The docs do not mention one.
Which apps can I use? The web app everywhere, a desktop app for Linux and Windows, and a mobile app that the docs say supports only basic features. For phone and calendar apps, use CalDAV (Step 5).
Can I import from Todoist, TickTick or Trello? The docs describe importers, and /api/v1/info lists the enabled ones. We did not test an import.
Does it send my data to the project? The docs describe no telemetry for the free version. Outgoing requests come from features you use, such as webhooks and OpenID sign-in.
Can I run it without a reverse proxy? Yes, on your own computer or a trusted network. The docs say Vikunja serves plain HTTP and ask you to end TLS at a proxy otherwise.
Sources#
- Vikunja release v2.7.1, README, Dockerfile and LICENSE - read November 2026
- Installing, Docker walkthrough, Full docker example, Setup behind a reverse proxy - read November 2026
- Configuration options, Security hardening, Command line interface, What to backup - read November 2026
- CalDAV and Desktop packages - read November 2026
- Vikunja 2.7.1 release post, Vikunja 2.0.0 release post and Vikunja stays open - read November 2026
- Vikunja image on Docker Hub (tag
2.7.1checked) and PostgreSQL image (tag18checked) - November 2026 - Caddy Docker image (tag
2.11.8checked), Caddy automatic HTTPS and tls directive - read November 2026 - Forum threads 3349, 4159, 2025, 4081, 1801, 4581, 9386 and 9460 - read November 2026
- GitHub pull request #4092 (PostgreSQL credentials fix) - read November 2026
Checked against the official docs on 2026-11-05.