Deployment & maintenance guide — self-hosted web app on Synology DSM.
The Recurring Checklist is a small Node.js web application that lives in a Docker container on the Synology NAS. Multi-location operators (Woonsocket, North Attleboro, etc.) open it in a web browser, identify themselves once per device, and tick items off through the week. Every Sunday at 3:00 PM the server emails a digest of who completed what (and what was missed) to a single recipient, then resets all checkboxes for the following week.
Users (LAN or internet)
│
▼
https://check.watersmedia.org (Boba-Fett DSM, port 443)
│
│ reverse proxy
▼
http://10.1.1.1:3000 (Chronicles-NA DSM, Docker container)
│
▼
node:20-alpine + Express + node-cron + nodemailer
│
├── data volume → /volume1/docker/recurring-checklist/data/checklists.json
│ (backed up nightly to Google Drive via Hyper Backup)
│
└── Sunday 3:00 PM cron → Gmail SMTP → media@waterschurch.org
| Item | Value |
|---|---|
| App URL (external) | https://check.watersmedia.org |
| App URL (LAN, direct) | http://10.1.1.1:3000 |
| Public TLS host | Boba-Fett (10.1.1.4), DSM reverse proxy + Let's Encrypt cert |
| Container host | Chronicles-NA (10.1.1.1), Container Manager project recurring-checklist |
| Project folder on NAS | /volume1/docker/recurring-checklist/ |
| Persisted data file | /volume1/docker/recurring-checklist/data/checklists.json |
| Backups | Hyper Backup → Google Drive (recurring-checklist_server-backup/Chronicles-NA_1), daily 23:30 |
| Email digest schedule | Sunday 15:00 America/New_York |
| Email recipient | media@waterschurch.org |
| SMTP relay | smtp.gmail.com:587 (Google Workspace app password) |
| Admin password | Set in .env as ADMIN_PASSWORD |
The container was created using the following procedure. Use this section as the disaster-recovery / rebuild reference.
/volume1/docker/checklist-deploy.tgz (produced from the project's server/ directory)/volume1/docker/, create a folder named recurring-checklist.checklist-deploy.tgz into it..tar (Synology's
File Station unwraps in two passes)..tgz, .tar, and the PaxHeader folder that gets produced. The
folder should be left with:
server.js public/ Dockerfile
package.json data/ docker-compose.yml
package-lock.json README.md .env .env.example .dockerignore .gitignore
The deployment ships with a working .env file. Verify the following keys are populated:
| Variable | Purpose |
|---|---|
ADMIN_PASSWORD | Required for any list-management operation in the UI. |
REPORT_EMAIL | Where the Sunday digest is delivered. |
REPORT_CRON | cron schedule for the digest. Default 0 15 * * 0 (Sun 3:00 PM). |
REPORT_TZ | IANA timezone for the cron (default America/New_York). |
RESET_AFTER_DIGEST | true = clear all checks once the digest is sent. false to disable. |
SMTP_HOST | smtp.gmail.com |
SMTP_PORT | 587 |
SMTP_USER | Gmail / Google Workspace address that sends the digest. |
SMTP_PASS | 16-character app password from myaccount.google.com/apppasswords. |
SMTP_FROM | Friendly From: header, e.g. Recurring Checklist <media@waterschurch.org> |
.env file contains credentials. Do not place it in a publicly-shared folder. Hyper
Backup is encrypted in transit; client-side encryption is not enabled because it complicates restore.
recurring-checklist/volume1/docker/recurring-checklist → Select.Container Manager will build the image (~60–90 seconds on first run) and start the container. The
project should report Exit Code: 0 at the end of the build log and the project status indicator
should turn green.
http://10.1.1.1:3000. The "Who are you?" prompt should appear.ADMIN_PASSWORD.Updates are delivered as a refreshed deployment tarball. Application data is mounted from the host's
data/ directory, so rebuilding the image preserves all checklists, locations, and check states.
The developer produces a fresh checklist-deploy.tgz from the latest project tree. It will
arrive as a single file (currently ~35 KB).
/volume1/docker/recurring-checklist/.checklist-deploy.tgz into this folder.server.js, the public/ folder, etc.; it does not touch data/)..tar..tgz, .tar, and PaxHeader when done.recurring-checklist row.Exit Code: 0../data/ is volume-mounted. Lists, items, per-location check states, and
lastCheckDate values are persisted. Configuration in .env is also untouched by an update unless
the new tarball explicitly ships a replacement.
If an update misbehaves:
checklist-deploy.tgz (kept by the developer) or restore from Hyper Backup./volume1/docker/recurring-checklist/ as in §4.2.A Hyper Backup task named Recurring Checklist backs up the entire docker
shared folder nightly at 23:30 to Google Drive. Destination:
Google Drive: recurring-checklist_server-backup / Chronicles-NA_1
This captures both the application source (in recurring-checklist/) and its data folder.
To restore the data file only:
recurring-checklist/data/ and restore checklists.json.recurring-checklist project to pick up the restored file.An integrity check runs weekly on Saturday at 01:30. Failed integrity checks generate a DSM notification.
check.watersmedia.orgLocated at Control Panel → Login Portal → Advanced → Reverse Proxy → "Recurring Checklist".
| Side | Protocol | Hostname | Port |
|---|---|---|---|
| Source | HTTPS | check.watersmedia.org | 443 |
| Destination | HTTP | 10.1.1.1 | 3000 |
check.watersmedia.org service in Control Panel → Security → Certificate → SettingsExisting port-forward of TCP 443 to Boba-Fett (10.1.1.4) is the only inbound rule required. No new forwarding is needed for the Recurring Checklist app.
| Symptom | Likely cause & fix |
|---|---|
App returns 502 / Bad Gateway from check.watersmedia.org |
The Docker container on Chronicles-NA is stopped. Open Container Manager → Project → click
recurring-checklist → Action → Start. If it fails to start, open
the build log for errors (commonly a malformed .env). |
| Browser shows "Not Secure" on the public URL | Certificate either expired or not bound. Check Control Panel → Security → Certificate on Boba-Fett.
The check.watersmedia.org cert should be green and bound under
Settings. |
| Sunday email did not arrive | Open Container Manager → Container → recurring-checklist → Details → Log.
Look for lines beginning with [digest]. Common causes: expired Google App Password,
Google Workspace 2FA disabled on the sending account, outbound TCP 587 blocked by firewall. |
| "This list is reserved for X" error | Expected. The list is location-scoped; a user signed in as a different location cannot tick it off. Either change the user's location to match the list or remove the list's location to make it shared. |
| Checks reappear after a user signs out and back in | Per-location checks are stored on the server keyed by the user's typed name (case-insensitive, whitespace-collapsed). If a user enters a different name, they are treated as a different location and see different state. |
| Container won't build | Usually a stale or partial extraction. Re-upload checklist-deploy.tgz and extract
both .tgz and the intermediate .tar step. Confirm server.js and the public/ folder
are present at the project root. |
/volume1/docker/recurring-checklist/| Path | Purpose |
|---|---|
server.js | Express app + scheduler + email logic. |
public/ | Static front-end (HTML, CSS, JS). |
Dockerfile | Image definition (node:20-alpine). |
docker-compose.yml | Container Manager project definition. |
package.json / package-lock.json | npm manifest, locked dependency versions. |
.env | Runtime config (passwords, SMTP, schedule). Not in version control. |
.env.example | Template for a fresh deploy. |
data/checklists.json | Persistent state. Volume-mounted; backed up nightly. |
| Action | Where |
|---|---|
| View live logs | Container Manager → Container → recurring-checklist → Details → Log |
| Restart container | Container Manager → Project → recurring-checklist → Action → Stop, then Start |
| Rebuild image after update | Container Manager → Project → recurring-checklist → Action → Build |
| Edit env vars | Edit /volume1/docker/recurring-checklist/.env via File Station, then restart the project |
| Method | Path | Purpose |
|---|---|---|
| GET | /api/state | Returns current lists, items, and per-location check map (read-only). |
| POST | /api/admin/verify | Validates the admin password (used by the UI). |
| POST | /api/admin/state | Replaces full state. Requires X-Admin-Password. |
| POST | /api/admin/send-digest | Manually triggers the email digest. Requires admin. |
| POST | /api/check | Toggle a single item's checked state for a location. |
Document version 1 · Generated for the Recurring Checklist deployment on Chronicles-NA / Boba-Fett. Maintained alongside the project source.