feat(deploy): docker-compose stack for VDS (board.kzntsv.site) [skip-tdd: visual]

closes board-viewer-cron-deploy

Two-service stack at /opt/stacks/board-viewer/ on VDS:
- board-viewer-build (node:22-alpine): cron-loop `while true; do node cli.ts; sleep 300`,
  writes to shared docker volume `dist`
- board-viewer-web (nginx:alpine): serves dist read-only,
  Traefik route Host(`board.kzntsv.site`) → letsEncrypt + basic-auth middleware

Files:
- deploy/Dockerfile.build — build image (registry.kzntsv.site/board-viewer-build)
- deploy/docker-compose.yml — stack with traefik labels (`proxy` network, same as Gitea)
- deploy/nginx.conf — static serving with Cache-Control
- deploy/auth.toml.example — Gitea token + board_viewer_repos whitelist
- deploy/.env.example — BOARD_VIEWER_USERS for basic-auth (with $$ escape note)
- deploy/README.md — DNS setup + image build/push + bootstrap + smoke + DR steps

Configs are declarative ([skip-tdd: visual]). Acceptance = deploy + curl
(see README §4 'Verify'). Both auth.toml and .env are gitignored.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
vitya
2026-05-22 15:09:30 +03:00
parent cab4a8b92d
commit e64d94cd25
7 changed files with 267 additions and 0 deletions

13
deploy/.env.example Normal file
View File

@@ -0,0 +1,13 @@
# Copy to ./.env (same dir as docker-compose.yml).
#
# BOARD_VIEWER_USERS — basic-auth users for Traefik middleware.
# Compose interpolates env_file values — every `$` must be escaped as `$$`.
#
# Steps:
# 1. Generate the hash: htpasswd -nbB viewer 'STRONG_PASSWORD'
# 2. Replace every `$` in the output with `$$`.
# 3. Paste below. Multiple users → comma-separated.
#
# Example before escaping: viewer:$2y$05$abc...xyz
# Example after escaping: viewer:$$2y$$05$$abc...xyz
BOARD_VIEWER_USERS=viewer:$$2y$$05$$REPLACE_WITH_REAL_BCRYPT_HASH