9.3 KiB
9.3 KiB
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
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.
- Go project init — go.mod, directory structure, dependencies
- Config loader — parse YAML, validate, resolve env vars
- Docker client — connect to socket, pull image, list/start/stop/remove containers, manage networks
- NPM client — authenticate (JWT), create/update/delete proxy hosts, list existing hosts
- Store — SQLite for deploy history and state tracking
Phase 2: Detection & Deployment
The core loop — detecting new images and deploying them.
- Registry client — Gitea registry API: list tags, compare with last known
- Poller — periodic check for new tags matching configured patterns
- Webhook handler — HTTP endpoint receiving push events from Gitea CI
- Deployer — orchestrate: pull → stop old → start new → NPM update → health check
- Health checker — HTTP GET with retries and timeout
- Rollback — on health check failure: stop new, restart old, revert NPM
- Notifications — send webhook on deploy success/failure
Phase 3: Web UI
Dashboard for visibility and manual control.
- API layer — REST endpoints for projects, stages, deploys, manual trigger
- SvelteKit app — dashboard, project detail, deploy log, manual deploy button
- Embed in Go — build SvelteKit to static, embed with
go:embed, serve from Go - Real-time updates — SSE or WebSocket for deploy progress
Phase 4: Hardening
- Blue-green deploys — start new, health check, swap, stop old (zero downtime)
- Promote flow — enforce
promote_fromfor production deploys - Auth on dashboard — basic auth or token-based
- Graceful shutdown — drain in-progress deploys on SIGTERM
- Structured logging — JSON logs with deploy context
Key Dependencies (Go)
github.com/docker/docker— Docker Engine APIgithub.com/go-chi/chiornet/http— HTTP routinggopkg.in/yaml.v3— YAML configgithub.com/mattn/go-sqlite3ormodernc.org/sqlite— SQLite (CGo-free)github.com/robfig/cron— Polling scheduler
Docker Compose (self-deployment)
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)