Self-hosted service
Quenary/tugtainer avatar
Quenary/tugtainer

Tugtainer: a self-hosted Docker update watcher with a web UI

An application for automated Docker container updates with a web UI

1,538 stars54 forksPythonMIT

At a glance

What is it?
Tugtainer automates Docker container updates from a self-hosted web UI, with per-container check or auto-update settings, cron scheduling and notifications. The README states it is not recommended for production, so the useful question is where it fits in a homelab or a small internal host.
Who is it for?
Tugtainer suits people running a handful of Docker hosts who want a browser interface for deciding which containers update themselves and which only get watched. It is the wrong choice if you cannot accept the README's own warning that the application is distributed as-is and is not recommended for a production environment, or if you need a managed control plane.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository received new commits within the last day.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The gap Tugtainer fills in a Docker host

Docker gives you no built-in answer to the question of whether a running container's image has moved on. Watchtower-style tools exist, but the README positions Tugtainer as an application with a web UI, authentication, multiple hosts, per-container configuration and notifications rather than a single-purpose updater binary. The audience is visible in the topics attached to the repository: homelab, self-hosted, dashboard, container-monitoring. That is a person running several containers on one or more machines who wants to see, in a browser, which images have a newer tag and to choose per container whether the update happens automatically or only produces a notification. The README is explicit that automatic updates are disabled by default and that you enable only what you need, which is a deliberate posture: the tool will not start replacing your containers the moment you deploy it. The same README carries a warning worth repeating before anything else: the application is distributed as-is and is not recommended for use in a production environment, and it asks for regular backups of important data.

How the app, the agent and the Docker socket fit together

The architecture splits into two container images. The main application serves the web UI and holds the state; the agent talks to a Docker daemon and is what actually checks and recreates containers. The main image is ghcr.io/quenary/tugtainer:1 and the agent image is ghcr.io/quenary/tugtainer-agent:1. The main container listens on port 80 internally and the quick-start command publishes it as 9412; the agent listens on 8001 internally and is published as 9413. Communication between backend and agent is HTTP by default, with a reverse proxy suggested for HTTPS, and requests are signed with a shared secret. AGENT_SECRET is described as mandatory unless ALLOW_UNAUTHENTICATED_AGENT is true, and the latter is called a development-purpose setting. There is an internal agent for the local host, controlled by AGENT_ENABLED, which defaults to true. Remote hosts are added through Menu, then Hosts in the UI, and the Agent secret field there must match the AGENT_SECRET given to the agent container. The security documentation notes that agent host URLs resolving to private or reserved networks are blocked by default, with AGENT_ALLOW_NETWORKS and AGENT_ALLOW_ENDPOINTS as the escape hatches; by default only 127.0.0.1:8001 is allowed when AGENT_ENABLED is true. On the Python side, pyproject.toml lists fastapi, uvicorn, python-on-whales, aiosqlite, alembic, aiocron, apprise, paho-mqtt and python-socketio, which describes a FastAPI backend with SQLite storage, migrations, cron-style scheduling, notification delivery and websocket updates to the frontend.

Deploying Tugtainer and running a first check

The README's quick start creates a named volume, pulls the image and runs the container with the Docker socket mounted read-only. Set a real value for AGENT_SECRET before running this; the empty string in the README is a placeholder.

bash
docker volume create tugtainer_data
docker pull ghcr.io/quenary/tugtainer:1
docker run -d -p 9412:80 \
    --name=tugtainer \
    --restart=unless-stopped \
    -e AGENT_SECRET="" \
    -v tugtainer_data:/tugtainer \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    ghcr.io/quenary/tugtainer:1

After the container starts, the UI is reachable on port 9412 of the host. The repository also ships docker-compose.app.yml and docker-compose.agent.yml, which the README says use a socket proxy by default rather than a direct socket mount, so the compose path is the one to prefer if you would rather not hand the container /var/run/docker.sock. For a remote machine, the agent is deployed separately and then registered in the UI.

bash
docker pull ghcr.io/quenary/tugtainer-agent:1
docker run -d -p 9413:8001 \
    --name=tugtainer-agent \
    --restart=unless-stopped \
    -e AGENT_SECRET="" \
    -v /var/run/docker.sock:/var/run/docker.sock:ro \
    ghcr.io/quenary/tugtainer-agent:1

If you go the socket proxy route instead, the README lists exactly which proxy environment variables each feature needs: CONTAINERS, IMAGES, POST, INFO and PING for the base check feature, NETWORKS for updates, ALLOW_LOGS for logs, and ALLOW_START, ALLOW_STOP, ALLOW_RESTARTS, ALLOW_PAUSE and ALLOW_UNPAUSE for container controls. You then point the Tugtainer or agent container at it with DOCKER_HOST, for example tcp://my-socket-proxy:port. Nothing updates until you turn it on: the README states automatic updates are disabled by default.

Per-container control, labels and hooks

The per-container model is the part that distinguishes Tugtainer from a blanket updater. Each container can be set to check only, which produces a notification when a newer image exists, or to auto-update. Two custom labels carry behaviour that Docker itself does not express. dev.quenary.tugtainer.protected=true marks a container as one that cannot be stopped, so even if a new image appears it will not be updated by the app; the README says this is used for tugtainer itself, tugtainer-agent and socket-proxy in the supplied compose files. dev.quenary.tugtainer.depends_on takes a comma-separated list of container names and declares a dependency across compose projects, which the built-in compose label cannot do. Hooks are the other extension point: shell commands run inside the target container at pre_update, post_update, pre_stop, pre_rollback and post_rollback, each executed as sh -c "<command>" via the agent. The README is candid that Tugtainer has no built-in database or service-specific backup logic, which means the hooks are the only mechanism you get for ordering a backup before a container is replaced. That is a real design boundary, not a missing checkbox: if your service needs a consistent dump before restart, you write it yourself and wire it to pre_update.

