Internal documentation

Recurring Checklist

Deployment & maintenance guide — self-hosted web app on Synology DSM.

Contents
  1. Overview
  2. Environment summary
  3. Initial deployment
  4. Pushing app updates
  5. Backups
  6. DDNS, reverse proxy & HTTPS
  7. Troubleshooting
  8. Reference

1. Overview

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.

Architecture

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

2. Environment summary

ItemValue
App URL (external)https://check.watersmedia.org
App URL (LAN, direct)http://10.1.1.1:3000
Public TLS hostBoba-Fett (10.1.1.4), DSM reverse proxy + Let's Encrypt cert
Container hostChronicles-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
BackupsHyper Backup → Google Drive (recurring-checklist_server-backup/Chronicles-NA_1), daily 23:30
Email digest scheduleSunday 15:00 America/New_York
Email recipientmedia@waterschurch.org
SMTP relaysmtp.gmail.com:587 (Google Workspace app password)
Admin passwordSet in .env as ADMIN_PASSWORD

3. Initial deployment

The container was created using the following procedure. Use this section as the disaster-recovery / rebuild reference.

3.1 Prerequisites

3.2 Upload the bundle

  1. Open File Station on the NAS.
  2. Inside /volume1/docker/, create a folder named recurring-checklist.
  3. Upload checklist-deploy.tgz into it.
  4. Right-click → Extract → Extract here. Repeat once on the resulting .tar (Synology's File Station unwraps in two passes).
  5. Delete the .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

3.3 Configure environment

The deployment ships with a working .env file. Verify the following keys are populated:

VariablePurpose
ADMIN_PASSWORDRequired for any list-management operation in the UI.
REPORT_EMAILWhere the Sunday digest is delivered.
REPORT_CRONcron schedule for the digest. Default 0 15 * * 0 (Sun 3:00 PM).
REPORT_TZIANA timezone for the cron (default America/New_York).
RESET_AFTER_DIGESTtrue = clear all checks once the digest is sent. false to disable.
SMTP_HOSTsmtp.gmail.com
SMTP_PORT587
SMTP_USERGmail / Google Workspace address that sends the digest.
SMTP_PASS16-character app password from myaccount.google.com/apppasswords.
SMTP_FROMFriendly From: header, e.g. Recurring Checklist <media@waterschurch.org>
Security note The .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.

3.4 Create the Container Manager project

  1. Open Container Manager → Project → Create.
  2. Project name: recurring-checklist
  3. Path: browse to /volume1/docker/recurring-checklist → Select.
  4. Source: "Use existing docker-compose.yml" (auto-selected). Click OK.
  5. On the Web portal settings screen, leave the checkbox unchecked. Click Next.
  6. Confirm the summary and click Done.

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.

3.5 Verify

  1. From a browser on the LAN, open http://10.1.1.1:3000. The "Who are you?" prompt should appear.
  2. Click Admin (top right) and authenticate with the value of ADMIN_PASSWORD.
  3. Use Send Digest Now in the admin drawer to verify SMTP connectivity. A test email should arrive within a few seconds.

4. Pushing app updates

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.

4.1 Receive the update

The developer produces a fresh checklist-deploy.tgz from the latest project tree. It will arrive as a single file (currently ~35 KB).

4.2 Apply on the NAS

  1. Open File Station → navigate to /volume1/docker/recurring-checklist/.
  2. Upload the new checklist-deploy.tgz into this folder.
  3. Right-click → Extract → Extract here. Choose Overwrite when prompted (this replaces server.js, the public/ folder, etc.; it does not touch data/).
  4. Repeat the extract on the resulting .tar.
  5. Delete the .tgz, .tar, and PaxHeader when done.

4.3 Rebuild the container

  1. Open Container Manager → Project → click the recurring-checklist row.
  2. Click Action → Build (rebuilds the image from the updated source).
  3. Wait for the build log to finish with Exit Code: 0.
  4. Click Action → Stop, then Action → Start if the build did not auto-restart.
What gets preserved across updates Everything in ./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.

4.4 Rollback

If an update misbehaves:

  1. Recover the previous checklist-deploy.tgz (kept by the developer) or restore from Hyper Backup.
  2. Replace the files in /volume1/docker/recurring-checklist/ as in §4.2.
  3. Rebuild the project as in §4.3.

5. Backups

5.1 What's backed up

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.

5.2 Restoring

To restore the data file only:

  1. Open Hyper Backup → Recurring Checklist task → Restore → Data.
  2. Pick a version. Browse into recurring-checklist/data/ and restore checklists.json.
  3. Container Manager → Project → Stop, then Start the recurring-checklist project to pick up the restored file.

5.3 Backup verification

An integrity check runs weekly on Saturday at 01:30. Failed integrity checks generate a DSM notification.

6. DDNS, reverse proxy & HTTPS

6.1 DDNS record

6.2 Reverse proxy rule (on Boba-Fett, 10.1.1.4)

Located at Control Panel → Login Portal → Advanced → Reverse Proxy → "Recurring Checklist".

SideProtocolHostnamePort
SourceHTTPScheck.watersmedia.org443
DestinationHTTP10.1.1.13000

6.3 TLS certificate

6.4 Router (UniFi)

Existing 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.

7. Troubleshooting

SymptomLikely 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.

8. Reference

8.1 Files in /volume1/docker/recurring-checklist/

PathPurpose
server.jsExpress app + scheduler + email logic.
public/Static front-end (HTML, CSS, JS).
DockerfileImage definition (node:20-alpine).
docker-compose.ymlContainer Manager project definition.
package.json / package-lock.jsonnpm manifest, locked dependency versions.
.envRuntime config (passwords, SMTP, schedule). Not in version control.
.env.exampleTemplate for a fresh deploy.
data/checklists.jsonPersistent state. Volume-mounted; backed up nightly.

8.2 Useful container actions

ActionWhere
View live logsContainer Manager → Container → recurring-checklist → Details → Log
Restart containerContainer Manager → Project → recurring-checklist → Action → Stop, then Start
Rebuild image after updateContainer Manager → Project → recurring-checklist → Action → Build
Edit env varsEdit /volume1/docker/recurring-checklist/.env via File Station, then restart the project

8.3 Server endpoints (for diagnostics)

MethodPathPurpose
GET/api/stateReturns current lists, items, and per-location check map (read-only).
POST/api/admin/verifyValidates the admin password (used by the UI).
POST/api/admin/stateReplaces full state. Requires X-Admin-Password.
POST/api/admin/send-digestManually triggers the email digest. Requires admin.
POST/api/checkToggle 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.