Docker Compose¶
The recommended way to run Punchlist. All examples are in
examples/docker.
SQLite (the default)¶
One container, data in a named volume. Good for a household or a small team: SQLite handles many people comfortably.
# Punchlist with SQLite (the default): one container, data in a named volume.
# docker compose up -d
# docker compose logs punchlist # the first admin password is printed here, once
# Then open http://localhost:8080. Docs: docs/install/docker-compose.md
services:
punchlist:
image: registry.gitlab.com/killswitch10/punchlist:latest
container_name: punchlist
restart: unless-stopped
ports:
- '8080:8080'
volumes:
- punchlist-data:/data
# Attached files, apart from the database.
- punchlist-files:/files
environment:
# Months in stats and "today" on the server follow this time zone.
TZ: Etc/UTC
# The address people open, once it is behind a reverse proxy with HTTPS.
# PUBLIC_URL: https://tasks.example.com
# Choose the first admin password instead of reading it from the logs (12+ characters).
# ADMIN_PASSWORD_FILE: /run/secrets/admin_password
volumes:
punchlist-data:
punchlist-files:
Open http://localhost:8080. Read first start for what comes next.
Data in a folder instead of a volume
compose.bind-mount.yaml
keeps the data in ./data and attached files in ./files (point that one at bigger storage if
you like). Both folders must belong to uid 1654 (sudo chown 1654:1654 data files), or run the
container as your own user with user:.
With HTTPS (Caddy)¶
Caddy gets and renews a certificate by itself. Point a DNS name at the machine, open ports 80 and 443, and put the name in both files.
# Punchlist behind Caddy with automatic HTTPS (Let's Encrypt).
# Point a DNS name at this machine, open ports 80 and 443, put the name in Caddyfile and below, then:
# docker compose -f compose.caddy.yaml up -d
# Only Caddy is published; Punchlist is reachable on the compose network alone.
services:
punchlist:
image: registry.gitlab.com/killswitch10/punchlist:latest
container_name: punchlist
restart: unless-stopped
volumes:
- punchlist-data:/data
# Attached files, apart from the database.
- punchlist-files:/files
environment:
TZ: Etc/UTC
PUBLIC_URL: https://tasks.example.com
caddy:
image: caddy:2
restart: unless-stopped
ports:
- '80:80'
- '443:443'
- '443:443/udp'
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
- caddy-config:/config
volumes:
punchlist-data:
punchlist-files:
caddy-data:
caddy-config:
Behind Traefik instead: compose.traefik.yaml.
Other proxies: reverse proxy and HTTPS.
PostgreSQL¶
For people who already run PostgreSQL or want the database on its own server. Make the secrets first (the commands are at the top of the file).
# Punchlist with PostgreSQL 15+ instead of the built-in SQLite.
# Secrets come from files (every setting also works as NAME_FILE). Make them first:
# mkdir -p secrets
# openssl rand -hex 24 > secrets/db_password
# printf 'Host=db;Username=punchlist;Password=%s;Database=punchlist' "$(cat secrets/db_password)" > secrets/db_connection
# docker compose -f compose.postgres.yaml up -d
services:
punchlist:
image: registry.gitlab.com/killswitch10/punchlist:latest
container_name: punchlist
restart: unless-stopped
ports:
- '8080:8080'
environment:
TZ: Etc/UTC
DB_PROVIDER: postgres
DB_CONNECTION_STRING_FILE: /run/secrets/db_connection
secrets:
- db_connection
volumes:
# Snapshots, logs and audit archives still go to /data, attached files to /files.
- punchlist-data:/data
# Attached files, apart from the database.
- punchlist-files:/files
depends_on:
db:
condition: service_healthy
db:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_USER: punchlist
POSTGRES_DB: punchlist
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- db-data:/var/lib/postgresql/data
secrets:
- db_password
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'punchlist']
interval: 10s
retries: 5
secrets:
db_password:
file: secrets/db_password
db_connection:
file: secrets/db_connection
volumes:
punchlist-data:
punchlist-files:
db-data:
More in database, including moving an existing SQLite server to PostgreSQL.
Single sign-on¶
# Punchlist with single sign-on through an OpenID Connect provider (Pocket ID here; any works).
# Register this redirect URI with the provider: https://tasks.example.com/auth/oidc/pocket/callback
# Put the client secret in a file first: mkdir -p secrets && echo 'the-secret' > secrets/oidc_secret
# Docs: docs/guides/single-sign-on.md
services:
punchlist:
image: registry.gitlab.com/killswitch10/punchlist:latest
container_name: punchlist
restart: unless-stopped
ports:
- '8080:8080'
volumes:
- punchlist-data:/data
# Attached files, apart from the database.
- punchlist-files:/files
environment:
TZ: Etc/UTC
PUBLIC_URL: https://tasks.example.com
OIDC_PROVIDERS: pocket
OIDC_POCKET_AUTHORITY: https://id.example.com
OIDC_POCKET_CLIENT_ID: punchlist
OIDC_POCKET_CLIENT_SECRET_FILE: /run/secrets/oidc_secret
OIDC_POCKET_DISPLAY_NAME: Pocket ID
# Only members of this group may sign in; new ones get an account by themselves.
OIDC_POCKET_REQUIRED_GROUPS: punchlist
OIDC_POCKET_AUTO_CREATE: 'true'
# Members of punchlist-admins become admins at every sign-in.
OIDC_POCKET_ROLE_MAP: punchlist-admins=admin
# No passwords at all, in the app or the API (punchlist admin login-link gets an admin back in).
# LOCAL_LOGIN: disabled
secrets:
- oidc_secret
secrets:
oidc_secret:
file: secrets/oidc_secret
volumes:
punchlist-data:
punchlist-files:
Provider by provider: single sign-on.
Everyday commands¶
docker compose pull && docker compose up -d # upgrade (read the changelog first)
docker compose logs -f punchlist # follow the logs
docker compose exec punchlist punchlist --version
docker compose exec punchlist punchlist snapshot # a backup now
docker compose restart punchlist # after changing a setting that needs it
The Restart now button on the admin settings page stops the server; the restart: policy in
these files brings it back. Without one, the server stays stopped.