What Tugtainer cannot update, and where it breaks

The README's most important operational note is that you cannot update an agent or a socket proxy from within the app, because those containers are the channel the app uses to talk to the Docker CLI. It goes further and warns against putting those containers in a docker-compose file alongside containers you want updated automatically, since the update will error. The recommended workaround is to enable check only on them and recreate them manually or with another tool such as Portainer. That is a structural limitation, not a bug that will be fixed by a flag. The remote-host path has its own friction: private and reserved network URLs are blocked by default, so a LAN agent will not connect until you add the network or endpoint to AGENT_ALLOW_NETWORKS or AGENT_ALLOW_ENDPOINTS, and the README describes that check as best-effort. TLS is also fiddly. With a public CA you set the host URL to https and leave SSL on; with a private or self-signed CA you paste the CA PEM into Custom CA and the certificate hostname or SAN must match the URL host. If you terminate TLS on the agent itself with --ssl-certfile and --ssl-keyfile, the README notes the image healthcheck still probes http://localhost:8001 and may fail. Finally, the storage layer is SQLite only, DB_URL defaults to sqlite+aiosqlite:////tugtainer/tugtainer.db, and the JWT secret is autogenerated unless you set JWT_SECRET_KEY, so every container restart invalidates existing access tokens and forces a login.

Tugtainer against Watchtower and Portainer

The closest comparison is Watchtower, which is a single container that watches for new images and replaces running containers according to its own flags. The difference in approach is where the decision lives. Watchtower's configuration is environment variables and labels read at startup; Tugtainer keeps state in SQLite, exposes a web UI with authentication, and lets you change per-container behaviour after the fact without recreating the updater. Watchtower has no multi-host agent model; Tugtainer splits into an app and an agent precisely so one UI can drive several Docker daemons. The second comparison is Portainer, which is a broader container management UI with its own update and stack features. Tugtainer is narrower: the README lists basic container control (start, stop), inspect and logs, but the product is organized around checking and updating images, with scheduling through crontab and notifications through apprise. If you already run Portainer for general management, Tugtainer overlaps on the update axis and adds a check-only mode plus update-lifecycle hooks that Portainer does not describe in this material. If you want the smallest possible moving part and one host, Watchtower is less to operate.

Maintenance cost, licence and what to watch on upgrade

The repository is not archived and the last push was on 2026-09-07, which is recent. Releases are frequent and small: v1.38.0 on 2026-09-04, v1.39.0 on 2026-09-06 and v1.40.0 on 2026-09-07. The versioning is automated through python-semantic-release with conventional commits, and pyproject.toml shows the version in the working tree at 1.41.0, one ahead of the newest listed release. That cadence means upgrade cost is mostly about the database schema rather than API churn: alembic is a dependency, so migrations run against the SQLite file in the tugtainer_data volume. Back that volume up before pulling a new image, which is consistent with the README's own advice about regular backups. Two upgrade traps are documented. First, JWT_SECRET_KEY is autogenerated by default, so a restart logs everyone out; set it explicitly if that matters. Second, the protected label on the app, agent and socket-proxy containers means those will never be updated by Tugtainer itself, so they need their own update path. The licence is MIT, which permits commercial use and modification; the LICENSE file is at the repository root. Nothing here is legal advice, and the README's as-is disclaimer sits alongside the licence rather than replacing it.

Editorial conclusion

Tugtainer suits people running a handful of Docker hosts who want a browser interface for deciding which containers update themselves and which only get watched. It is the wrong choice if you cannot accept the README's own warning that the application is distributed as-is and is not recommended for a production environment, or if you need a managed control plane. Before adopting it, verify the AGENT_SECRET handling in your compose file, confirm which containers carry the protected label, and decide whether you want the socket proxy path instead of a direct /var/run/docker.sock mount.

Frequently asked questions

Is there a default password for the Tugtainer web UI?

The README does not describe a default password. Authentication can be disabled entirely with the DISABLE_AUTH environment variable, which the .env.example notes defaults to empty, meaning false.

Where can I find the Tugtainer changelog?

The repository has a CHANGELOG.md at the top level, and releases are generated automatically by python-semantic-release from conventional commits, with tags in the format v{version}.

Which Docker socket permissions does Tugtainer need?

The quick-start command mounts /var/run/docker.sock read-only, and the README says the supplied compose files instead use a socket proxy with specific environment variables enabled per feature, such as CONTAINERS and IMAGES for checking and NETWORKS for updates.

Can Tugtainer update its own container?

No. The README states you cannot update an agent or a socket proxy from within the app because they are the channel used to communicate with the Docker CLI, and the protected label prevents those containers from being stopped.

What database does Tugtainer use?

Only SQLite is supported. DB_URL defaults to sqlite+aiosqlite:////tugtainer/tugtainer.db, and alembic is listed as a dependency, so schema migrations apply to that file.

Official sources

  1. Issues
  2. License: MIT
  3. Quenary/tugtainer on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/quenary-tugtainer.svg)](https://hysenlabs.com/projects/quenary-tugtainer)