Run a demo server¶
A demo server is a public Punchlist anyone can try without installing anything: they sign up,
or sign in to one of the pre-made accounts, and every night the server forgets it all and starts
again from a seed. DEMO_MODE does the rest.
What demo mode does¶
- A banner, one line at the bottom: "Demo server: resets every night at 04:00, anyone can sign up". Demo accounts on it lists each pre-made account with its password. Visitors can hide it until they close the tab.
- Open sign-up, whatever
REGISTRATIONsays, limited toSIGNUP_RATE_LIMITaccounts per address an hour. - Deleted accounts go at once (
ACCOUNT_DELETE_GRACE_DAYS0) and files stay small (ATTACHMENT_MAX_MBat most 2). - Nothing leaves the server: no usage report, no release check, no Web Push (reminders still show on the device). Only an identity provider or log server you set up yourself is called.
- No search engines: every answer says
X-Robots-Tag: noindex, nofollow, and the page has a robots meta tag. - Demo accounts (
DEMO_ACCOUNTS) are put back at every start as plain users with the listed passwords. Wrong guesses lock nobody out, and nobody can change their passwords.
The demo settings are read from the environment only, so nobody turns them off from Admin > Settings. Keep the admin password to yourself: visitors never need an admin.
The seed¶
Every image carries a seed at /app/demo/seed.json, made by that version's own code: three
friends, Sam, Alex and Jordan (demo-sam, demo-alex, demo-jordan), with about 230 tasks in 13
lists, six of them shared with each other (a household, groceries, a trip, a garden, a book club,
a game night), with subtasks, tags, repeats, assignments, comments and a few things in Trash.
loads it into an empty database. --shift-dates moves every date
on by the days since the seed was made, so Today always has something in it. The seed has no
passwords: DEMO_ACCOUNTS gives the three accounts theirs when the server starts, so pick your
own there.
To make a seed yourself (another day, or after changing web/src/demo/seed.ts):
With Docker Compose¶
examples/demo has the
server and a reset service whose cron runs the nightly reset.
# A public demo server that resets every night (PL-117). Docs: docs/install/demo.md
# printf '%s' 'a long admin password' > admin_password.txt
# docker compose up -d && docker compose exec reset sh /demo/reset.sh # load the seed now
# Then open http://localhost:8080 and sign in as demo-sam / try-punchlist-sam.
name: punchlist-demo
services:
punchlist:
image: registry.gitlab.com/killswitch10/punchlist:latest
restart: unless-stopped
ports:
- '8080:8080'
volumes:
# Both are deleted and made anew at every reset: keep nothing here you want back.
- data:/data
- files:/files
secrets:
- admin_password
environment:
TZ: Etc/UTC
DEMO_MODE: 'true'
# The seed's three people, with the passwords the banner shows.
DEMO_ACCOUNTS: demo-sam:try-punchlist-sam,demo-alex:try-punchlist-alex,demo-jordan:try-punchlist-jordan
# As the banner says it; keep it the same as RESET_AT below.
DEMO_RESET_TIME: '04:00'
# The operator's own way in. Visitors never get an admin.
ADMIN_PASSWORD_FILE: /run/secrets/admin_password
# Behind a reverse proxy with HTTPS (the sign-up limit counts the real addresses):
# PUBLIC_URL: https://demo.example.com
# TRUSTED_PROXIES: 172.16.0.0/12
# The nightly reset: stop the server, make its volumes anew, load the seed, start it again.
# It drives Docker through the socket, which is as good as root on this host; run reset.sh from
# the host's own cron instead if you would rather not (see the docs).
reset:
image: docker:27.5.1-cli
restart: unless-stopped
# The same path as on the host, so the compose file it drives resolves as it does here.
working_dir: ${PWD}
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- .:${PWD}:ro
- ./reset.sh:/demo/reset.sh:ro
environment:
# Minute and hour (UTC) for cron.
RESET_AT: '0 4'
entrypoint:
- sh
- -c
- echo "$$RESET_AT * * * cd $$PWD && sh /demo/reset.sh" > /etc/crontabs/root && exec crond -f -l 6
volumes:
data:
files:
secrets:
admin_password:
file: ./admin_password.txt
#!/bin/sh
# The demo server's nightly reset (PL-117): fresh volumes, the seed this image was built with, a
# new start. Run from the folder with compose.yaml, by the reset service's cron or the host's own:
# 0 4 * * * cd /srv/punchlist-demo && sh reset.sh
set -eu
project=punchlist-demo
compose="docker compose -p $project"
echo "$(date -u +%FT%TZ) reset: stopping the server"
$compose stop punchlist
$compose rm -f punchlist
docker volume rm -f "${project}_data" "${project}_files" >/dev/null
# `run` makes the volumes again. --shift-dates moves the seed's tasks so they start from today.
echo "$(date -u +%FT%TZ) reset: loading the seed"
$compose run --rm --no-deps punchlist import /app/demo/seed.json --shift-dates
# On start the server gives the demo accounts their passwords from DEMO_ACCOUNTS.
$compose up -d punchlist
echo "$(date -u +%FT%TZ) reset: done"
mkdir punchlist-demo && cd punchlist-demo # with compose.yaml and reset.sh in it
printf '%s' 'a long admin password' > admin_password.txt
docker compose up -d
sh reset.sh # the first load; then every night at 04:00 UTC
Open http://localhost:8080: the banner is at the bottom, and demo-sam with
try-punchlist-sam signs you in to Sam's lists.
The reset stops the server, deletes the data and files volumes, makes them again with the
seed in, and starts the server. It takes a few seconds, and everyone signed in is signed out.
The reset service holds the Docker socket
With /var/run/docker.sock mounted, the reset container can do anything Docker can on this
host. On a host that runs other things, leave the reset service out and run the same script
from the host's cron instead:
Keep DEMO_RESET_TIME the same as the cron time
(RESET_AT in the compose file, or your crontab line), in the server's time zone (TZ).
In front of it¶
Put it behind a reverse proxy with HTTPS and set
PUBLIC_URL and
TRUSTED_PROXIES, so the sign-up limit counts
real addresses rather than the proxy's. A demo is open to anyone: give it its own machine or
network, apart from anything private.