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:
- Cost control: One-time hardware cost, zero monthly subscriptions
- Privacy: Your tasks stay on your network, not on Todoist's servers
- Customization: Modify the task schema, add custom fields, integrate with your own tools
- Integration: Hook task manager directly to your local services (webhooks to Home Assistant, API calls to Discord, etc.)
- Offline access: When internet drops, your tasks are still there
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:
- A Linux server or VM with Docker and Docker Compose installed (I run mine on a Hetzner VPS and also have a spare Pi 4 at home)
- A domain name pointing to your server's IP (optional but recommended for SSL)
- Caddy or Nginx Proxy Manager for reverse proxy and SSL termination
- At least 2GB RAM available; 1GB disk space minimum
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.
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:
- Enable/disable user registration (I disable it to prevent randos signing up)
- Configure email notifications (optional, but useful for task reminders)
- Set rate limits
- Manage other users if you're adding team members
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:
- Work
- Home
- Learning
- Admin (server maintenance tasks)
Within each list, add tasks. Vikunja supports:
- Subtasks: Break a task into smaller steps
- Due dates: Calendar-based deadlines
- Recurring tasks: Daily, weekly, monthly repeats
- Priority levels: Urgent, high, medium, low
- Assignees: If you have team members, assign tasks to people
- Labels: Tag-based organization
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:
- In the app, go to Settings → Server
- Enter your domain:
https://tasks.example.com - Log in with your credentials
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:
- Create tasks from Home Assistant automations
- Post task summaries to Discord weekly
- Bulk-import tasks from a CSV
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