An open API service indexing awesome lists of open source software.

https://github.com/pbuzdygan/contiwatch

Minimal Docker image watcher with simple containers management actions
https://github.com/pbuzdygan/contiwatch

container-management contiwatch docker-image image-container update-checker

Last synced: about 2 months ago
JSON representation

Minimal Docker image watcher with simple containers management actions

Awesome Lists containing this project

README

          

# Contiwatch


CONTIWATCH Banner

**Contiwatch** is minimal Docker image watcher inspired by Watchtower. Scans local containers, checks for new images, optionally recreates containers, and sends Discord webhook notifications.

## Features
- ✅ Scan local Docker daemon for running containers
- ✅ Remote agent support (token-authenticated)
- ✅ Pull image tags and detect updates
- ✅ Global and per-container policy (`contiwatch.policy` label)
- ✅ Optional update (recreate container) or notify-only
- ✅ Simple HTML UI for updates, servers, events, and settings
- ✅ Server maintenance mode to pause scans and updates per server
- ✅ Experimental containers management UI (opt-in)
- ✅ Experimental container shell (opt-in)
- ✅ Experimental container logs (opt-in)
- ✅ Discord webhook notifications

---
## Demo / Screenshots

### Main UI

## Policies
Set on containers via label:
- `contiwatch.policy=update`
- `contiwatch.policy=notify_only`
- `contiwatch.policy=skip`

Global default is `notify_only` unless changed in the UI or config.

Policy behavior:
- `notify_only`: pulls image metadata, detects updates, sends notification only (no container changes).
- `update`: pulls the image and recreates the container with the same config; it only starts the new container if it was running before. By default, non-running containers are skipped for updates (reported as `Skipped`); enable `update_stopped_containers` to update stopped containers but keep them stopped.
- `skip`: ignores the container entirely.

When using remote agents, the controller syncs `global_policy` to agents; per-container labels still override the global setting.
Status summary includes a Skipped metric for containers that were intentionally skipped.

## Run (container)
```bash
docker build -t contiwatch .

docker run -d \
--name contiwatch \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v contiwatch-data:/data \
contiwatch
```

Open `http://localhost:8080`.

For a production-like controller deployment, do not stop at the minimal example above. In practice you should enable at least:
- required controller PIN gate for the whole interactive UI/API session
- a non-default strong agent token for every remote agent

Example controller run with required PIN:
```bash
docker run -d \
--name contiwatch \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v contiwatch-data:/data \
-e APP_PIN="SET_A_UNIQUE_4_TO_8_DIGIT_PIN" \
contiwatch
```

## Agent mode (remote)
Run on a remote host with Docker socket access and a token:
```bash
docker run -d \
--name contiwatch-agent \
-p 8080:8080 \
-v /var/run/docker.sock:/var/run/docker.sock \
-v contiwatch-agent-data:/data \
-e CONTIWATCH_AGENT=true \
-e CONTIWATCH_AGENT_TOKEN="" \
contiwatch
```

## Container images (GHCR)
Main releases (multi-arch):
```bash
docker pull ghcr.io//:latest
docker pull ghcr.io//:
```

Dev releases (multi-arch):
```bash
docker pull ghcr.io//:dev_latest
docker pull ghcr.io//:dev_
```

## Environment
- `CONTIWATCH_ADDR` (default `:8080`)
- `CONTIWATCH_CONFIG` (default `/data/config.json`)
- `TZ` (optional; e.g. `Europe/Warsaw` for local timestamps)
- `APP_PIN` (required in controller mode; 4-8 digits)
- `CONTIWATCH_APP_PIN` (optional backward-compatibility fallback for old deployments; use `APP_PIN` in new setup)
- `CONTIWATCH_AGENT` (optional; set to `true` to run in agent mode)
- `CONTIWATCH_AGENT_TOKEN` (required in agent mode; bearer token for API access)
- `CONTIWATCH_REPO` (optional; GitHub repo in `owner/name` form for release links/checks)
- `CONTIWATCH_CHANNEL` (optional; `main` or `dev` for release checks)
- `CONTIWATCH_RELEASE_CHECK` (optional; set to `0`/`false` to disable release checks)
- `CONTIWATCH_GITHUB_TOKEN` (optional; token for private repos or higher GitHub API limits)

