Run Personal Task Manager

I got tired of paying for task management software. Todoist costs money. Asana's free tier is limiting. Microsoft To Do syncs everything to cloud servers I don't control. So I decided to host my own task manager at home, and honestly, it's better than paying for the cloud versions.

When I set up Vikunja, I gained something the SaaS tools don't offer: complete control over my workflow, zero subscription fees, and the ability to integrate it directly with my home infrastructure. No vendor lock-in. No rate limits. No worrying about whether the service will exist next year.

This tutorial walks you through deploying a self-hosted task manager using Docker Compose. I'll show you how to run Vikunja (my top pick) with persistent storage, SSL termination through Caddy, and optional team collaboration. You'll have a fully functional task management system running on your home server in under 30 minutes.

Why Self-Host a Task Manager?

Before I dive into the setup, let me be clear about why this matters. Cloud task managers charge $5–12 per user per month. If you're managing tasks for a small team or yourself, that adds up. More importantly, you're paying for convenience while sacrificing privacy.

Self-hosting gives you:

The downside? You maintain the server. You handle backups. You manage updates. For my use case—personal task management across a small team of 3 people—that trade-off is worth it.

Choosing Your Task Manager

I evaluated three popular self-hosted options:

Vikunja is what I run. It's Go-based, lightweight, and has a clean web UI plus mobile app support. Single binary deployment. Supports teams, recurring tasks, priorities, and subtasks. The UI is modern without being bloated.

Plane is newer. It's built with React and appeals to engineering teams. Great for project-level organization and issue tracking. Heavier resource footprint than Vikunja. Better if you're managing software projects.

OpenTodos is minimal and fast. Fewer features than Vikunja or Plane, but if you just need a simple checklist, it works. I didn't choose this because I needed recurring tasks and subtasks.

For this tutorial, I'm going with Vikunja. It's the sweet spot between features and simplicity.

Prerequisites

You'll need:

If you don't have Docker yet, I have a separate guide. For now, assume you can run docker-compose up -d and it works.

Docker Compose Setup for Vikunja

I keep my Vikunja stack in /opt/vikunja. Here's the exact compose file I use:

version: '3.8'

services:
  vikunja:
    image: vikunja/vikunja:latest
    container_name: vikunja
    restart: unless-stopped
    ports:
      - "127.0.0.1:3456:3456"
    environment:
      VIKUNJA_SERVICE_FRONTENDURL: "https://tasks.example.com"
      VIKUNJA_AUTH_OPENID_ENABLED: "false"
      VIKUNJA_MAILER_ENABLED: "false"
      VIKUNJA_LOGGER_LEVEL: "info"
    volumes:
      - vikunja_data:/app/vikunja/files
      - ./vikunja.conf.d:/etc/vikunja/vikunja.conf.d
    depends_on:
      - vikunja_db
    networks:
      - vikunja_net

  vikunja_db:
    image: postgres:15-alpine
    container_name: vikunja_db
    restart: unless-stopped
    environment:
      POSTGRES_USER: vikunja
      POSTGRES_PASSWORD: changeme_to_strong_password
      POSTGRES_DB: vikunja
    volumes:
      - vikunja_db_data:/var/lib/postgresql/data
    networks:
      - vikunja_net
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U vikunja"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  vikunja_data:
    driver: local
  vikunja_db_data:
    driver: local

networks:
  vikunja_net:
    driver: bridge

Create the directory structure:

mkdir -p /opt/vikunja/vikunja.conf.d
cd /opt/vikunja
touch docker-compose.yml
# Paste the compose file above into docker-compose.yml

# Start the stack
docker-compose up -d

# Watch for startup errors
docker-compose logs -f vikunja

The database will initialize automatically. Wait 10–15 seconds, then check if Vikunja is running:

curl -s http://127.0.0.1:3456/api/v1/info | jq .

If you see JSON back, Vikunja is alive.

Watch out: Change the Postgres password immediately. That changeme_to_strong_password is not a real password. Use something like openssl rand -base64 32 to generate a secure one. Store it in a password manager.

Reverse Proxy with Caddy

I don't expose Vikunja directly to the internet. Instead, Caddy sits in front of it, handles SSL, and proxies traffic. Here's my Caddyfile:

