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
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
I tried a few video files like AVI and old phoen 3GP files
However, we can just use vert.sh directly
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