## Recommended security setup
For the controller instance, the enforced baseline is:
- set `APP_PIN` (controller will not start without it)
- keep the default config volume mounted on `/data`
- use a strong unique `CONTIWATCH_AGENT_TOKEN` on every remote agent

Example controller `docker-compose.yml` security block:

```yaml
environment:
CONTIWATCH_ADDR: ":8080"
CONTIWATCH_CONFIG: "/data/config.json"
TZ: Europe/Warsaw
APP_PIN: "SET_A_UNIQUE_4_TO_8_DIGIT_PIN"
```

Example remote agent `compose_agent.yml` security block:

```yaml
environment:
CONTIWATCH_AGENT: "true"
CONTIWATCH_AGENT_TOKEN: "PUT_LONG_RANDOM_TOKEN_HERE"
CONTIWATCH_ADDR: ":8080"
CONTIWATCH_CONFIG: "/data/config.json"
TZ: Europe/Warsaw
```

Important:
- `APP_PIN` is for the controller only. Do not use it on remote agents.
- Do not use predictable values such as `1234`, `1111`, or `0000`.
- Basic Auth has been removed from controller mode.
- Agent mode is protected by bearer token, not by PIN Guard.
- PIN session is per browser window/tab: refresh in the same tab keeps access, but opening a new tab/window requires PIN again.
- Active work in the same tab is not interrupted by a short session TTL; stale server-side PIN tokens are only cleaned up after prolonged inactivity as a fallback safeguard.

## Config file
`/data/config.json` fields include:
- `scan_interval_sec` (in seconds; UI shows minutes)
- `scheduler_enabled` (if `true`, periodic scans run every `scan_interval_sec`)
- `global_policy`
- `discord_webhook_url`
- `discord_notifications_enabled` (if `false`, no Discord notifications are sent)
- `discord_notify_on_start`
- `discord_notify_on_update_detected`
- `discord_notify_on_container_updated`
- `update_stopped_containers` (if `true`, `update` policy also updates stopped containers but keeps them stopped)
- `prune_dangling_images` (if `true`, prune dangling images after updates)
- `experimental_features` (object of feature flags: `containers`, `containers_sidebar`, `stacks`, `images`, `networks`, `volumes`, `container_shell`, `container_logs`, `container_resources`)
- `experimental_features.container_shell` (enables container shell UI)
- `experimental_features.container_logs` (enables container logs UI)
- `experimental_features.container_resources` (enables container resources UI)
- `experimental_features.stacks` / `experimental_features.images` / `experimental_features.networks` / `experimental_features.volumes` (enables Container stacks/images/networks/volumes buttons in the Containers top bar)
- `experimental_features.containers_sidebar` (shows enabled container subfeatures in the sidebar as shortcuts)
- `local_servers` (list of local Docker daemons with `name`, `socket`, and optional `maintenance`)
- `remote_servers` (list of remote servers with `name`, `url`, optional `token`, and optional `maintenance`)
- `remote_servers[].public_ip` (optional public IP/host used for opening container services from the UI)
- `local_servers[].public_ip` (optional public IP/host used for opening container services from the UI)

Security notes:
- Config file is stored with restricted permissions (`0600`).
- Sensitive values are not returned in full by controller read APIs:
- `GET /api/config` returns `discord_webhook_url` hidden and `discord_webhook_configured` flag.
- `GET /api/servers` returns remote token hidden and `token_configured` flag.
- Controller hardening is environment-driven:
- `APP_PIN` is required for controller startup and enables session gating for protected controller API endpoints and browser UI.
- Remote agents stay token-protected through `CONTIWATCH_AGENT_TOKEN`.
- To keep an existing secret when updating:
- send `discord_webhook_url="__keep__"` for config updates,
- send empty `token` for existing remote server updates.

