How it works

Git push → running container, on your hardware

WatchTower deploys apps to servers you own over SSH. No proxies, no third-party build cloud, no per-deploy fees. Here's the full lifecycle.

The deploy pipeline

1 / 6

Trigger

GitHub webhook, manual button, scheduled cron, or CLI. WatchTower verifies the HMAC signature, dedupes replays, and enqueues a build.

2 / 6

Queue

If Redis is configured the build goes to RQ; otherwise it falls back to in-process BackgroundTasks. Builds survive API restarts on the Redis path.

3 / 6

Build

Worker clones the repo into $WATCHTOWER_BUILD_DIR, runs the build command, and packages the artifact. Build env detected via Nixpacks or your custom command.

4 / 6

Ship

rsync over SSH (via fabric) to each OrgNode registered to the project. SSH keys are encrypted at rest with Fernet.

5 / 6

Reload

Each node runs its configured reload command — systemd unit restart, container swap, nginx reload, whatever you set. WatchTower waits for healthy.

6 / 6

Observe

Deployment.status updates live in the dashboard. Failed deploys auto-rollback if the previous artifact is still on the node.

The whole pipeline runs on the WatchTower process you control — no third party sees your code, your env vars, or your build artifacts. Same threat model as running CI locally.

What's inside the WatchTower process

One FastAPI app at watchtower.api:app. Same process serves the React SPA from web/dist in production — no separate frontend server.

API + auth

Routers under /api/*. Auth via signed user-session token (GitHub OAuth or Device Flow) or a static WATCHTOWER_API_TOKEN for CI.

Database

SQLite by default at ~/.watchtower/watchtower.db. Postgres-capable via DATABASE_URL. Migrations are Alembic-driven.

Build queue

RQ + Redis when configured; FastAPI BackgroundTasks otherwise. Probe failure cached for the process lifetime so a missing broker doesn't add 2s to every webhook.

Builder

Clone → build → package → rsync → SSH reload. Per-deploy isolation in $WATCHTOWER_BUILD_DIR. Audit log records every mutation.

Container watcher

Optional watchtower start daemon polls running Podman containers, pulls newer images, and restarts on a schedule. Configured via config/watchtower.yml.

LLM agent

Provider-agnostic — speaks any OpenAI-compatible endpoint (Ollama, LM Studio, OpenAI, OpenRouter). Tools run under the caller's identity, no privilege escalation.

Your machines form a private mesh

Every computer you add — a laptop, a Mac mini, a Raspberry Pi, even a cloud box — joins one encrypted Tailscale network. They all see each other; the internet sees nothing until you say so.

encrypted tailscale mesh GitHub your repo push Your laptop runs WatchTower site live here Mac mini database node Cloud box optional standby Raspberry Pi backups Internet via Cloudflare, opt-in
Deploy to any of them from one dashboard. Reach them from anywhere — coffee shop, phone, office — with nothing port-forwarded and nothing exposed. Public is a choice you make per project, not a default you have to defend.

Try it in 60 seconds

# 1. clone and run
git clone https://github.com/sinhaankur/WatchTower.git
cd WatchTower
./run.sh

# 2. open http://127.0.0.1:8000

# 3. (optional) connect a remote host via SSH from the dashboard,
#    register a project, push to its git repo to trigger a deploy