Innovate | Connect | Transform
ContactService statusUSD $

GICT knowledge base

How to Self-Host Vikunja with Docker on Ubuntu 24.04

Published

Self Hosted
How to Self-Host Vikunja with Docker

Task lists become more useful when they can hold project notes, due dates, and attachments in one place. Vikunja provides that in a browser, with your tasks stored on your own server.

Before you start

Use a fresh Ubuntu 24.04 VPS with sudo access and capacity for the database, application and attachments. Measure startup and representative use before sizing a production service. Shared hosting without Docker access is not suitable.

This setup suits personal use or a small group whose members already have SSH access. It does not provide a public HTTPS address for colleagues. Email reminders and password-reset messages need an SMTP configuration and are outside this installation guide. The features used here do not require a Vikunja Pro license.

Install Docker and Compose

Inspect the current server first:

$ sudo ss -lntp
$ dpkg -l docker.io docker-ce docker-compose-v2 docker-compose-plugin

Missing packages are normal on a fresh VPS. If Docker is already installed from Docker’s repository, keep that installation and its Compose plugin instead of mixing package sources.

On a fresh Ubuntu installation:

$ sudo apt-get update
$ sudo apt-get install -y docker.io docker-compose-v2 openssl curl
$ sudo systemctl enable --now docker
$ sudo docker version --format '{{.Server.Version}}'
$ sudo docker compose version

The Compose command returned:

Docker Compose version 2.40.3+ds1-0ubuntu1~24.04.1

Use Docker Engine 28 or later and Compose v2. Earlier Docker versions have a localhost port-publishing limitation that can expose a loopback-bound port to other machines on the same network. Update through your existing package source if needed before continuing.

Create the project files

As your normal sudo-capable user:

$ mkdir -p ~/vikunja
$ chmod 700 ~/vikunja
$ cd ~/vikunja
$ mkdir -p files
$ sudo chown 1000 files
$ sudo chmod 750 files

The official Vikunja container runs as UID 1000. Its attachment directory must be writable by that user; database permissions are handled by the PostgreSQL named volume below. This example uses regular Docker Engine, not rootless Docker.

Generate two different random values:

$ openssl rand -hex 32
$ openssl rand -hex 32

Keep them private. Create ~/vikunja/.env, replacing the two REPLACE_ values:

VIKUNJA_VERSION=2.6.0
POSTGRES_PASSWORD=REPLACE_WITH_FIRST_RANDOM_VALUE
VIKUNJA_SECRET=REPLACE_WITH_SECOND_RANDOM_VALUE

The application secret signs authentication tokens; it is not your account password. Protect the file:

$ chmod 600 .env

Create ~/vikunja/compose.yaml:

name: vikunja
services:
  db:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_DB: vikunja
      POSTGRES_USER: vikunja
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 30s

  vikunja:
    image: vikunja/vikunja:${VIKUNJA_VERSION:?Set VIKUNJA_VERSION}
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "127.0.0.1:3456:3456"
    environment:
      VIKUNJA_SERVICE_PUBLICURL: http://localhost:3456/
      VIKUNJA_SERVICE_SECRET: ${VIKUNJA_SECRET:?Set VIKUNJA_SECRET}
      VIKUNJA_SERVICE_ENABLEREGISTRATION: "false"
      VIKUNJA_DATABASE_TYPE: postgres
      VIKUNJA_DATABASE_HOST: db
      VIKUNJA_DATABASE_DATABASE: vikunja
      VIKUNJA_DATABASE_USER: vikunja
      VIKUNJA_DATABASE_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD}
      VIKUNJA_MAILER_ENABLED: "false"
    volumes:
      - ./files:/app/vikunja/files

volumes:
  pgdata:

PostgreSQL has no published port, and Vikunja listens only on the VPS loopback address. PUBLICURL is the address you will open in your browser, not the VPS IP. It includes the local port. The PostgreSQL 18 volume mount is /var/lib/postgresql; do not substitute an older PostgreSQL example’s data path.

