Lightweight shared todo lists — no accounts, just shareable links. The host manages lists behind a container password; participants collaborate via unguessable URLs.
- Host-only management (password set via
HOST_PASSWORDwhen starting the container) - Multiple simple lists — each item is a string that can be checked off
- Share links with ~256-bit unguessable tokens (
/l/{token}) - Optional per-list password on share links
- Export / import all lists as JSON (includes share tokens, items, and list passwords)
- SQLite backend, Docker-ready for Caddy reverse proxy
- Mobile-first UI with Pico CSS
- Installable as a home-screen web app on Android and iOS (PWA manifest + service worker)
Local Compose loads settings from a .env file in the project root (env_file in docker-compose.local.yml). Create it once before starting the container:
cp .env.example .envFor local development, set at least:
SECRET_KEY=dev-local-secret-change-me
HOST_PASSWORD=dev-host-password
APP_BASE_URL=http://localhost:8080
SL_ALLOW_DEV_SECRET=1SL_ALLOW_DEV_SECRET=1 is required when using placeholder secrets locally — the app refuses to start with known dev defaults otherwise. Share links use APP_BASE_URL, so keep it aligned with the port you open in the browser.
The SQLite database is stored in ./data (mounted into the container). Your lists persist across restarts.
Matches the production container setup on port 8080:
mkdir -p data
podman compose -f docker-compose.local.yml up -d --buildOpen http://localhost:8080 and log in with the HOST_PASSWORD from your .env.
After code or template changes, rebuild and recreate the container (a plain restart reuses the old image):
podman compose -f docker-compose.local.yml up -d --build --force-recreateStop the stack:
podman compose -f docker-compose.local.yml downIf the UI looks stale after a deploy, hard-refresh the browser (Ctrl+Shift+R) so the service worker and static assets reload.
docker compose works the same way if you use Docker instead of Podman.
Requires uv. Useful for quick Python debugging on port 5000:
uv sync
mkdir -p data
cp .env.example .env # then edit as above; use APP_BASE_URL=http://127.0.0.1:5000
set -a && source .env && set +a
uv run flask --app wsgi run --debugDesigned to run behind a caddy_reverse stack on the external Docker network caddy_net.
sudo deploy-app simple-lists
cd /opt/stacks/simple-lists
cp .env.example .env # set SECRET_KEY, HOST_PASSWORD, and APP_BASE_URL
docker compose up -d --buildEnsure .env contains:
SECRET_KEY=<long-random-string>
HOST_PASSWORD=<your-host-password>
APP_BASE_URL=https://simple-lists.<your-apps-domain>simple-lists.${APPS_DOMAIN} {
reverse_proxy simple-lists:8080
}No basic auth on Caddy — participants must reach share links without a gate password. The host area is protected by HOST_PASSWORD; share links rely on unguessable tokens (and optional per-list passwords).
Ensure simple-lists is covered by your wildcard *.${APPS_DOMAIN} record.
| Link | Path | Purpose |
|---|---|---|
| Landing | / |
Private-app info and owner login |
| Host dashboard | /host/ |
Manage all lists (after login) |
| Share link | /l/{share_token} |
Collaborate on a list |
Keep share links private — they are the primary access control for participants.
{
"version": 1,
"exported_at": "2026-07-04T12:00:00+00:00",
"lists": [
{
"title": "Groceries",
"share_token": "...",
"locked": true,
"password": "optional-list-password",
"items": [
{ "text": "Milk", "completed": false }
]
}
]
}Copy .env.example to .env for local development. Compose (docker-compose.local.yml) reads this file automatically; production deployment uses the same pattern.
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
(required in prod) | Flask secret for sessions/CSRF |
HOST_PASSWORD |
(required in prod) | Password for host management area |
APP_BASE_URL |
request root | Public base URL for share links |
DATABASE_URL |
./data/simple_lists.db |
SQLite connection string |
BEHIND_PROXY |
1 |
Enable ProxyFix for Caddy |
SL_ALLOW_DEV_SECRET |
unset | Set to 1 for local dev with placeholder secrets |
Production requires a strong SECRET_KEY and HOST_PASSWORD. The app refuses to start with known dev defaults unless SL_ALLOW_DEV_SECRET=1.
MIT