OS Apps: Vert, Watchtower and Tugtainer

Published: Oct 28, 2025 by Isaac Johnson

Vert is an excellent media convertor that I found from Marius. It just is about converting types. I had a lot of old 3gpp files from “the olden days” of cell phones to try out.

Watchtower is a great container updating tool (itself a container) but for those that want more UI, there is a related project, Tugtainer which I found offers a bit better user experience.

Let’s try all these tools both locally in Docker and in some cases Kubernetes.

Vert.sh

I saw this Marius post a few weeks ago about Vert which is an open-source file converter

Let’s start with Docker:

$ docker run -d \
    --restart unless-stopped \
    -p 3000:80 \
    --name "vert" \
    ghcr.io/vert-sh/vert:latest

Which loads jsut fine

/content/images/2025/10/vert-01.png

I now convert files using this local docker instance. For example, I can upload a PNG and then select GIF and convert all to convert it to a gif

/content/images/2025/10/vert-02.png

I tried a few video files like AVI and old phoen 3GP files

/content/images/2025/10/vert-03.png

However, we can just use vert.sh directly

/content/images/2025/10/vert-04.png

Watchtower

Watchtower is all about automating the update of a containerized app whenever a new version is pushed to a registry.

Let’s take a look at it’s usage docs to see how we might deploy this on a docker host.

Let’s start with a container that is way out of date, Cinny

$ docker ps | grep cinny
9fe031d65142   ghcr.io/cinnyapp/cinny:latest                                    "/docker-entrypoint.…"   21 months ago    Up 4 weeks                0.0.0.0:8088->80/tcp, :::8088->80/tcp                                              cinny

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\watchtower-01.png

I should be able to run

docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower cinny --debug

It looks like it scheduled a check in 24hr

builder@builder-T100:~$ docker run -d \
  --name watchtower \
  -v /var/run/docker.sock:/var/run/docker.sock \
  containrrr/watchtower cinny --debug
Unable to find image 'containrrr/watchtower:latest' locally
latest: Pulling from containrrr/watchtower
57241801ebfd: Pull complete
3d4f475b92a2: Pull complete
1f05004da6d7: Pull complete
Digest: sha256:6dd50763bbd632a83cb154d5451700530d1e44200b268a4e9488fefdfcf2b038
Status: Downloaded newer image for containrrr/watchtower:latest
9e91a2848c4948e52c6c76bf3afd0ff0462728a7e31174949f1426c70c3a9c8b
builder@builder-T100:~$ docker ps | grep watch
9e91a2848c49   containrrr/watchtower                                            "/watchtower cinny -…"   7 seconds ago    Up 7 seconds (health: starting)   8080/tcp                                                                           watchtower
builder@builder-T100:~$ docker logs watchtower
time="2025-10-15T12:02:41Z" level=debug msg="Sleeping for a second to ensure the docker api client has been properly initialized."
time="2025-10-15T12:02:42Z" level=debug msg="Making sure everything is sane before starting"
time="2025-10-15T12:02:42Z" level=debug msg="Retrieving running containers"
time="2025-10-15T12:02:42Z" level=debug msg="There are no additional watchtower containers"
time="2025-10-15T12:02:42Z" level=debug msg="Watchtower HTTP API skipped."
time="2025-10-15T12:02:42Z" level=info msg="Watchtower 1.7.1"
time="2025-10-15T12:02:42Z" level=info msg="Using no notifications"
time="2025-10-15T12:02:42Z" level=info msg="Only checking containers which name matches \"cinny\""
time="2025-10-15T12:02:42Z" level=info msg="Scheduling first run: 2025-10-16 12:02:42 +0000 UTC"
time="2025-10-15T12:02:42Z" level=info msg="Note that the first check will be performed in 23 hours, 59 minutes, 59 seconds"

I’m patient so I’ll wait a day and see (after 8am Thur…)

Tugtainer

Another interesting app that came on my radar is Tugtainer. This can be used to both list your docker containers and update them similar to Watchtower

It’s easy to install. Let’s add it to our docker host with a docker run command

builder@builder-T100:~/tugtainer$ mkdir data
builder@builder-T100:~/tugtainer$ docker run -d -p 9412:80 \
    --name=tugtainer \
    --restart=unless-stopped \
    -v ./data:/tugtainer \
    -v /var/run/docker.sock:/var/run/docker.sock \
    quenary/tugtainer:latest
