DockerSelf-Hosting

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.

Vikunja official logo

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.

WhatValue (official docs, read November 2026)
Current versionVikunja 2.7.1, released 20 October 2026 (latest stable)
Image to pinvikunja/vikunja:2.7.1 (tag checked on Docker Hub)
Time neededAbout 20 minutes (our estimate, not measured)
Size and RAMDocker Hub lists the 2.7.1 tag at about 55 MB. The docs publish no RAM or CPU figures
PortTCP 3456 inside the container
LicenceAGPL-3.0-or-later (README and LICENSE at the release tag)
Official docsvikunja.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.

Left-to-right diagram: a browser and a CalDAV app on your devices connect over HTTPS to Caddy or Traefik on your server, which forwards to the Vikunja container on port 3456, which stores tasks in a database folder and attachments in a files folder 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#

SystemWhat you need
LinuxDocker Engine, the Compose plugin, curl, openssl
WindowsDocker Desktop, PowerShell (curl.exe ships with Windows 10 and 11)
MacDocker Desktop, Terminal (curl and openssl are built in)

The project publishes no minimum RAM or disk figures. Check Docker on any system:

Run in terminalbash
docker --version
docker compose version

Checklist:

  • [ ] Docker and Docker Compose work (commands above)
  • [ ] Port 3456 is free on the host (ss -ltn | grep 3456 on 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#

Run in terminalbash
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.

Codepowershell
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.

Run in terminalbash
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. PUID and PGID variables from other images do nothing here; the docs say to use Docker's user: 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#

Run in terminalbash
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).

Codepowershell
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:

Run in terminalbash
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:// or https://, 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#

Run in terminalbash
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)#

Codepowershell
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:

Run in terminalbash
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 sh fails. Read problems with docker logs vikunja and run commands by the full path, for example docker 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:

Run in terminalbash
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:

Run in terminalbash
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:

Codepowershell
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:

  1. Open Settings, then Apps, Calendar, Calendar Accounts (iOS 17 and earlier: Calendar, Accounts).
  2. Tap Add Account, Other, Add CalDAV Account.
  3. Server: the full URL, for example https://tasks.example.com/api/v1/dav/principals/<username>/ (with https:// and the trailing slash). User name: your Vikunja username. Password: the CalDAV token.
  4. 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:

Run in terminalbash
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:

Run in terminalpowershell
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:

Run in terminalbash
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:

Run in terminalbash
docker network create proxy

Replace docker-compose.yml with this file:

File contentyaml
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:

Run in terminalbash
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:

File contentyaml
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:

File contenttext
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:

File contenttext
tasks.home.arpa {
    tls internal
    reverse_proxy vikunja:3456
}

Start both stacks:

Run in terminalbash
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#

Run in terminalbash
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:

Codepowershell
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)#

Run in terminalbash
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_URL and the browser address differ (for example http against https). Make them equal and run docker 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.

ControlHow
SecretFixed VIKUNJA_SERVICE_SECRET from openssl rand -hex 32
RegistrationOff after setup (Step 4)
Port127.0.0.1:3456, or no host port behind a proxy. Never forward 3456 on your router
TLSVikunja serves plain HTTP; end TLS at a reverse proxy
Two-factorLocal accounts can turn on TOTP in their settings; it is not enforced. CalDAV then needs a token
Single sign-onOptional OpenID Connect or LDAP. Our authentik guide shows one provider
Rate limitsLogins, sign-up and password resets: 10 per minute per IP, always on
UserThe image runs as UID 1000. The docs say not to override it with root
Dumps and backupsA 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:

Run in terminalbash
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:

File contentyaml
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.

MistakeWhy it happensDo this instead
No secret, so everyone is logged out after each restartThe default secret is random "at each startup" (docs); the maintainer named it in a forum logout threadSet VIKUNJA_SERVICE_SECRET once in .env
Wrong or missing public URLThe log says service.publicurl is required when cors.enable is true; forum threads show a wrong variable name, no port or no exposed portUse VIKUNJA_SERVICE_PUBLICURL with scheme, host, port and trailing slash
Files folder not owned by user 1000The image has no tools to fix ownership; uploads fail with open /app/vikunja/files/1: permission deniedsudo 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 ErrorSet the public URL to the https address and recreate the container
Leaving sign-up open on a reachable serverservice.enableregistration defaults to trueRegister your users, then set it to false (Step 4)
Running a shell command in the containerThe image is built from scratch and has no shelldocker exec vikunja /app/vikunja/vikunja <command>, or docker logs vikunja
Pointing the database elsewhere in config.ymlThe image presets VIKUNJA_DATABASE_PATH, and environment variables beat the config fileChange the host side of the volume (./db), not the inside path
Using the tagless image from the official examplesNo tag means latest. 2.7.1 renamed API token permission keys and added import and bucket limitsPin 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 endOlder tutorials show them. The docs now call JWTSecret deprecated, say PUID/PGID have no effect, and say one container serves bothUse VIKUNJA_SERVICE_SECRET, the user: option, and one vikunja/vikunja service
AI assistants: a Compose healthcheck that runs curl inside the Vikunja containerThe image has no curl and no shell, so the check always failsCheck from the host: curl -s http://127.0.0.1:3456/health

Tip: Run docker exec vikunja /app/vikunja/vikunja doctor after 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#

ProblemSolution
service.publicurl is required when cors.enable is trueSet VIKUNJA_SERVICE_PUBLICURL, then docker compose up -d. Check with docker exec vikunja /app/vikunja/vikunja doctor
open /app/vikunja/files/1: permission deniedsudo 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 refusedPostgreSQL 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 misbehavingVIKUNJA_DATABASE_HOST must equal the database service name in Compose
hostname resolving error: lookup @db: no such hostVikunja 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 RequestsToo 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:

Run in terminalbash
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:

Run in terminalbash
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):

Run in terminalbash
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:

File contenttext
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:

Codepowershell
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
Run in terminalpowershell
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:

Run in terminalbash
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 vikunja shows live CPU and memory.
  • Health: curl -s http://127.0.0.1:3456/health answers OK when 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#

Checked against the official docs on 2026-11-05.