feat: Docker deployment for Synology NAS (Container Station)#3
feat: Docker deployment for Synology NAS (Container Station)#3jcodling wants to merge 17 commits into
Conversation
New files: - Dockerfile: Alpine-based Bun image, non-root user, one-shot CMD - docker-compose.yml: Alternative Compose spec reference - .dockerignore: Excludes node_modules, .env, logs, reports from build context - scripts/deploy-nas.sh: One-command deploy — builds image locally, transfers to NAS via SCP, loads image, starts container with volumes - DOCKER-DEPLOY.md: Full deployment guide for Container Station Modified: - scripts/run.sh: Detects container env, skips caffeine/pmset in container - README.md: Added Deployment options section, updated stack/scheduler, added NAS deployment instructions in Setup and Automation sections
Whitespace cleanup: - Remove trailing whitespace from README.md, DOCKER-DEPLOY.md, and scripts - Remove tabs from scripts - Fix run.sh empty line trailing whitespace Deploy script fixes: - Replace broken double-quoted SSH block with heredoc (was passing literal backslashes to docker due to bash quoting rules) - Align helper function definitions - Replace emoji with text for terminal compatibility
…int-fix.sh utility - Replace broken double-quoted SSH block with proper heredoc in deploy-nas.sh so bash variable expansion works correctly - Add scripts/lint-fix.sh — runs trailing-whitespace cleanup across all tracked files. Use: ./scripts/lint-fix.sh --dry-run to preview ./scripts/lint-fix.sh to apply fixes
Code ReviewOverviewAdds a self-contained Docker deployment path for running the daily report pipeline on a Synology NAS, so it no longer depends on macOS + launchd. Includes Dockerfile, compose spec, a one-shot deploy script, documentation, and developer tooling (editorconfig, pre-commit hook, lint-fix script). BugsCritical — NAS_PASS="${NAS_PASS:""}" # BUG: should be "${NAS_PASS:-}"
Medium — scp "$IMAGE_FILE" "${NAS_USER}@${NAS_IP}:/tmp/" # no sshpass!Every other SSH command uses Medium — env-file check runs locally, not on NAS ( $(if [ -f /volume1${DOCKER_DIR}/.env ]; then echo '--env-file ...'; fi) \This command substitution inside the heredoc is evaluated on the Mac before the SSH session, so it checks Medium — sshpass -p "$NAS_PASS" scp ... "$PROJECT_DIR/.env" "${NAS_USER}@${NAS_IP}:${DOCKER_DIR}/.env"
Lower-priority issues
if grep -Pn '\s+$' "$file" >/dev/null 2>&1; thenmacOS ships BSD grep, which doesn't support
docker exec dailyreport_daily ls /app/reports/
docker exec dailyreport_daily cat /app/reports/2025-05-08.mdSince the container runs for ~5 seconds and exits, Named volume vs bind mount inconsistency ( The compose file uses a named Docker volume (
RUN bun install --productionConsider adding What works well
SummaryThree medium bugs need fixing before merge: the |
Replace ${NAS_PASS:""} with ${NAS_PASS:-} so that an unset
NAS_PASS is caught by the existing error check rather than
causing an immediate unbound-variable crash under set -u.
The scp call that transfers the built image to the NAS was missing sshpass, causing an interactive password prompt that breaks automation. Consistent with all other SSH/scp calls in the script.
…onal The $(if [ -f ... ]) was evaluated on the Mac, not on the NAS, so the --env-file flag was never passed and the container started without environment variables. Replaced with a direct --env-file flag using the consistent DOCKER_DIR path.
The scp destination was ${DOCKER_DIR}/.env which resolves to
/docker/dailyreport/.env — missing the /volume1 prefix that all
other NAS paths use. This meant the .env land'd one directory
too high for the container's volume mount to find it.
macOS ships BSD grep which doesn't support -P (Perl regex). Replace with grep -En and [[:space:]] character class which works on both macOS and Linux.
…ndard flag Replace fragile positional $1/$2 flag parsing with a for/case loop so --dry-run and --all can be passed in any order and combined. Also fix --no-exclude-standard (invalid git option) to --exclude-standard (the correct flag). Fixes pre-existing bug that made --all silently do nothing on both macOS and Linux.
…tainer docker exec fails on exited containers since the container runs ~5s then exits. Replace both the 'View generated reports' and 'Cannot reach SFTP host' sections with direct NAS filesystem/curl commands.
Named volumes aren't accessible from Synology File Station. This note clarifies that NAS deployments use bind mounts instead.
Prevents silent dependency drift between deploys by ensuring the image reproduces exactly from bun.lock.
Review fixes appliedCritical
Medium
Low
All fixes verified with |
|
Claude Code — Sonnet 4.6 reviewing the PR code changes Code Review — fix commitsCritical — 1 remaining bug
The echo " docker exec ${CONTAINER_NAME} ls /app/reports/" # line 165
echo " ssh ${NAS_USER}@${NAS_IP} ls /volume1${DOCKER_DIR}/reports/"Medium —
|
- deploy-nas.sh: replace docker exec with ssh in next-steps output (container exits after run) - lint-fix.sh: add --others to --all flag so it actually scans untracked files - lint-fix.sh: exit on unknown option to prevent silent fallback to default behavior - pre-commit.hook: replace git grep -P with -F for macOS portability
|
I'm Pi Agent running qwen3.6. The following is the result of a full automated review of the changes in this branch: 1. Medium Issues
2. Nice-to-haves
3. Security Observations
Positive Findings
Verdict: The branch is ready to merge once the missing |
|
I'm Pi Agent running qwen3.6. Here is a summary of the most recent changes applied to this branch:
All changes have passed the pre-commit hooks and bash syntax validation. |
|
Claude Code — Sonnet 4.6 reviewing the PR code changes Code Review — followup roundAll previous bugs confirmed fixed
New issuesMedium — command -v sshpass || warn "sshpass not found — run: brew install sshpass"
command -v rsync || warn "rsync not found — run: brew install rsync"
Medium — if [ "$SCAN_ALL" = "1" ]; then
files=$(git ls-files -z --others --exclude-standard 2>/dev/null | tr '\0' '\n')
else
files=$(git ls-files -z 2>/dev/null | tr '\0' '\n')Header says if [ "$SCAN_ALL" = "1" ]; then
files=$(
git ls-files -z 2>/dev/null
git ls-files -z --others --exclude-standard 2>/dev/null
) | tr '\0' '\n'
elseLow — The [ -f "$PROJECT_DIR/.env" ] || error "No .env file found at $PROJECT_DIR/.env — create it before deploying"Low — ssh admin@192.168.1.100
docker stop dailyreport_daily
docker rm dailyreport_daily
docker rmi dailyreport:latest
./scripts/deploy-nas.shThe # On NAS:
ssh admin@192.168.1.100 "docker stop dailyreport_daily; docker rm dailyreport_daily; docker rmi dailyreport:latest"
# On Mac:
./scripts/deploy-nas.shThe same pattern appears in Pre-existing — Not introduced by this PR. SummaryFour actionable items. The |
What
Add a self-contained Docker deployment path for running the daily report pipeline on a Synology NAS via Container Station — no longer dependent on macOS and launchd.
Why
The NAS is already always-on and has the power to run the pipeline reliably at 3 AM without waking a Mac. This moves the scheduler from a personal Mac (which goes to sleep, loses power, etc.) to a dedicated always-on device.
Changes
New files
Dockerfile— Alpine-based Bun image (~80 MB), runs the pipeline as a one-shot container (bun run generate), uses non-rootappuserdocker-compose.yml— Compose spec for local dev; deploy script handles NAS provisioning directly.dockerignore— Excludesnode_modules,.env,logs/,reports/from build contextscripts/deploy-nas.sh— One-command deployment: builds image locally, SCPs the tar to NAS, loads it, starts the containerDOCKER-DEPLOY.md— Full guide: CLI deploy, GUI fallback, scheduling, troubleshooting.editorconfig— Enforces spaces, LF, trailing whitespace removal across editorsscripts/pre-commit.hook+scripts/lint-fix.sh— Pre-commit hook blocks trailing whitespace + bash syntax errorsModified files
README.md— Added NAS deployment section, updated stack/scheduler to list both optionsscripts/run.sh— Detects Docker env; skipscaffeinate/pmsetwhen inside containerHow it works
deploy-nas.shbuilds the Docker image locally on your MacNAS Deployment
Linting
Pre-commit hook blocks trailing whitespace and bash syntax errors. Run
./scripts/lint-fix.shto fix whitespace, then commit.