Unable to find image 'quenary/tugtainer:latest' locally
latest: Pulling from quenary/tugtainer
8c7716127147: Already exists
eb47ef3fe06b: Already exists
326ad4d2abf0: Already exists
97385090cab0: Already exists
1157ce8d3c4a: Pull complete
d248418bcf38: Pull complete
e147d769b83c: Pull complete
271d55492ae9: Pull complete
5a37a23d9d0d: Pull complete
ef016fbb39d2: Pull complete
e15da9aca888: Pull complete
47a1f3722687: Pull complete
38c1499509de: Pull complete
Digest: sha256:3bb2dc4e3b428719b015252253ae88fd4efdc224b93bfb089946c198bd779586
Status: Downloaded newer image for quenary/tugtainer:latest
8cf5956d4904246de02449fcc095c1f96153433e0da3f76159853db7976f5047

I’m now presented with the login page where I can create an admin password

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-01.png

Then I use it to sign back in

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-02.png

I’m now presented with a large list of all my running containers

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-03.png

For any service I can expand to see what ports it serves (if any)

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-04.png

Let’s test with Beszel. I can see I do not have a tag pinned in the docker compose and it’s been 6 months since I fired it up.

builder@builder-T100:~/beszel$ cat docker-compose.yaml
services:
  beszel:
    image: 'henrygd/beszel'
    container_name: 'beszel'
    restart: unless-stopped
    ports:
      - '8095:8090'
    volumes:
      - ./beszel_data:/beszel_data

builder@builder-T100:~/beszel$ docker ps | grep besz
ab9ef739723f   henrygd/beszel                                                   "/beszel serve --htt…"   6 months ago    Up 4 weeks               0.0.0.0:8095->8090/tcp, :::8095->8090/tcp                                          beszel
c3f76f918df1   henrygd/beszel-agent                                             "/agent"                 11 months ago   Up 4 weeks                                                                                                  beszel-agent

It looks like presently I’m running 0.6.2

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-05.png

As we can see, that was incredibly easy to use to update a containerized app:

I can now tick the “Check” and “Update” boxes to have Tugtainer automatically check for updates and update

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-06.png

Another app I have tied to “latest” is dockpeek

builder@builder-T100:~/dockpeek$ cat docker-compose.yml
services:
  dockpeek:
    image: ghcr.io/dockpeek/dockpeek:latest
    container_name: dockpeek
    environment:
      - SECRET_KEY=my_secret_key   # Set secret key
      - USERNAME=builder           # Change default username
      - PASSWORD=Redliub$1Redliub$1 #  Change default password
    ports:
      - "3420:8000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    restart: unless-stopped

Here I’ll just set the auto-update and watch it work

For containers that are pinned to a service, this won’t do much, like my kokoro instance

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-08.png

While Tugtainer can update images, it cannot stop/start them if they are down. I find it’s best to pair up with Containery for that

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-09.png

Exposing with TLS

Let’s fire up an A record in Azure DNS

 az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.72.233.202 -n tugtainer
{
  "ARecords": [
    {
      "ipv4Address": "75.72.233.202"
    }
  ],
  "TTL": 3600,
  "etag": "5657cb50-4a98-4329-a957-849990a8881a",
  "fqdn": "tugtainer.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/tugtainer",
  "name": "tugtainer",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

I can now create the ingres, service and endpoint to hand off traffic

$ cat ./tugtainer-ingress.yaml

apiVersion: v1
kind: Endpoints
metadata:
  name: tugtainer-external-ip
subsets:
- addresses:
  - ip: 192.168.1.99
  ports:
  - name: tugtainerint
    port: 9412
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: tugtainer-external-ip
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: RequireDualStack
  ports:
  - name: tugtainer
    port: 80
    protocol: TCP
    targetPort: 9412
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: tugtainer-external-ip
  generation: 1
  name: tugtaineringress
spec:
  rules:
  - host: tugtainer.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: tugtainer-external-ip
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - tugtainer.tpk.pw
    secretName: tugtainer-tls

$ kubectl apply -f ./tugtainer-ingress.yaml
endpoints/tugtainer-external-ip created
service/tugtainer-external-ip created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/tugtaineringress created

Once the cert is live

$ kubectl get cert tugtainer-tls
NAME            READY   SECRET          AGE
tugtainer-tls   True    tugtainer-tls   10m

I can just login with the URL to check on containers

\wsl.localhost\Ubuntu\home\builder\Workspaces\jekyll-blog\content\images\2025\10\tugtainer-10.png

vert watchtower tugtainer opensource docker

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes