From ae6735b05c6b7711968bd41156743668006e0985 Mon Sep 17 00:00:00 2001 From: "alexei.dolgolyov" Date: Thu, 26 Mar 2026 11:20:10 +0300 Subject: [PATCH] docs: add implementation plan --- PLAN.md | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 PLAN.md diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..af7d1bb --- /dev/null +++ b/PLAN.md @@ -0,0 +1,256 @@ +# Docker Watcher — Implementation Plan + +## Overview + +A self-hosted tool that automates Docker container deployment with Nginx Proxy Manager integration. Detects new images from Gitea/GitHub registries, deploys containers, and configures reverse proxy routing — all from a web dashboard. + +## Architecture + +``` +Gitea CI → pushes image → Registry + ↓ + Docker Watcher (Go) + ├── Webhook listener (instant) + └── Registry poller (fallback) + ↓ + Match tag → project + stage + ↓ + ┌── auto_deploy? ──┐ + ↓ ↓ + Deploy now Notify, wait for + manual trigger (UI) + ↓ ↓ + Pull image + Stop old container + Start new container on shared network + ↓ + NPM API: create/update proxy host + → stage-dev-project.example.com + ↓ + Health check + → success: done, notify + → failure: rollback, alert +``` + +## Decisions + +| Decision | Choice | Rationale | +|----------|--------|-----------| +| Language | Go | Single binary, excellent Docker SDK, low resource usage | +| Web UI | SvelteKit (embedded in Go binary) | User's existing stack, lightweight | +| Reverse proxy | Nginx Proxy Manager | Already deployed, API available | +| Routing | Subdomain-based | No sub-path issues with SPAs | +| Image detection | Webhook + polling | Webhook for speed, polling as fallback | +| Config | YAML | Version-controlled, easy to edit | +| Deployment target | Same TrueNAS host | Docker socket mounted | + +## Subdomain Convention + +| Stage | Pattern | Example | +|-------|---------|---------| +| Dev | `stage-dev-{project}.{domain}` | `stage-dev-web-app-launcher.dolgolyov-family.by` | +| Release | `stage-rel-{project}.{domain}` | `stage-rel-web-app-launcher.dolgolyov-family.by` | +| Production | `{custom}.{domain}` | `launcher.dolgolyov-family.by` | + +## Config Format + +```yaml +global: + domain: dolgolyov-family.by + network: staging-net + npm: + url: http://npm:81 + email: docker-watcher@dolgolyov-family.by + password_env: NPM_PASSWORD + subdomain_pattern: "stage-{stage}-{project}" + notification_url: https://notify.dolgolyov-family.by/webhook + +registries: + gitea: + url: https://git.dolgolyov-family.by + type: gitea + token_env: GITEA_TOKEN + # github: + # url: https://ghcr.io + # type: github + # token_env: GITHUB_TOKEN + +projects: + web-app-launcher: + registry: gitea + image: gitea.dolgolyov-family.by/alexei/web-app-launcher + port: 3000 + healthcheck: /api/health + env_file: ./envs/web-app-launcher.env + volumes: [] + stages: + dev: + tag_pattern: "dev-*" + auto_deploy: true + rel: + tag_pattern: "v*" + auto_deploy: false + prod: + tag_pattern: "v*" + auto_deploy: false + confirm: true + promote_from: rel + subdomain: launcher # overrides pattern → launcher.dolgolyov-family.by +``` + +## Project Structure + +``` +docker-watcher/ +├── cmd/ +│ └── server/ +│ └── main.go # Entry point +├── internal/ +│ ├── config/ +│ │ ├── config.go # YAML parsing, validation +│ │ └── config_test.go +│ ├── docker/ +│ │ ├── client.go # Docker Engine API wrapper +│ │ ├── container.go # Create, start, stop, remove +│ │ └── client_test.go +│ ├── npm/ +│ │ ├── client.go # NPM API client (auth, CRUD proxy hosts) +│ │ └── client_test.go +│ ├── registry/ +│ │ ├── registry.go # Interface +│ │ ├── gitea.go # Gitea registry implementation +│ │ ├── github.go # GitHub Container Registry (future) +│ │ ├── poller.go # Periodic tag polling +│ │ └── registry_test.go +│ ├── deployer/ +│ │ ├── deployer.go # Orchestrates full deploy flow +│ │ ├── rollback.go # Rollback on failure +│ │ └── deployer_test.go +│ ├── health/ +│ │ ├── checker.go # HTTP health checks with retries +│ │ └── checker_test.go +│ ├── notify/ +│ │ ├── notifier.go # Webhook notifications +│ │ └── notifier_test.go +│ ├── webhook/ +│ │ ├── handler.go # Receive push events from Gitea/GitHub +│ │ └── handler_test.go +│ ├── api/ +│ │ ├── router.go # HTTP API for web UI +│ │ ├── projects.go # Project endpoints +│ │ ├── deploys.go # Deploy endpoints +│ │ └── middleware.go # Auth, logging, CORS +│ └── store/ +│ ├── store.go # Deploy history, state (SQLite) +│ └── store_test.go +├── web/ # SvelteKit frontend +│ ├── src/ +│ │ ├── routes/ +│ │ │ ├── +page.svelte # Dashboard +│ │ │ ├── projects/ +│ │ │ │ └── [id]/ +│ │ │ │ └── +page.svelte # Project detail + deploy controls +│ │ │ └── settings/ +│ │ │ └── +page.svelte # Config view +│ │ ├── lib/ +│ │ │ ├── api.ts # API client +│ │ │ └── types.ts # Shared types +│ │ └── app.html +│ ├── package.json +│ ├── svelte.config.js +│ └── vite.config.ts +├── docker-watcher.example.yaml +├── Dockerfile +├── docker-compose.yml +├── go.mod +└── go.sum +``` + +## Implementation Phases + +### Phase 1: Foundation + +Core infrastructure — config, Docker client, NPM client. + +1. **Go project init** — go.mod, directory structure, dependencies +2. **Config loader** — parse YAML, validate, resolve env vars +3. **Docker client** — connect to socket, pull image, list/start/stop/remove containers, manage networks +4. **NPM client** — authenticate (JWT), create/update/delete proxy hosts, list existing hosts +5. **Store** — SQLite for deploy history and state tracking + +### Phase 2: Detection & Deployment + +The core loop — detecting new images and deploying them. + +6. **Registry client** — Gitea registry API: list tags, compare with last known +7. **Poller** — periodic check for new tags matching configured patterns +8. **Webhook handler** — HTTP endpoint receiving push events from Gitea CI +9. **Deployer** — orchestrate: pull → stop old → start new → NPM update → health check +10. **Health checker** — HTTP GET with retries and timeout +11. **Rollback** — on health check failure: stop new, restart old, revert NPM +12. **Notifications** — send webhook on deploy success/failure + +### Phase 3: Web UI + +Dashboard for visibility and manual control. + +13. **API layer** — REST endpoints for projects, stages, deploys, manual trigger +14. **SvelteKit app** — dashboard, project detail, deploy log, manual deploy button +15. **Embed in Go** — build SvelteKit to static, embed with `go:embed`, serve from Go +16. **Real-time updates** — SSE or WebSocket for deploy progress + +### Phase 4: Hardening + +17. **Blue-green deploys** — start new, health check, swap, stop old (zero downtime) +18. **Promote flow** — enforce `promote_from` for production deploys +19. **Auth on dashboard** — basic auth or token-based +20. **Graceful shutdown** — drain in-progress deploys on SIGTERM +21. **Structured logging** — JSON logs with deploy context + +## Key Dependencies (Go) + +- `github.com/docker/docker` — Docker Engine API +- `github.com/go-chi/chi` or `net/http` — HTTP routing +- `gopkg.in/yaml.v3` — YAML config +- `github.com/mattn/go-sqlite3` or `modernc.org/sqlite` — SQLite (CGo-free) +- `github.com/robfig/cron` — Polling scheduler + +## Docker Compose (self-deployment) + +```yaml +services: + docker-watcher: + image: docker-watcher:latest + container_name: docker-watcher + restart: unless-stopped + ports: + - "8080:8080" + volumes: + - /var/run/docker.sock:/var/run/docker.sock + - ./docker-watcher.yaml:/app/config.yaml:ro + - ./envs:/app/envs:ro + - ./data:/app/data # SQLite DB + environment: + - NPM_PASSWORD=${NPM_PASSWORD} + - GITEA_TOKEN=${GITEA_TOKEN} + networks: + - staging-net + +networks: + staging-net: + external: true +``` + +## API Endpoints (Phase 3) + +``` +GET /api/projects — list all projects with current status +GET /api/projects/:id — project detail + stage statuses +GET /api/projects/:id/stages/:stage — stage detail + deploy history +POST /api/projects/:id/stages/:stage/deploy — trigger manual deploy +POST /api/projects/:id/stages/:stage/rollback — rollback to previous +GET /api/deploys — recent deploys across all projects +GET /api/deploys/:id/logs — deploy log stream (SSE) +POST /api/webhook/gitea — Gitea push event receiver +POST /api/webhook/github — GitHub push event receiver (future) +```