Start Vikunja and create your account

From ~/vikunja, validate the configuration without printing its expanded secrets, then start the services:

$ sudo docker compose config --quiet
$ sudo docker compose pull
$ sudo docker compose up -d
$ sudo docker compose ps
$ curl -fsS http://127.0.0.1:3456/health

The health request returned:

OK

The database must become healthy before Vikunja starts. If the HTTP request arrives during initialization, wait a few seconds and run it again. A successful health request confirms the server is answering, but it does not prove that attachments work.

Check the application version:

$ sudo docker compose exec -T vikunja /app/vikunja/vikunja version

A successful account-creation command should confirm the requested user. Output wording can vary by release; inspect the command result before attempting login.

Vikunja api version v2.6.0
Built with go1.27.0

Public registration is disabled. Create your account through the CLI, replacing the example username and email address:

$ sudo docker compose exec vikunja /app/vikunja/vikunja user create --username owner --email you@example.com


Enter a strong, unique password when prompted. Do not put it in the command line. Use an address you control even though email is disabled here. This creates a normal account that owns its projects; it does not require the paid instance-administration panel.

Open the private browser connection

On your own computer, open another terminal and run the following command. Replace ubuntu with your VPS login name and VPS_IP with its address:

$ ssh -N -o ExitOnForwardFailure=yes -L 127.0.0.1:3456:127.0.0.1:3456 ubuntu@VPS_IP

Use your normal SSH key options if needed. Leave this terminal running. It is normal for the command to show no prompt or output while the tunnel is open.

Open http://localhost:3456/ in your browser and sign in with the account you just created. HTTP runs only on the local ends of this connection; traffic between your computer and VPS is carried inside encrypted SSH. Do not replace the loopback binding with a public port to make access easier.

If local port 3456 is already occupied, close the conflicting local service or choose another local port. If you change it, update VIKUNJA_SERVICE_PUBLICURL to match the new browser address and recreate the Vikunja service with sudo docker compose up -d. Keep the remote destination at port 3456.

Verify tasks and attachments

Select Projects in the sidebar, then New project. Enter Website launch and select Create. Add a task named Review the homepage, open it, add a short description, and set a due date. Select Save below the description. Use Set Due Date, click the new date field, choose a date, and select Confirm. Reload the page to confirm the changes remain.

Select Add Attachments and choose a small text file or PDF. Use the attachment’s download-arrow button (Download this attachment) and open the downloaded copy. Seeing the task save is not enough: project data lives in PostgreSQL, while attachments live in the files directory. In the test, a task still saved when that directory was deliberately made unwritable, but the upload failed. Correcting its ownership restored uploads.

Select Mark task done! and check that Vikunja shows its completed state. Some views hide completed tasks; enable their display if it disappears from the list. Sign out and sign in again to confirm both the account and project remain available.

If something does not work

  • Browser cannot connect: confirm that the SSH terminal is still running. On the VPS, run the health request above and sudo docker compose ps from ~/vikunja. A working server-side response with a failed browser connection points to the tunnel or local port.
  • Attachment upload fails: check the ownership of ~/vikunja/files. For this fresh installation, UID 1000 needs write access. Do not make the directory world-writable. Rootless Docker has different UID-mapping requirements.
  • Wrong browser address: keep VIKUNJA_SERVICE_PUBLICURL consistent with the exact URL and port you open. Editing a Compose setting requires sudo docker compose up -d; a plain restart does not apply changed environment variables.
  • Cannot open a shell in the container: the official image has no shell. Run the Vikunja executable directly, as in the account command, or inspect logs:
$ cd ~/vikunja
$ sudo docker compose logs --tail=50 vikunja db

Do not delete the database volume to fix a login or configuration error. The tasks belong to that database, and changing a database password in .env alone does not change an existing PostgreSQL user’s password.

Reference: Vikunja Docker deployment. Check the documentation for your installed version before applying changes.

Related guides

Account services are unavailable until the production backend is configured.