tasks.example.com {
  reverse_proxy 127.0.0.1:3456 {
    header_up X-Forwarded-For {remote_host}
    header_up X-Forwarded-Proto https
  }
}

If you already have Caddy running globally, add this block to your existing Caddyfile. If not, start Caddy in Docker:

docker run -d \
  --name caddy \
  --network host \
  -v /etc/caddy/Caddyfile:/etc/caddy/Caddyfile:ro \
  -v caddy_data:/data \
  -v caddy_config:/config \
  --restart unless-stopped \
  caddy:latest

Caddy auto-renews Let's Encrypt certificates. No manual renewal needed. Once it's up, visit https://tasks.example.com (replace with your actual domain) and you'll see the Vikunja login screen.

Initial Configuration and User Setup

On first visit, Vikunja prompts you to create an account. The first user becomes admin. Create your account and log in.

Then access the admin panel. In the top right, click your profile → Settings → Admin panel. From here you can:

To invite team members, go to Admin → Users → Add New User. Create accounts for each person, or let them self-register if your instance is internal-only.

Creating Your First Workspace and Tasks

After login, click "New List" to create your first list. I organize mine by context:

Within each list, add tasks. Vikunja supports:

The interface is intuitive. Click on a task to expand it and add details. Use the calendar view to see upcoming deadlines.

Backup Strategy

Your task data lives in two places: the Postgres database and the files volume. You need both.

I back up Vikunja weekly using a simple script:

#!/bin/bash
# /opt/vikunja/backup.sh

BACKUP_DIR="/backups/vikunja"
DATE=$(date +%Y%m%d_%H%M%S)

mkdir -p $BACKUP_DIR

# Dump database
docker exec vikunja_db pg_dump -U vikunja vikunja | gzip > $BACKUP_DIR/vikunja_db_$DATE.sql.gz

# Copy files volume
docker run --rm \
  -v vikunja_data:/data \
  -v $BACKUP_DIR:/backup \
  alpine:latest \
  tar czf /backup/vikunja_files_$DATE.tar.gz /data

echo "Backup complete: $BACKUP_DIR"

Run this via cron weekly:

0 2 * * 0 /opt/vikunja/backup.sh

Every Sunday at 2 AM, your database and files are backed up. Store backups offsite (rsync to another server, cloud storage, whatever you prefer).

Mobile Access

Vikunja has an official mobile app. Download "Vikunja" from the App Store or Google Play, then add your server:

The app syncs bidirectionally. Create a task on your phone, it appears on the web. Complete a task on the web, your phone updates. Solid offline support too—the app caches tasks locally.

Performance and Scaling

On my 2GB VPS, Vikunja uses roughly 80–150 MB RAM at idle. Database queries are fast even with thousands of tasks. Postgres handles the heavy lifting.

If you're running this on a Raspberry Pi at home, allocate 2GB+ RAM and use an SSD, not SD card. Vikunja isn't heavy, but database writes wear out storage over time.

For teams larger than 10 people, consider splitting to dedicated hardware or upgrading your VPS. I haven't hit scaling issues with 3 users and ~500 tasks, so I can't speak to enterprise loads.

Security Considerations

A few hardening steps I took:

Disable public registration. In Admin panel, toggle "Allow Registration" off. Only you can create accounts manually. Prevents randoms from signing up.

Use strong Postgres passwords. Already mentioned, but essential. Don't use `password123`.

Enable 2FA. Vikunja supports TOTP-based 2FA. In Settings → Security, enable it. Adds an extra layer if your password is compromised.

Firewall Vikunja port. The Vikunja container listens on 127.0.0.1:3456, so it's not directly exposed. But if you ever expose the raw port, use UFW:

ufw allow from 10.0.0.0/8 to any port 3456  # Internal network only

Keep Docker images updated. Run docker pull vikunja/vikunja:latest monthly and restart the container. Check the Vikunja GitHub for release notes.

Integrations and Next Steps

Vikunja exposes a REST API. I use this to:

The API docs live at https://tasks.example.com/api/v1/docs. Any task can be created, updated, or deleted programmatically. Powerful if you like automation.

For example, here's a curl command to create a task:

curl -X POST https://tasks.example.com/api/v1/lists/1