
Please be aware that the application is distributed as is and is not recommended for use in a production environment.
And don't forget about regular backups of important data.
Automatic updates are disabled by default. You can choose only what you need.
Use docker-compose.app.yml or following docker commands.
```bash # create volume docker volume create tugtainer_data
# pull image docker pull ghcr.io/quenary/tugtainer:1
# run container docker run -d -p 9412:80 \ --name=tugtainer \ --restart=unless-stopped \ -e AGENT_SECRET="CHANGE_ME!" \ -v tugtainer_data:/tugtainer \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ ghcr.io/quenary/tugtainer:1 ```
[!IMPORTANT] Keep in mind that you cannot update an agent or a socket-proxy from within the app because they are used to communicate with the Docker CLI. Avoid including these containers in a docker-compose that contains other containers you want to update automatically, as this will result in an error during the update. To keep them updated, you can activate the "check" only to receive notifications, and recreate manually or from another tool, such as Portainer.
To manage remote hosts from one UI, you have to deploy the Tugtainer Agent. To do so, you can use docker-compose.agent.yml or following docker commands.
After deploying the agent, in the UI follow Menu -> Hosts, and add it with the respective parameters.
Remember that the machine with the agent must be accessible for the primary instance.
Don't forget to change AGENT_SECRET variable. It is used for backend-agent requests signature.
Backend and agent use http to communicate, so you can utilize reverse proxy for https.
```bash # pull image docker pull ghcr.io/quenary/tugtainer-agent:1
# run container docker run -d -p 9413:8001 \ --name=tugtainer-agent \ --restart=unless-stopped \ -e AGENT_SECRET="CHANGE_ME!" \ -v /var/run/docker.sock:/var/run/docker.sock:ro \ ghcr.io/quenary/tugtainer-agent:1 ```
You can use Tugtainer and Tugtainer Agent without direct mount of docker socket.
docker-compose.app.yml and docker-compose.agent.yml use this approach by default.
Manual setup: - Deploy socket-proxy e.g. https://hub.docker.com/r/linuxserver/socket-proxy - Enable at least CONTAINERS, IMAGES, POST, INFO, PING for the check feature, and NETWORKS for the update feature; - Set env var DOCKER_HOST="tcp://my-socket-proxy:port" to the Tugtainer(-agent) container(s);
Scheduled process includes all enabled hosts and all containers selected for auto-check.
Manual process includes all containers despite auto-check toggle (or a single container if you've clicked one)
Containers without dependencies are treated as independent nodes in the graph
running containers are skipped by default (can be changed in the settings);Scheduled process being performed for all enabled hosts.
To use private registries, you have to mount docker config to Tugtainer or Tugtainer Agent, depending on where the container with the private image is located.
docker login <registry>json
{
"auths": {
"<registry>": {
"auth": "base64 encoded 'username:password_or_token'"
}
}
}-v $HOME/.docker/config.json:/root/.docker/config.json:ro or in a docker-compose file.This label indicates that the container cannot be stopped. This means that even if there is a new image for the container, it cannot be updated from the app. This label is primarily used for tugtainer itself and tugtainer-agent, as well as for socket-proxy in the provided docker-compose files.
This label is an alternative to the docker compoes label. It allows you to declare that a container depends on another container, even if they are not in the same compose project. List of container names, separated by commas.
The app uses Apprise to send notifications and Jinja2 to generate their content. You can view the documentation for each of them for more details.
Jinja2 custom filters:
Jinja2 context schema:
{
"hostname": "Tugtainer container hostname",
"results": [
{
"host_id": 0,
"host_name": "string",
"items": [
{
"container": {
"id": "string",
"image": "string",
"...other keys of 'docker container inspect' in snake_case": {},
},
"local_image": {
"id": "string",
"repo_digests": [
"digest1",
"digest2",
],
"...other keys of 'docker image inspect' in snake_case": {},
},
"remote_image": {
"...same schema as for local_image": {},
},
"local_digests": [
"list of platform specific image digests",
],
"remote_digests": [
"list of platform specific image digests",
],
"result": "not_available|available|available(notified)|updated|rolled_back|failed|None"
}
],
"prune_result": "string",
}
]
}
"result" options:
The notification is sent only if the body is not empty. For instance, if there is only containers with "available(notified)" results, the body will be empty (with default template), and notification will not be sent.
If you want to restore default template, it's here
The app uses password authorization by default. The password is stored in the file in encrypted form.
Auth cookies are not domain-specific and not https only, but you can change this using env variables.
Starting with v1.6.0, you can use the OpenID Connect provider instead of password. This can also be configured using env variables.
The backend API is served under the /api base path.
/api/docs/api/redocGET /api/public/healthGET /api/public/versionGET /api/public/summary (requires ENABLE_PUBLIC_API=true)GET /api/public/update_count (requires ENABLE_PUBLIC_API=true)GET /api/public/is_update_available (requires ENABLE_PUBLIC_API=true)Environment variables are not required, but you can still define some. There is .env.example containing list of vars with description.

Contributions are welcome. Please follow these guidelines to keep the project consistent and maintainable.
feat(backend): add user authentication. Common types: feat, fix, docs, refactor, test, choreSee /backend/README.md and /frontend/README.md for more details
$ claude mcp add tugtainer \
-- python -m otcore.mcp_server <graph>