## API
- `GET /api/version`
- `GET /api/meta` (version/channel/repo metadata used by the UI)
- `GET /api/release` (latest release info + update availability)
- `GET /api/pin/status`
- `POST /api/pin/verify`
- `POST /api/pin/logout`
- `POST /api/scan` run scan
- `POST /api/scan/stop` cancel scan
- `GET /api/scan/state` scan running status
- `GET /api/status` last scan (local or agent)
- `GET /api/aggregate` local + remote status
- `GET/PUT /api/config`
- `GET/POST /api/servers` (remote servers)
- `DELETE /api/servers/{name}`
- `GET/POST /api/locals` (local servers)
- `GET /api/servers/info` versions + reachability
- `GET /api/servers/stream` live server info + scan updates (SSE)
- `POST /api/servers/refresh` trigger on-demand reachability checks (updates stream + returns snapshot)
- `POST /api/status/refresh` pull last scan snapshots from online agents (updates stream)
- `GET /api/containers?scope=local:{name}|remote:{name}` list containers for a selected server
- `POST /api/containers/action` run container action (`start`, `stop`, `restart`, `pause`, `unpause`, `kill`)
- `GET /api/containers/shell` (WebSocket) interactive shell for a container
- `GET /api/containers/logs` (WebSocket) stream logs for a container
- `POST /api/containers/resources` fetch resource metrics for selected containers (`scope`, `container_ids`)
- `GET /api/images?scope=local:{name}|remote:{name}` list images for a selected server
- `POST /api/images/pull` pull image (`repository`, optional `tag`)
- `POST /api/images/prune` prune images (`mode=unused|dangling`)
- `POST /api/images/remove` remove image by `image_id`
- `GET /api/stacks?scope=local:{name}|remote:{name}` list compose stacks stored on the controller
- `GET /api/stacks/get?scope=local:{name}|remote:{name}&name={stack}` fetch compose + env content
- `PUT /api/stacks/save` save compose + env without deploy
- `POST /api/stacks/validate` validate compose yaml (Docker Compose config)
- `POST /api/stacks/action` run stack action (`up`, `down`, `start`, `stop`, `restart`, `kill`, `rm`)
- `POST /api/update/{container_id}` update container
- `POST /api/self-update?container={container_id}` update agent container via helper (agent mode only)
- `GET/POST/DELETE /api/logs`
- `POST /api/notifications/test` test Discord webhook

Notes:
- `POST /api/scan` is a one-off trigger; if a scan is already running it returns `409`.
- `POST /api/update/{container_id}` returns `old_image_id`, `new_image_id`, and `applied_image_id` to help debug tag/image mismatches.
- Periodic scans are disabled by default; enable via `scheduler_enabled` in the config (UI).
- Agent mode exposes a limited API surface (token required).
- Controller mode requires `APP_PIN` and enforces active PIN session for protected API endpoints (except `/api/health`, `/api/version`, `/api/meta`, `/api/release`, `/api/pin/*`).
- Discord webhook test endpoint validates official Discord webhook URLs (`https://.../api/webhooks/...`).

UI timing:
- `Last scan` refers to the last update-check scan (`/api/scan` / agent `/api/status`).
- `Last checked` in server tooltips refers to the last reachability check (`POST /api/servers/refresh`), not the scan time.

## Run (docker compose)
```bash
docker compose up -d --build
```

Before first start, edit [docker-compose.yml](/home/buzuser/github/contiwatch_dev/docker-compose.yml) and set:
- `APP_PIN`

For a remote agent, edit [compose_agent.yml](/home/buzuser/github/contiwatch_dev/compose_agent.yml) and set a long random `CONTIWATCH_AGENT_TOKEN`.

Stop:
```bash
docker compose down
```

## Permissions
If `/data/config.json` shows permission errors, set user IDs in compose:
```yaml
environment:
PUID: "${PUID}"
PGID: "${PGID}"
```
Use your host user IDs (from `id`).

If you see `permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock`, ensure the socket is mounted and the container user can access it. Contiwatch tries to detect the socket group ID automatically; if your setup needs it explicitly, set `DOCKER_GID` to the group id of `/var/run/docker.sock` on the host (e.g. from `stat -c '%g' /var/run/docker.sock`).

## Buy Me a Coffee
If you like the results of this project, feel free to support it.

[!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/pbuzdygan)


BMC QR code