Files
tiny-forge/PLAN.md
T

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.

  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.

  1. Registry client — Gitea registry API: list tags, compare with last known
  2. Poller — periodic check for new tags matching configured patterns
  3. Webhook handler — HTTP endpoint receiving push events from Gitea CI
  4. Deployer — orchestrate: pull → stop old → start new → NPM update → health check
  5. Health checker — HTTP GET with retries and timeout
  6. Rollback — on health check failure: stop new, restart old, revert NPM
  7. Notifications — send webhook on deploy success/failure

Phase 3: Web UI

Dashboard for visibility and manual control.

  1. API layer — REST endpoints for projects, stages, deploys, manual trigger
  2. SvelteKit app — dashboard, project detail, deploy log, manual deploy button
  3. Embed in Go — build SvelteKit to static, embed with go:embed, serve from Go
  4. Real-time updates — SSE or WebSocket for deploy progress

Phase 4: Hardening

  1. Blue-green deploys — start new, health check, swap, stop old (zero downtime)
  2. Promote flow — enforce promote_from for production deploys
  3. Auth on dashboard — basic auth or token-based
  4. Graceful shutdown — drain in-progress deploys on SIGTERM
  5. 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)

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)