Tududi — a self-hosted task and project manager with areas, projects, tasks, notes and tags, plus recurring tasks and a calendar view. Deployed as a single host-networked Nomad service (SQLite).
Needs nomad-pack on PATH. The script only adds the nomploy registry and runs this pack.
Source ↗ Project ↗ ★ 3.4k ⚑ Report an issue
Save as values.hcl, edit, then run:
# The name of the Nomad job.
job_name = "tududi"
# The Nomad namespace to deploy into.
namespace = "default"
# The datacenters to deploy to.
datacenters = ["*"]
# The Tududi container image. Pin a tag in production.
image = "chrisvel/tududi:latest"
# Host port for the Tududi web UI.
port = 3002
# Email of the admin account seeded on first start.
admin_email = "admin@example.com"
# Password of the admin account seeded on first start. CHANGE THIS.
admin_password = "tududi_change_me"
# Secret used to sign sessions (openssl rand -hex 64). CHANGE THIS.
session_secret = "change_me_to_a_long_random_session_secret_0000"
# Comma-separated origins allowed to reach the API. Set to your public URL.
allowed_origins = "http://localhost:3002"
# Set true when running behind a reverse proxy (e.g. Traefik) so secure cookies work.
trust_proxy = "true"
# Named volume for the SQLite database (/app/db).
db_volume = "tududi_db"
# Named volume for uploaded files (/app/uploads).
uploads_volume = "tududi_uploads"
# Named volume for backups (/app/backups).
backups_volume = "tududi_backups"
# Placement constraints. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true".
constraints = []
# The task resources.
resources = {
cpu = 500
memory = 768
}
| Name | Type | Default | Description |
|---|---|---|---|
| job_name | string | "tududi" | The name of the Nomad job. |
| namespace | string | "default" | The Nomad namespace to deploy into. |
| datacenters | list | ["*"] | The datacenters to deploy to. |
| image | string | "chrisvel/tududi:latest" | The Tududi container image. Pin a tag in production. |
| port | number | 3002 | Host port for the Tududi web UI. |
| admin_email set me | string | "admin@example.com" | Email of the admin account seeded on first start. |
| admin_password set me | string | "tududi_change_me" | Password of the admin account seeded on first start. CHANGE THIS. |
| session_secret set me | string | "change_me_to_a_long_random_session_secret_0000" | Secret used to sign sessions (openssl rand -hex 64). CHANGE THIS. |
| allowed_origins | string | "http://localhost:3002" | Comma-separated origins allowed to reach the API. Set to your public URL. |
| trust_proxy | string | "true" | Set true when running behind a reverse proxy (e.g. Traefik) so secure cookies work. |
| db_volume | string | "tududi_db" | Named volume for the SQLite database (/app/db). |
| uploads_volume | string | "tududi_uploads" | Named volume for uploaded files (/app/uploads). |
| backups_volume | string | "tududi_backups" | Named volume for backups (/app/backups). |
| constraints | list | [] | Placement constraints. On a nomploy cluster: attribute = "$${meta.nomploy_control_plane}", operator = "=", value = "true". |
| resources | object | {
cpu = 500
memory = 768
} | The task resources. |
No variables match.
This pack stores data in 3 Docker named volumes:
tududi_dbtududi_uploadstududi_backups
restic
# Run on the node hosting this pack. Point restic at your repo first: # export RESTIC_REPOSITORY="s3:https://<account>.r2.cloudflarestorage.com/<bucket>" # export RESTIC_PASSWORD="<repo-password>" # export AWS_ACCESS_KEY_ID=<key> AWS_SECRET_ACCESS_KEY=<secret> restic backup \ /var/lib/docker/volumes/tududi_db/_data \ /var/lib/docker/volumes/tududi_uploads/_data \ /var/lib/docker/volumes/tududi_backups/_data
rclone (sync to S3/R2)
rclone sync /var/lib/docker/volumes/tududi_db/_data backup:<bucket>/tududi_db rclone sync /var/lib/docker/volumes/tududi_uploads/_data backup:<bucket>/tududi_uploads rclone sync /var/lib/docker/volumes/tududi_backups/_data backup:<bucket>/tududi_backups
Paths assume the default Docker volume location (/var/lib/docker/volumes). Restore by stopping the job, restoring files into the same volume, and re-running the pack.
Tududi is a self-hosted task and project management app. It organises work into areas → projects → tasks, with notes, tags, recurring tasks, due dates, a today/calendar view and multi-user sharing — a clean, keyboard-friendly alternative to Todoist or Things that runs on your own server.
This pack runs Tududi as a single host-networked Nomad service backed by SQLite.
nomad-pack run tududi --registry=nomploy \
--var admin_email=you@example.com \
--var admin_password=$(openssl rand -hex 12) \
--var session_secret=$(openssl rand -hex 64) \
--var allowed_origins=https://tasks.example.com
Open http://<node-ip>:3002 and sign in with your admin email/password.
| Variable | Default | Description |
|---|---|---|
image |
chrisvel/tududi:latest |
Container image (pin a tag in production). |
port |
3002 |
Host port for the web UI. |
admin_email |
admin@example.com |
Seeded admin email. |
admin_password |
tududi_change_me |
Seeded admin password — change this. |
session_secret |
placeholder | Session signing secret — change this. |
allowed_origins |
http://localhost:3002 |
Public URL(s) allowed to call the API. |
trust_proxy |
true |
Trust X-Forwarded-* (behind Traefik). |
db_volume |
tududi_db |
Volume for the SQLite database (/app/db). |
resources |
300 MHz / 384 MB | CPU and memory for the task. |
A prestart init task makes the volumes writable by the app user. The database,
uploads and backups persist across restarts. Set allowed_origins to the exact
URL you serve Tududi from, or the browser's requests will be rejected.