# 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) ```