Centrálne automatické aktualizácie Docker kontajnerov pomocou Watchtoweru, mTLS a e-mailových notifikácií

Pri väčšom počte Docker hostov začne byť ručné kontrolovanie nových image pomerne otravné. Na jednom serveri beží monitoring, na inom mailové služby, inde vlastné aplikácie a postupne sa z jednoduchého prostredia stáva infraštruktúra, kde treba mať aktualizácie pod kontrolou.
Cieľom tejto dokumentácie je ukázať, ako som nasadil centrálne riadený Watchtower, ktorý automaticky aktualizuje iba vybrané Docker kontajnery na vzdialených hostoch.
1. Návrh riešenia a architektúra
Riešenie som navrhol tak, aby Watchtower:
- bežal centrálne na jednom serveri,
- pristupoval k vzdialeným Docker daemon-om cez zabezpečené mTLS spojenie,
- aktualizoval iba explicitne povolené kontajnery,
- neposielal zbytočné notifikácie pri každej kontrole,
- poslal e-mail pri úspešnom update alebo chybe,
- nezasahoval do kritických služieb ako databázy, Mailcow, FreeIPA alebo ďalšie infraštruktúrne komponenty.
Použitá architektúra vyzerá nasledovne:

Na serveri 10.10.8.6 teda bežia dve nezávislé Watchtower inštancie:
watchtower-84
watchtower-85
Každá z nich sa pripája k jednému konkrétnemu Docker hostu.
Prvá spravuje:
10.10.8.4
Druhá spravuje:
10.10.8.5
čiže logika je:

Výhodou tohto riešenia je, že na vzdialených serveroch nemusí bežať ďalší Watchtower kontajner. Centrálne miesto zároveň umožňuje jednoduchšie spravovať certifikáty, notifikácie a harmonogram aktualizácií.
Prečo nepoužiť Docker API bez TLS
Docker daemon je veľmi privilegovaná služba. Klient, ktorý má plný prístup k Docker API, môže vytvárať kontajnery, mountovať filesystem hosta a v praxi tým môže získať veľmi vysoké oprávnenia na danom serveri.
Preto som nepoužil nezabezpečený Docker API endpoint:
tcp://10.10.8.4:2375
Namiesto toho používam:
tcp://10.10.8.4:2376
tcp://10.10.8.5:2376
s mTLS autentifikáciou.
Pri mTLS sa neoveruje iba server voči klientovi, ale aj klient voči serveru.
To znamená, že samotná znalosť IP adresy a portu nestačí.
Bez správneho klientského certifikátu Docker spojenie odmietne.
Pri teste bez klientského certifikátu sme dostali:
tlsv13 alert certificate required
a pri použití správneho certifikátu:
OK
To je presne stav, ktorý chceme.
Prečo nepovoliť automatické aktualizácie všetkého
Watchtower vie veľmi jednoducho aktualizovať všetky kontajnery, ale pri produkčnom alebo domácom infra prostredí to podľa mňa nie je dobrý prístup.
Niektoré služby môžu pri novej verzii:
meniť databázovú schému,
vyžadovať migráciu,
meniť formát konfigurácie,
meniť API,
vyžadovať koordinovaný upgrade viacerých komponentov.
Preto používam label-only režim.
Watchtower aktualizuje iba kontajnery, ktoré majú explicitne:
labels:
- "com.centurylinklabs.watchtower.enable=true"
V mojom prípade sú automaticky aktualizované napríklad:
Element
ChangeDetection
Browserless Chrome
Grafana
Naopak automaticky neaktualizujem:
Mailcow
Synapse
Zabbix
MariaDB
PostgreSQL
Passbolt
NetBox
FreePBX
FreeIPA
vlastné kontajnery
Pri týchto službách preferujem klasický postup:
backup
kontrola release notes
ručný update
kontrola aplikácie
To je bezpečnejšie a dáva väčšiu kontrolu nad prípadnými migráciami alebo zmenami.
E-mailové notifikácie
Súčasťou riešenia sú aj e-mailové notifikácie.
Watchtower odosiela správy cez SMTP účet:
updater@ibasterisk.eu
Používa sa samostatný App Password s povoleným iba SMTP protokolom.
Komunikácia prebieha cez:
SMTP port 587
STARTTLS
Heslo nie je uložené priamo v docker-compose.yml, ale v samostatnom súbore s prístupom iba pre root.
Watchtower neposiela mail pri každej nočnej kontrole.
Správa sa odošle iba vtedy, keď:
sa kontajner aktualizuje
alebo
update skončí chybou
Tým sa zabráni zbytočnému spamu.
2. Vytvorenie vlastnej Docker CA a certifikátov pre mTLS
Pre zabezpečenú komunikáciu medzi centrálnym Watchtower serverom a vzdialenými Docker daemonmi som použil vlastnú certifikačnú autoritu určenú výhradne pre Docker API.
Certifikáty vytváram centrálne na serveri:
10.10.8.6
freeipa.ibasterisk.local
Docker CA bude následne podpisovať:
serverový certifikát pre 10.10.8.4
serverový certifikát pre 10.10.8.5
klientsky certifikát pre Watchtower
Architektúra certifikátov teda vyzerá nasledovne:

Vďaka tomu Docker daemon vie overiť, že prichádzajúci klient je skutočne náš Watchtower, a Watchtower zároveň overí identitu vzdialeného Docker servera.
Vytvorenie adresára pre PKI
Na serveri 10.10.8.6 som vytvoril samostatný adresár:
sudo mkdir -p /etc/watchtower-pki
sudo chmod 700 /etc/watchtower-pki
Následne som prešiel do root shellu:
sudo -i
cd /etc/watchtower-pki
Obsah tohto adresára je dostupný iba používateľovi root.
Vytvorenie Docker CA
Najskôr som vytvoril privátny kľúč certifikačnej autority:
openssl genrsa -out docker-ca-key.pem 4096
Potom samotný CA certifikát:
openssl req \
-new \
-x509 \
-days 3650 \
-key docker-ca-key.pem \
-sha256 \
-out docker-ca.pem \
-subj "/CN=iBasterisk Docker CA"
CA je platná desať rokov. Privátny kľúč CA je najcitlivejší súbor v celom riešení.
Nastavil som preto práva:
chmod 400 docker-ca-key.pem
chmod 444 docker-ca.pem
Privátny kľúč:
docker-ca-key.pem
zostáva iba na 10.10.8.6.
Na vzdialené Docker servery sa nikdy nekopíruje.
Serverový certifikát pre Docker host 10.10.8.4
Pre prvý Docker host som vytvoril samostatný privátny kľúč:
openssl genrsa -out docker-10.10.8.4-key.pem 4096
openssl req \
-subj "/CN=10.10.8.4" \
-sha256 \
-new \
-key docker-10.10.8.4-key.pem \
-out docker-10.10.8.4.csr
Samotné CN však pri modernom TLS nestačí. Certifikát musí obsahovať správny Subject Alternative Name.
Preto som vytvoril:
cat > docker-10.10.8.4-ext.cnf <<'EOF'
subjectAltName = IP:10.10.8.4,DNS:mail-ibasterisk
extendedKeyUsage = serverAuth
EOF
Certifikát som následne podpísal Docker CA:
openssl x509 \
-req \
-days 825 \
-sha256 \
-in docker-10.10.8.4.csr \
-CA docker-ca.pem \
-CAkey docker-ca-key.pem \
-CAcreateserial \
-out docker-10.10.8.4-cert.pem \
-extfile docker-10.10.8.4-ext.cnf
Dôležitá je najmä hodnota:
extendedKeyUsage = serverAuth
Tým explicitne určujeme, že certifikát slúži na autentifikáciu TLS servera.
Výsledok som overil:
openssl x509 \
-in docker-10.10.8.4-cert.pem \
-noout \
-subject \
-issuer \
-dates \
-ext subjectAltName \
-ext extendedKeyUsage
Výstup obsahoval:
subject=CN = 10.10.8.4
issuer=CN = iBasterisk Docker CA
a:
IP Address:10.10.8.4
DNS:mail-ibasterisk
plus:
TLS Web Server Authentication
SAN som si ešte samostatne overil:
openssl x509 \
-in docker-10.10.8.4-cert.pem \
-noout -text | grep -A2 "Subject Alternative Name"
Výsledok:
X509v3 Subject Alternative Name:
IP Address:10.10.8.4, DNS:mail-ibasterisk
Serverový certifikát pre Docker host 10.10.8.5
Rovnakým spôsobom som vytvoril certifikát pre monitoring server:
10.10.8.5
mon.ibasterisk.local
Privátny kľúč:
openssl genrsa -out docker-10.10.8.5-key.pem 4096
CSR:
openssl req \
-subj "/CN=10.10.8.5" \
-sha256 \
-new \
-key docker-10.10.8.5-key.pem \
-out docker-10.10.8.5.csr
SAN konfigurácia:
cat > docker-10.10.8.5-ext.cnf <<'EOF'
subjectAltName = IP:10.10.8.5,DNS:mon.ibasterisk.local
extendedKeyUsage = serverAuth
EOF
A podpis:
openssl x509 \
-req \
-days 825 \
-sha256 \
-in docker-10.10.8.5.csr \
-CA docker-ca.pem \
-CAkey docker-ca-key.pem \
-CAserial docker-ca.srl \
-out docker-10.10.8.5-cert.pem \
-extfile docker-10.10.8.5-ext.cnf
Kontrola SAN:
openssl x509 \
-in docker-10.10.8.5-cert.pem \
-noout -text | grep -A2 "Subject Alternative Name"
Výsledok:
X509v3 Subject Alternative Name:
IP Address:10.10.8.5, DNS:mon.ibasterisk.local
Klientsky certifikát pre Watchtower
Serverové certifikáty máme hotové. Teraz potrebujeme certifikát, ktorým sa bude Watchtower autentifikovať voči Docker daemonu.
Vytvoril som privátny kľúč:
openssl genrsa -out watchtower-key.pem 4096
A CSR:
openssl req \
-subj "/CN=watchtower-10.10.8.6" \
-new \
-sha256 \
-key watchtower-key.pem \
-out watchtower.csr
Tentokrát nejde o serverový, ale o klientsky certifikát.
Preto som vytvoril:
cat > watchtower-ext.cnf <<'EOF'
extendedKeyUsage = clientAuth
EOF
Certifikát som podpísal rovnakou Docker CA:
openssl x509 \
-req \
-days 825 \
-sha256 \
-in watchtower.csr \
-CA docker-ca.pem \
-CAkey docker-ca-key.pem \
-CAserial docker-ca.srl \
-out watchtower-cert.pem \
-extfile watchtower-ext.cnf
Privátny kľúč klienta som zabezpečil:
chmod 400 watchtower-key.pem
chmod 444 watchtower-cert.pem
Kontrola:
openssl x509 \
-in watchtower-cert.pem \
-noout \
-subject \
-issuer \
-dates \
-ext extendedKeyUsage
Výsledok:
subject=CN=watchtower-10.10.8.6
issuer=CN=iBasterisk Docker CA
a hlavne:
TLS Web Client Authentication
To potvrdzuje, že certifikát je určený na autentifikáciu klienta.
Čo máme v tejto chvíli pripravené
Na centrálnom serveri máme približne:
/etc/watchtower-pki/
│
├── docker-ca.pem
├── docker-ca-key.pem
├── docker-ca.srl
│
├── docker-10.10.8.4-cert.pem
├── docker-10.10.8.4-key.pem
├── docker-10.10.8.4.csr
│
├── docker-10.10.8.5-cert.pem
├── docker-10.10.8.5-key.pem
├── docker-10.10.8.5.csr
│
├── watchtower-cert.pem
├── watchtower-key.pem
└── watchtower.csr
Z pohľadu bezpečnosti je najdôležitejšie:
docker-ca-key.pem
Tento súbor nesmie opustiť centrálny PKI server.
Na vzdialené Docker hosty pôjdu iba ich vlastné serverové certifikáty, serverové privátne kľúče a verejný certifikát CA.
Na 10.10.8.4 teda neskôr skončí:
ca.pem
server-cert.pem
server-key.pem
a rovnako na 10.10.8.5.
Watchtower na 10.10.8.6 bude používať:
docker-ca.pem
watchtower-cert.pem
watchtower-key.pem
3. Konfigurácia Docker daemonu pre vzdialený prístup cez mTLS
Po vytvorení certifikátov je ďalším krokom sprístupnenie Docker API na vzdialených hostoch.
V mojom prípade ide o:
10.10.8.4
10.10.8.5
Docker bude na oboch serveroch naďalej používať lokálny socket:
/var/run/docker.sock
a zároveň pribudne zabezpečený TCP listener:
10.10.8.4:2376
10.10.8.5:2376
Port 2376 používam výhradne s TLS a overovaním klientského certifikátu.
Architektúra vyzerá takto:

Kontrola existujúcej Docker konfigurácie
Pred akoukoľvek zmenou odporúčam najskôr zistiť, ako je Docker daemon aktuálne spúšťaný:
sudo systemctl cat docker | grep -E '^\[Service\]|^ExecStart'
V mojom prípade bol Docker spúšťaný takto:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Taktiež som skontroloval:
sudo ss -lntp | grep -E ':(2375|2376)\b'
Pred konfiguráciou nevystupoval žiadny listener:
2375/2376 momentalne nepocuvaju
To je správny východiskový stav.
Prenos serverových certifikátov
Na každý Docker host potrebujeme iba:
CA certificate
server certificate
server private key
Nikdy tam neposielame:
docker-ca-key.pem
Privátny kľúč CA zostáva iba na centrálnom PKI serveri.
Na Docker hoste som vytvoril:
sudo mkdir -p /etc/docker/tls
sudo chmod 700 /etc/docker/tls
Výsledné súbory sú:
/etc/docker/tls/ca.pem
/etc/docker/tls/server-cert.pem
/etc/docker/tls/server-key.pem
Nastavenie práv:
sudo chmod 444 /etc/docker/tls/ca.pem
sudo chmod 444 /etc/docker/tls/server-cert.pem
sudo chmod 400 /etc/docker/tls/server-key.pem
Výsledok:
-r--r--r-- ca.pem
-r--r--r-- server-cert.pem
-r-------- server-key.pem
Overenie certifikátu
Pred reštartom Dockeru som overil, že serverový certifikát je skutočne podpísaný našou CA:
sudo openssl verify \
-CAfile /etc/docker/tls/ca.pem \
/etc/docker/tls/server-cert.pem
Výsledok musí byť:
/etc/docker/tls/server-cert.pem: OK
Ďalej som overil, že certifikát a privátny kľúč patria k sebe.
Certifikát:
sudo openssl x509 \
-in /etc/docker/tls/server-cert.pem \
-pubkey -noout | sha256sum
Privátny kľúč:
sudo openssl pkey \
-in /etc/docker/tls/server-key.pem \
-pubout | sha256sum
Obidva SHA256 hashe musia byť rovnaké.
Napríklad:
25b4724dd654398c4da2b89acf2402f424682ea171c527de363854278acb03f4
25b4724dd654398c4da2b89acf2402f424682ea171c527de363854278acb03f4
Až potom pokračujem konfiguráciou Docker daemonu.
Zapnutie live-restore
Keďže na Docker hostoch bežia produkčné služby, nechcel som, aby krátky reštart samotného Docker daemonu automaticky ukončil všetky bežiace kontajnery.
Preto používam:
"live-restore": true
Na 10.10.8.4, kde pôvodne daemon.json neexistoval, som vytvoril:
sudo nano /etc/docker/daemon.json
{
"live-restore": true
}
Na 10.10.8.5 som už mal definované DNS servery, takže výsledok vyzeral napríklad takto:
{
"dns": ["10.10.8.2", "8.8.8.8", "1.1.1.1"],
"live-restore": true
}
Pred použitím som syntax vždy overil:
sudo dockerd --validate --config-file=/etc/docker/daemon.json
Správny výsledok:
configuration OK
Následne stačí reload:
sudo systemctl reload docker
A kontrola:
sudo docker info | grep -i "Live Restore"
Výsledok:
Live Restore Enabled: true
Systemd override pre Docker API
Keďže Docker už bol spúšťaný cez:
-H fd://
nepridával som parameter hosts do daemon.json.
Namiesto toho som použil systemd override.
Pre host 10.10.8.4:
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo tee /etc/systemd/system/docker.service.d/remote-tls.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://10.10.8.4:2376 --tlsverify --tlscacert=/etc/docker/tls/ca.pem --tlscert=/etc/docker/tls/server-cert.pem --tlskey=/etc/docker/tls/server-key.pem --containerd=/run/containerd/containerd.sock
EOF
Pre 10.10.8.5 je konfigurácia rovnaká, mení sa iba IP:
sudo tee /etc/systemd/system/docker.service.d/remote-tls.conf >/dev/null <<'EOF'
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// -H tcp://10.10.8.5:2376 --tlsverify --tlscacert=/etc/docker/tls/ca.pem --tlscert=/etc/docker/tls/server-cert.pem --tlskey=/etc/docker/tls/server-key.pem --containerd=/run/containerd/containerd.sock
EOF
Dôležitá je dvojica:
-H fd://
-H tcp://10.10.8.x:2376
Prvá zachováva lokálny Docker socket.
Druhá pridáva vzdialený TLS listener.
Parameter:
--tlsverify
znamená, že Docker nebude iba šifrovať komunikáciu, ale bude zároveň vyžadovať platný klientsky certifikát podpísaný našou CA.
Načítanie systemd konfigurácie
Po vytvorení override:
sudo systemctl daemon-reload
Pred reštartom som si ešte skontroloval výsledný ExecStart:
sudo systemctl show docker -p ExecStart --no-pager
Na 10.10.8.5 napríklad:
/usr/bin/dockerd
-H fd://
-H tcp://10.10.8.5:2376
--tlsverify
--tlscacert=/etc/docker/tls/ca.pem
--tlscert=/etc/docker/tls/server-cert.pem
--tlskey=/etc/docker/tls/server-key.pem
--containerd=/run/containerd/containerd.sock
Ak to sedí, Docker môžeme reštartovať:
sudo systemctl restart docker
Kontrola služby
Po reštarte:
sudo systemctl is-active docker
Výsledok:
active
A kontrola TCP listenera:
sudo ss -lntp | grep 2376
Na prvom hoste:
LISTEN ... 10.10.8.4:2376 ... dockerd
Na druhom hoste:
LISTEN ... 10.10.8.5:2376 ... dockerd
Tým je TLS Docker API aktívne.
Kontrola kontajnerov po reštarte
Po zásahu som si vždy overil existujúce kontajnery:
sudo docker ps --format 'table {{.Names}}\t{{.Status}}\t{{.Image}}'
Keďže som mal zapnuté:
Live Restore Enabled: true
kontajnery zostali počas reštartu Docker daemonu bežať.
Pri produkčnom hoste je to veľmi užitočné, ale stále odporúčam robiť podobnú zmenu v čase, keď prípadný krátky výpadok nie je kritický.
Test mTLS z Watchtower servera
Teraz môžeme z 10.10.8.6 overiť, či Docker API skutočne vyžaduje klientsky certifikát.
Prejdeme do PKI adresára:
sudo -i
cd /etc/watchtower-pki
Test so správnym klientskym certifikátom
Pre 10.10.8.4:
curl \
--cacert docker-ca.pem \
--cert watchtower-cert.pem \
--key watchtower-key.pem \
https://10.10.8.4:2376/_ping
Správny výsledok:
OK
Pre druhý host:
curl \
--cacert docker-ca.pem \
--cert watchtower-cert.pem \
--key watchtower-key.pem \
https://10.10.8.5:2376/_ping
Výsledok:
OK
To znamená, že:
server certificate OK
CA trust OK
client certificate OK
private key OK
network connectivity OK
Docker TLS API OK
Test bez klientského certifikátu
Veľmi dôležitý je aj opačný test.
curl \
--cacert docker-ca.pem \
https://10.10.8.4:2376/_ping
Docker spojenie odmietne:
OpenSSL SSL_read:
tlsv13 alert certificate required
Rovnako na .5:
curl \
--cacert docker-ca.pem \
https://10.10.8.5:2376/_ping
opäť:

Odporúčané dodatočné zabezpečenie firewallom
mTLS je hlavná ochranná vrstva, ale v produkčnom prostredí by som port 2376 navyše obmedzil firewallom tak, aby bol dostupný iba z Watchtower servera:
10.10.8.6
Logicky teda:
10.10.8.6 → 10.10.8.4:2376 ALLOW
10.10.8.6 → 10.10.8.5:2376 ALLOW
ostatné zdrojové IP DENY
V mojom pôvodnom nasadení bol UFW na hostoch vypnutý, takže toto je ďalšia hardening vrstva, ktorú je vhodné doplniť podľa používaného firewallu.
Ak sa používa UFW, princíp môže byť napríklad:
sudo ufw allow from 10.10.8.6 to any port 2376 proto tcp
Samotné zapnutie UFW však netreba robiť naslepo na vzdialenom serveri — pred jeho aktiváciou treba mať povolené aj SSH a všetky ostatné produkčné porty.
Upratanie PKI materiálu
Po úspešnom nasadení odporúčam z Docker hostov odstrániť všetky dočasné CSR súbory a najmä akékoľvek omylom prenesené CA privátne kľúče.
Na vzdialenom Docker hoste majú zostať iba:
/etc/docker/tls/
├── ca.pem
├── server-cert.pem
└── server-key.pem
Privátny kľúč CA:
docker-ca-key.pem
má zostať výhradne na PKI serveri.
Výsledok
Po dokončení tejto kapitoly máme pripravenú zabezpečenú komunikačnú vrstvu:

Docker API je dostupné vzdialene, ale klient bez platného certifikátu sa k nemu nedostane.
V ďalšej kapitole môžeme spraviť 4. Nasadenie centrálneho Watchtoweru na 10.10.8.6 — celé docker-compose.yml pre watchtower-84 a watchtower-85, plánovanie o 03:00/03:30, label-enable, cleanup a vysvetlenie, prečo používame dve Watchtower inštancie.
4. Inštalácia centrálneho Watchtower servera
Watchtower v tomto riešení neinštalujem ako systémový balík. Beží ako klasický Docker kontajner na centrálnom serveri:
10.10.8.6
freeipa.ibasterisk.local
Predpokladom je funkčný Docker a Docker Compose.
Kontrola:
docker version
docker compose version
Ak používateľ nemá prístup k /var/run/docker.sock, príkazy je možné spúšťať cez:
sudo docker ...
V mojom prípade používam udržiavaný Watchtower image:
nickfedor/watchtower:latest
Na serveri som vytvoril dva samostatné projekty:
sudo mkdir -p /opt/watchtower-84
sudo mkdir -p /opt/watchtower-85
Každá inštancia spravuje jeden vzdialený Docker daemon:
watchtower-84 → 10.10.8.4
watchtower-85 → 10.10.8.5
Takto zostáva konfigurácia jednoduchá a každá inštancia môže mať vlastný harmonogram.
Helper skript na vytvorenie Docker CA
Namiesto ručného zadávania všetkých príkazov môžeme v dokumentácii ponúknuť aj skript.
Napríklad:
sudo nano /usr/local/sbin/create-docker-ca.sh
Obsah:
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PKI_DIR="/etc/watchtower-pki"
mkdir -p "$PKI_DIR"
chmod 700 "$PKI_DIR"
cd "$PKI_DIR"
if [[ -f docker-ca-key.pem || -f docker-ca.pem ]]; then
echo "ERROR: Docker CA already exists."
exit 1
fi
echo "Creating Docker CA private key..."
openssl genrsa \
-out docker-ca-key.pem \
4096
echo "Creating Docker CA certificate..."
openssl req \
-new \
-x509 \
-days 3650 \
-key docker-ca-key.pem \
-sha256 \
-out docker-ca.pem \
-subj "/CN=iBasterisk Docker CA"
chmod 400 docker-ca-key.pem
chmod 444 docker-ca.pem
echo
echo "Docker CA created:"
echo " $PKI_DIR/docker-ca.pem"
echo " $PKI_DIR/docker-ca-key.pem"
Nastavenie práv:
sudo chmod 700 /usr/local/sbin/create-docker-ca.sh
Spustenie:
sudo /usr/local/sbin/create-docker-ca.sh
Výhodou je, že skript skončí chybou, ak CA už existuje. Nemôže teda nepozornosťou prepísať existujúcu certifikačnú autoritu.
Skript na vytvorenie serverového Docker certifikátu
Ďalší helper môže generovať certifikát pre ľubovoľný Docker host.
sudo nano /usr/local/sbin/create-docker-server-cert.sh
Obsah:
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PKI_DIR="/etc/watchtower-pki"
if [[ $# -ne 2 ]]; then
echo "Usage:"
echo " $0 <IP_ADDRESS> <DNS_NAME>"
echo
echo "Example:"
echo " $0 10.10.8.5 mon.ibasterisk.local"
exit 1
fi
IP="$1"
DNS="$2"
cd "$PKI_DIR"
CA_CERT="docker-ca.pem"
CA_KEY="docker-ca-key.pem"
KEY="docker-${IP}-key.pem"
CSR="docker-${IP}.csr"
CERT="docker-${IP}-cert.pem"
EXT="docker-${IP}-ext.cnf"
if [[ ! -f "$CA_CERT" || ! -f "$CA_KEY" ]]; then
echo "ERROR: Docker CA not found."
exit 1
fi
if [[ -f "$KEY" || -f "$CERT" ]]; then
echo "ERROR: Certificate for $IP already exists."
exit 1
fi
echo "Creating private key for $IP..."
openssl genrsa \
-out "$KEY" \
4096
echo "Creating CSR..."
openssl req \
-subj "/CN=${IP}" \
-sha256 \
-new \
-key "$KEY" \
-out "$CSR"
cat > "$EXT" <<EOF
subjectAltName = IP:${IP},DNS:${DNS}
extendedKeyUsage = serverAuth
EOF
echo "Signing certificate..."
openssl x509 \
-req \
-days 825 \
-sha256 \
-in "$CSR" \
-CA "$CA_CERT" \
-CAkey "$CA_KEY" \
-CAserial docker-ca.srl \
-CAcreateserial \
-out "$CERT" \
-extfile "$EXT"
chmod 400 "$KEY"
chmod 444 "$CERT"
echo
echo "Certificate created:"
echo " $CERT"
echo " $KEY"
echo
echo "Certificate details:"
openssl x509 \
-in "$CERT" \
-noout \
-subject \
-issuer \
-dates
openssl x509 \
-in "$CERT" \
-noout -text |
grep -A2 "Subject Alternative Name"
Práva:
sudo chmod 700 /usr/local/sbin/create-docker-server-cert.sh
Pre prvý host:
sudo /usr/local/sbin/create-docker-server-cert.sh \
10.10.8.4 \
mail-ibasterisk
Pre druhý:
sudo /usr/local/sbin/create-docker-server-cert.sh \
10.10.8.5 \
mon.ibasterisk.local
Takto sa veľmi pekne zjednoduší celá PKI časť dokumentácie.
Skript na vytvorenie Watchtower klientského certifikátu
Rovnako môžeme automatizovať aj klientsky certifikát.
sudo nano /usr/local/sbin/create-watchtower-client-cert.sh
Obsah:
#!/usr/bin/env bash
set -Eeuo pipefail
umask 077
PKI_DIR="/etc/watchtower-pki"
cd "$PKI_DIR"
if [[ ! -f docker-ca.pem || ! -f docker-ca-key.pem ]]; then
echo "ERROR: Docker CA not found."
exit 1
fi
if [[ -f watchtower-key.pem || -f watchtower-cert.pem ]]; then
echo "ERROR: Watchtower certificate already exists."
exit 1
fi
openssl genrsa \
-out watchtower-key.pem \
4096
openssl req \
-subj "/CN=watchtower-10.10.8.6" \
-new \
-sha256 \
-key watchtower-key.pem \
-out watchtower.csr
cat > watchtower-ext.cnf <<'EOF'
extendedKeyUsage = clientAuth
EOF
openssl x509 \
-req \
-days 825 \
-sha256 \
-in watchtower.csr \
-CA docker-ca.pem \
-CAkey docker-ca-key.pem \
-CAserial docker-ca.srl \
-out watchtower-cert.pem \
-extfile watchtower-ext.cnf
chmod 400 watchtower-key.pem
chmod 444 watchtower-cert.pem
echo
echo "Watchtower client certificate created."
echo
openssl x509 \
-in watchtower-cert.pem \
-noout \
-subject \
-issuer \
-dates \
-ext extendedKeyUsage
Spustenie:
sudo chmod 700 /usr/local/sbin/create-watchtower-client-cert.sh
sudo /usr/local/sbin/create-watchtower-client-cert.sh
Na konci chceme:
TLS Web Client Authentication
Bezpečné vytvorenie SMTP secretu
Určite by som do článku dal aj helper, ktorý sme používali na SMTP.
Výhoda je, že App Password nemusíme zadávať priamo do shell príkazu a tým pádom sa neobjaví v shell histórii.
Najprv:
sudo mkdir -p /etc/watchtower-notify
sudo chmod 700 /etc/watchtower-notify
Helper:
sudo nano /tmp/create-watchtower-smtp.py
Obsah:
import os
import getpass
from urllib.parse import quote, urlencode
smtp_host = input("SMTP hostname: ").strip()
mail_to = input("Notification recipient: ").strip()
password = getpass.getpass(
"App Password for updater@ibasterisk.eu: "
)
username = "updater@ibasterisk.eu"
query = urlencode({
"fromaddress": username,
"toaddresses": mail_to,
"encryption": "ExplicitTLS",
"usestarttls": "yes",
"auth": "Plain",
"timeout": "30s",
})
url = (
"smtp://"
+ quote(username, safe="")
+ ":"
+ quote(password, safe="")
+ "@"
+ smtp_host
+ ":587/?"
+ query
)
path = "/etc/watchtower-notify/notification-url.txt"
with open(path, "w") as f:
f.write(url + "\n")
os.chmod(path, 0o600)
print("SMTP secret created:", path)
Spustenie:
sudo python3 /tmp/create-watchtower-smtp.py
Program sa opýta:
SMTP hostname:
Notification recipient:
App Password for updater@ibasterisk.eu:
Pri zadávaní hesla sa nič nezobrazuje.
Po vytvorení:
sudo rm -f /tmp/create-watchtower-smtp.py
Kontrola:
sudo ls -l /etc/watchtower-notify/notification-url.txt
Výsledok:
-rw------- 1 root root ... notification-url.txt
Dôležité:
cat /etc/watchtower-notify/notification-url.txt
nepoužívame, pretože URL obsahuje SMTP App Password.
Inštalácia samotného Watchtoweru
Po pripravení PKI nemusíme inštalovať žiadny ďalší balík.
Vytvoríme Compose projekt:
sudo mkdir -p /opt/watchtower-84
A napríklad:
sudo nano /opt/watchtower-84/docker-compose.yml
Minimálna verzia ešte bez mailov môže vyzerať:
services:
watchtower-84:
image: nickfedor/watchtower:latest
container_name: watchtower-84
restart: unless-stopped
environment:
TZ: Europe/Bratislava
DOCKER_HOST: tcp://10.10.8.4:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /certs
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_SCHEDULE: "0 0 3 * * *"
volumes:
- /etc/watchtower-pki/docker-ca.pem:/certs/ca.pem:ro
- /etc/watchtower-pki/watchtower-cert.pem:/certs/cert.pem:ro
- /etc/watchtower-pki/watchtower-key.pem:/certs/key.pem:ro
Kontrola:
cd /opt/watchtower-84
sudo docker compose config
Stiahnutie image:
sudo docker compose pull
Spustenie:
sudo docker compose up -d
Kontrola:
sudo docker ps --filter name=watchtower-84
A log:
sudo docker logs watchtower-84 --tail 50
Pri úspešnom pripojení sme napríklad videli:
Watchtower 1.22.1 using Docker API v1.55
Next scheduled run: 2026-09-15 03:00:00 CEST
Pre druhý Docker host vytvoríme samostatne:
/opt/watchtower-85/
s:
DOCKER_HOST: tcp://10.10.8.5:2376
WATCHTOWER_SCHEDULE: "0 30 3 * * *"
Teda:
03:00 → Docker 10.10.8.4
03:30 → Docker 10.10.8.5
5. Finálna konfigurácia Watchtoweru s e-mailovými notifikáciami
Po overení komunikácie cez mTLS a základnom spustení Watchtoweru môžeme prejsť na finálnu konfiguráciu, ktorú používam v produkcii.
Na centrálnom serveri 10.10.8.6 bežia dve samostatné Watchtower inštancie:
watchtower-84 → Docker host 10.10.8.4
watchtower-85 → Docker host 10.10.8.5
Každá inštancia má vlastný harmonogram, vlastné označenie v notifikáciách a pripája sa iba k jednému vzdialenému Docker daemonu.
Aktualizácie som rozdelil časovo:
03:00 → 10.10.8.4
03:30 → 10.10.8.5
Tým sa oba servery nekontrolujú a prípadne neaktualizujú naraz.
5.1 Šablóna e-mailových notifikácií
Najskôr som vytvoril spoločnú šablónu, ktorú používajú obe Watchtower inštancie:
sudo nano /etc/watchtower-notify/update-template.txt
Obsah:
{{- if .Report -}}
{{- with .Report -}}
{{- if or .Updated .Failed -}}
Watchtower - {{.Host}}
Scanned: {{len .Scanned}}
Updated: {{len .Updated}}
Failed: {{len .Failed}}
{{- range .Updated}}
UPDATED: {{.Name}}
Image: {{.ImageName}}
{{- end}}
{{- range .Failed}}
FAILED: {{.Name}}
Image: {{.ImageName}}
Error: {{.Error}}
{{- end}}
{{- end -}}
{{- end -}}
{{- end -}}
Následne som nastavil práva:
sudo chmod 444 /etc/watchtower-notify/update-template.txt
Táto šablóna je zámerne navrhnutá tak, aby vytvorila obsah správy iba v prípade, že:
kontajner bol aktualizovaný
alebo
aktualizácia skončila chybou
Ak Watchtower iba skontroluje image a nič sa nezmenilo, šablóna nevytvorí obsah správy a zbytočný e-mail sa neodošle.
Výsledný adresár s notifikačnými súbormi teda obsahuje:
/etc/watchtower-notify/
├── notification-url.txt
└── update-template.txt
SMTP URL obsahujúce App Password zostáva:
-rw------- root root notification-url.txt
zatiaľ čo samotná šablóna môže byť iba na čítanie:
-r--r--r-- root root update-template.txt
5.2 Finálna konfigurácia watchtower-84
Prvá inštancia spravuje Docker host:
10.10.8.4
Konfigurácia sa nachádza v:
/opt/watchtower-84/docker-compose.yml
Súbor som vytvoril:
sudo nano /opt/watchtower-84/docker-compose.yml
Finálna konfigurácia:
services:
watchtower-84:
image: nickfedor/watchtower:latest
container_name: watchtower-84
restart: unless-stopped
environment:
TZ: Europe/Bratislava
DOCKER_HOST: tcp://10.10.8.4:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /certs
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_SCHEDULE: "0 0 3 * * *"
WATCHTOWER_NOTIFICATION_URL: /notify/notification-url.txt
WATCHTOWER_NOTIFICATION_REPORT: "true"
WATCHTOWER_NOTIFICATION_TEMPLATE_FILE: /notify/update-template.txt
WATCHTOWER_NO_STARTUP_MESSAGE: "true"
WATCHTOWER_NOTIFICATIONS_HOSTNAME: "docker-10.10.8.4"
volumes:
- /etc/watchtower-pki/docker-ca.pem:/certs/ca.pem:ro
- /etc/watchtower-pki/watchtower-cert.pem:/certs/cert.pem:ro
- /etc/watchtower-pki/watchtower-key.pem:/certs/key.pem:ro
- /etc/watchtower-notify/notification-url.txt:/notify/notification-url.txt:ro
- /etc/watchtower-notify/update-template.txt:/notify/update-template.txt:ro
Pred spustením som konfiguráciu overil:
cd /opt/watchtower-84
sudo docker compose config
Ak Compose nehlási chybu, image môžeme stiahnuť:
sudo docker compose pull
a následne spustiť:
sudo docker compose up -d
Kontrola:
sudo docker ps --filter name=watchtower-84
Log:
sudo docker logs watchtower-84 --tail 50
Pri správnej konfigurácii Watchtower oznámi, že sa pripojil k vzdialenému Docker API a vypočíta čas ďalšej kontroly.
5.3 Finálna konfigurácia watchtower-85
Druhá inštancia spravuje:
10.10.8.5
Compose súbor sa nachádza v:
/opt/watchtower-85/docker-compose.yml
Vytvorenie:
sudo nano /opt/watchtower-85/docker-compose.yml
Obsah:
services:
watchtower-85:
image: nickfedor/watchtower:latest
container_name: watchtower-85
restart: unless-stopped
environment:
TZ: Europe/Bratislava
DOCKER_HOST: tcp://10.10.8.5:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /certs
WATCHTOWER_LABEL_ENABLE: "true"
WATCHTOWER_CLEANUP: "true"
WATCHTOWER_SCHEDULE: "0 30 3 * * *"
WATCHTOWER_NOTIFICATION_URL: /notify/notification-url.txt
WATCHTOWER_NOTIFICATION_REPORT: "true"
WATCHTOWER_NOTIFICATION_TEMPLATE_FILE: /notify/update-template.txt
WATCHTOWER_NO_STARTUP_MESSAGE: "true"
WATCHTOWER_NOTIFICATIONS_HOSTNAME: "docker-10.10.8.5"
volumes:
- /etc/watchtower-pki/docker-ca.pem:/certs/ca.pem:ro
- /etc/watchtower-pki/watchtower-cert.pem:/certs/cert.pem:ro
- /etc/watchtower-pki/watchtower-key.pem:/certs/key.pem:ro
- /etc/watchtower-notify/notification-url.txt:/notify/notification-url.txt:ro
- /etc/watchtower-notify/update-template.txt:/notify/update-template.txt:ro
Kontrola konfigurácie:
cd /opt/watchtower-85
sudo docker compose config
Stiahnutie image:
sudo docker compose pull
Spustenie:
sudo docker compose up -d
Kontrola:
sudo docker ps --filter name=watchtower-85
A log:
sudo docker logs watchtower-85 --tail 50
5.4 Čo znamenajú jednotlivé parametre
Najdôležitejšia časť konfigurácie je:
DOCKER_HOST: tcp://10.10.8.4:2376
DOCKER_TLS_VERIFY: "1"
DOCKER_CERT_PATH: /certs
DOCKER_HOST určuje vzdialený Docker daemon.
DOCKER_TLS_VERIFY zapína overovanie TLS certifikátov a DOCKER_CERT_PATH určuje adresár, kde Watchtower nájde:
ca.pem
cert.pem
key.pem
Tieto názvy vzniknú pomocou bind mountov:
- /etc/watchtower-pki/docker-ca.pem:/certs/ca.pem:ro
- /etc/watchtower-pki/watchtower-cert.pem:/certs/cert.pem:ro
- /etc/watchtower-pki/watchtower-key.pem:/certs/key.pem:ro
Watchtower teda privátny kľúč nekopíruje do kontajnera natrvalo. Súbor je do kontajnera pripojený iba na čítanie.
Parameter:
WATCHTOWER_LABEL_ENABLE: "true"
zapína label-only režim.
To znamená, že Watchtower nebude aktualizovať všetky kontajnery na vzdialenom serveri. Spracuje iba tie, ktoré majú:
labels:
- "com.centurylinklabs.watchtower.enable=true"
Parameter:
WATCHTOWER_CLEANUP: "true"
spôsobí, že po úspešnej aktualizácii Watchtower odstráni starý image, ktorý už nie je potrebný.
Bez cleanupu by sa na Docker hostoch postupne hromadili staré image.
5.5 Harmonogram aktualizácií
Watchtower používa pri WATCHTOWER_SCHEDULE cron výraz so sekundami.
Prvá inštancia má:
WATCHTOWER_SCHEDULE: "0 0 3 * * *"
čo znamená:
sekunda: 0
minúta: 0
hodina: 3
deň: každý
mesiac: každý
deň týždňa: každý
Teda každý deň o:
03:00
Druhá inštancia používa:
WATCHTOWER_SCHEDULE: "0 30 3 * * *"
Čiže:
03:30
Časové pásmo zabezpečuje:
TZ: Europe/Bratislava
Takto sa harmonogram riadi lokálnym časom vrátane prechodu medzi letným a zimným časom.
5.6 Nastavenie notifikácií
Watchtower dostáva SMTP konfiguráciu zo súboru:
WATCHTOWER_NOTIFICATION_URL: /notify/notification-url.txt
Súbor je do kontajnera pripojený iba na čítanie:
- /etc/watchtower-notify/notification-url.txt:/notify/notification-url.txt:ro
Vďaka tomu sa SMTP App Password nenachádza priamo v:
docker-compose.yml
Reportovací režim zapína:
WATCHTOWER_NOTIFICATION_REPORT: "true"
a vlastnú šablónu určuje:
WATCHTOWER_NOTIFICATION_TEMPLATE_FILE: /notify/update-template.txt
Parameter:
WATCHTOWER_NO_STARTUP_MESSAGE: "true"
zabraňuje tomu, aby Watchtower posielal zbytočnú notifikáciu pri každom svojom spustení.
Nakoniec používam:
WATCHTOWER_NOTIFICATIONS_HOSTNAME: "docker-10.10.8.4"
alebo:
WATCHTOWER_NOTIFICATIONS_HOSTNAME: "docker-10.10.8.5"
V e-maile tak okamžite vidím, ktorého Docker hosta sa správa týka.
Napríklad:
Watchtower - docker-10.10.8.4
Scanned: 3
Updated: 1
Failed: 0
UPDATED: element
Image: vectorim/element-web:latest
Pri chybe môže report vyzerať napríklad:
Watchtower - docker-10.10.8.4
Scanned: 3
Updated: 0
Failed: 1
FAILED: changedetection
Image: dgtlmoon/changedetection.io
Error: ...
5.7 Kontrola pripojených súborov
Po spustení je vhodné overiť, že všetky bind mounty sú naozaj dostupné.
Použil som:
sudo docker inspect watchtower-84 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
A podobne:
sudo docker inspect watchtower-85 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
Výstup by mal obsahovať približne:
/etc/watchtower-pki/docker-ca.pem -> /certs/ca.pem
/etc/watchtower-pki/watchtower-cert.pem -> /certs/cert.pem
/etc/watchtower-pki/watchtower-key.pem -> /certs/key.pem
/etc/watchtower-notify/notification-url.txt -> /notify/notification-url.txt
/etc/watchtower-notify/update-template.txt -> /notify/update-template.txt
Watchtower image neobsahuje klasický shell, takže príkazy typu:
docker exec watchtower-84 sh
nemusia fungovať.
Na kontrolu konfigurácie je preto vhodnejšie používať:
docker inspect
docker inspect
sudo docker ps --filter name=watchtower
Očakávaný stav:
watchtower-84 Up ...
watchtower-85 Up ...
Logy:
sudo docker logs watchtower-84 --tail 30
sudo docker logs watchtower-85 --tail 30
V mojom nasadení sa Watchtower úspešne pripájal cez mTLS k obom vzdialeným Docker hostom a čakal na naplánované spustenie:
watchtower-84 → každý deň 03:00
watchtower-85 → každý deň 03:30
Tým máme centrálny Watchtower nakonfigurovaný. Stále však nebude automaticky aktualizovať žiadny kontajner, pokiaľ mu to explicitne nepovolíme pomocou Docker labelu.
6. Výber kontajnerov pomocou Docker labelov
Watchtower je už pripojený k vzdialeným Docker hostom, ale v konfigurácii používam:
WATCHTOWER_LABEL_ENABLE: "true"
To znamená, že automatická aktualizácia nie je povolená globálne pre všetky kontajnery.
Každý kontajner, ktorý má Watchtower spravovať, musí mať explicitne nastavený label:
labels:
- "com.centurylinklabs.watchtower.enable=true"
Kontajner bez tohto labelu Watchtower pri aktualizácii ignoruje.
Týmto spôsobom môžem na rovnakom Docker hoste prevádzkovať napríklad desať kontajnerov, ale automatické aktualizácie povoliť iba dvom alebo trom.
6.1 Prečo používam opt-in model
Pri automatických aktualizáciách preferujem princíp:
defaultne zakázané
explicitne povolené
Namiesto toho, aby Watchtower aktualizoval všetko a následne som vytváral výnimky, automatickú aktualizáciu povoľujem jednotlivo.
Výsledok môže vyzerať napríklad takto:
10.10.8.4
Element AUTO
ChangeDetection AUTO
Browserless Chrome AUTO
Synapse MANUAL
Mailcow MANUAL
ostatné kontajnery MANUAL
Na druhom serveri:
10.10.8.5
Grafana AUTO
Zabbix MANUAL
MariaDB MANUAL
PostgreSQL MANUAL
Passbolt MANUAL
NetBox MANUAL
FreePBX MANUAL
Takýto model výrazne znižuje riziko, že sa kritická aplikácia alebo databáza aktualizuje bez predchádzajúcej kontroly.
6.2 Povolenie automatickej aktualizácie pre Element
Na Docker hoste:
10.10.8.4
mám aplikáciu Element.
Do príslušnej služby v docker-compose.yaml som pridal:
services:
element:
image: vectorim/element-web:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
Nie je potrebné meniť ostatnú konfiguráciu služby. Dôležité je iba doplnenie sekcie:
labels:
- "com.centurylinklabs.watchtower.enable=true"
Po úprave Compose súboru je potrebné konfiguráciu aplikovať.
V mojom prípade:
cd /home/ivan/docker/chat
sudo docker compose config
Ak je konfigurácia v poriadku:
sudo docker compose up -d element
Keďže sa zmenila konfigurácia kontajnera, Docker ho môže znovu vytvoriť.
Následne som overil label:
sudo docker inspect element \
--format '{{ index .Config.Labels "com.centurylinklabs.watchtower.enable" }}'
Správny výsledok:
true
Od tejto chvíle je Element zaradený medzi kontajnery, ktoré môže watchtower-84 automaticky aktualizovať.
Synapse bežiaci v rovnakom prostredí som naopak ponechal bez Watchtower labelu.
6.3 ChangeDetection
Rovnaký postup som použil pri ChangeDetection.
Do služby:
services:
changedetection:
image: dgtlmoon/changedetection.io
labels:
- "com.centurylinklabs.watchtower.enable=true"
Po úprave konfigurácie:
cd /home/ivan/docker/changedetection
sudo docker compose config
sudo docker compose up -d changedetection
Kontrola:
sudo docker inspect changedetection \
--format '{{ index .Config.Labels "com.centurylinklabs.watchtower.enable" }}'
Výsledok:
true
Watchtower teda môže kontrolovať, či existuje novšia verzia image:
dgtlmoon/changedetection.io
a v prípade potreby kontajner aktualizovať.
6.4 Browserless Chrome
ChangeDetection v mojom prostredí používa aj Browserless Chrome.
Aj tento kontajner som povolil pre automatickú aktualizáciu:
services:
browserless-chrome:
image: browserless/chrome
labels:
- "com.centurylinklabs.watchtower.enable=true"
Po úprave:
sudo docker compose up -d browserless-chrome
Kontrola:
sudo docker inspect browserless-chrome \
--format '{{ index .Config.Labels "com.centurylinklabs.watchtower.enable" }}'
Očakávaný výsledok:
true
Na Docker hoste 10.10.8.4 tak Watchtower aktuálne sleduje:
element
changedetection
browserless-chrome
6.5 Grafana na druhom Docker hoste
Na serveri:
10.10.8.5
som pre automatické aktualizácie vybral Grafanu.
Do Compose konfigurácie som pridal:
services:
grafana:
image: grafana/grafana:latest
labels:
- "com.centurylinklabs.watchtower.enable=true"
Následne:
cd ~/grafana
sudo docker-compose \
-f docker-compose-grafana.yml \
config
Na tomto serveri používam samostatný príkaz docker-compose, preto aj aplikovanie konfigurácie vyzerá:
sudo docker-compose \
-f docker-compose-grafana.yml \
up -d
Kontrola labelu:
sudo docker inspect grafana \
--format '{{ index .Config.Labels "com.centurylinklabs.watchtower.enable" }}'
Výsledok:
true
Grafanu následne spravuje:
watchtower-85
na centrálnom serveri 10.10.8.6.
6.6 Kontrola všetkých Watchtower labelov na hoste
Namiesto kontroly každého kontajnera samostatne môžeme vypísať všetky bežiace kontajnery spolu s hodnotou Watchtower labelu:
sudo docker ps -q | xargs -r sudo docker inspect \
--format '{{.Name}} -> {{index .Config.Labels "com.centurylinklabs.watchtower.enable"}}'
Na 10.10.8.4 môže výsledok vyzerať približne:
/element -> true
/changedetection -> true
/browserless-chrome -> true
/synapse -> <no value>
...
Kontajnery s:
true
sú povolené pre automatické aktualizácie.
Kontajnery bez hodnoty zostávajú mimo automatickej správy Watchtoweru.
Podobne na 10.10.8.5:
/grafana -> true
/zabbix-server -> <no value>
/mariadb -> <no value>
...
6.7 Label nie je jednorazové nastavenie kontajnera
Dôležité je pridávať label priamo do:
docker-compose.yml
alebo:
docker-compose.yaml
Nie iba manuálne do momentálne bežiaceho kontajnera.
Compose súbor predstavuje požadovanú konfiguráciu aplikácie. Ak by sme label pridali iba nejakým jednorazovým príkazom a kontajner neskôr znovu vytvorili z Compose konfigurácie, nastavenie by sa stratilo.
Preto má byť napríklad:
labels:
- "com.centurylinklabs.watchtower.enable=true"
súčasťou trvalej definície služby.
6.8 Čo sa deje počas automatickej aktualizácie
Ak Watchtower pri naplánovanej kontrole zistí novší image pre kontajner označený správnym labelom, typický postup je:
1. skontroluje aktuálne používaný image
2. overí dostupnosť novšej verzie
3. stiahne nový image
4. zastaví pôvodný kontajner
5. vytvorí nový kontajner s rovnakou konfiguráciou
6. spustí nový kontajner
7. pri zapnutom cleanup odstráni starý image
8. odošle report
Dáta uložené vo volume alebo bind mounte týmto postupom zostávajú zachované.
Preto je pri výbere kontajnerov na automatickú aktualizáciu dôležité, aby aplikácia mala svoje persistentné dáta uložené mimo zapisovateľnej vrstvy samotného kontajnera.
6.9 Výsledný stav
Po pridaní labelov vyzerá moja konfigurácia nasledovne:

Tým máme definované, ktoré kontajnery sa smú aktualizovať automaticky a ktoré zostávajú pod manuálnou správou.
7. Testovanie Watchtoweru a overenie reálnej aktualizácie
Po pridaní labelov je vhodné nečakať na nočný harmonogram, ale celé riešenie otestovať manuálne.
Na tento účel môžeme použiť:
/watchtower --run-once
Watchtower vykoná jednu kontrolu, prípadne aktualizáciu, a následne skončí.
7.1 Manuálny test watchtower-84
Na centrálnom serveri:
10.10.8.6
som spustil:
sudo docker exec watchtower-84 /watchtower --run-once
Watchtower sa cez mTLS pripojí k:
10.10.8.4:2376
a skontroluje iba kontajnery označené:
com.centurylinklabs.watchtower.enable=true
V mojom prípade:
Element
ChangeDetection
Browserless Chrome
Ak existuje novší image, Watchtower ho stiahne a kontajner znovu vytvorí.
Pri teste s Elementom bola aktualizácia úspešná.
Log obsahoval informácie v štýle:
Found new image
Stopping /element
Creating /element
Removing image
Session done
a výsledok:
failed=0
scanned=1
updated=1
Po aktualizácii som skontroloval stav:
sudo docker ps --filter name=element
a následne aj samotnú aplikáciu cez webové rozhranie.
Element fungoval korektne aj po automatickej aktualizácii.
7.2 Manuálny test watchtower-85
Rovnaký test som vykonal aj pre druhú inštanciu:
sudo docker exec watchtower-85 /watchtower --run-once
Táto inštancia sa pripája k:
10.10.8.5:2376
a v mojom prípade sleduje Grafanu.
Počas testu Watchtower našiel novšiu verziu image:
grafana/grafana:latest
starý kontajner zastavil, vytvoril nový a odstránil pôvodný image.
Výsledok bol:
failed=0
scanned=1
updated=1
Po aktualizácii:
sudo docker ps --filter name=grafana
a následne som overil Grafanu cez webové rozhranie.
Aj v tomto prípade prebehla automatická aktualizácia úspešne.
7.3 Overenie aktuálne používaného image
Po aktualizácii je vhodné overiť, aký image kontajner skutočne používa.
Napríklad:
sudo docker inspect element \
--format '{{.Image}}'
alebo:
sudo docker inspect grafana \
--format '{{.Image}}'
ID image môžeme porovnať s image dostupným lokálne:
sudo docker image inspect grafana/grafana:latest \
--format '{{.Id}}'
Ak sa hodnoty zhodujú, kontajner používa aktuálne stiahnutý image.
7.4 Test SMTP notifikácie bez aktualizácie kontajnera
E-mailové notifikácie je dobré otestovať samostatne bez toho, aby sme kvôli testu reálne aktualizovali aplikáciu.
Na to môžeme použiť:
--monitor-only
Pre Grafanu som použil:
sudo docker exec \
-e WATCHTOWER_NOTIFICATION_TEMPLATE_FILE= \
watchtower-85 \
/watchtower \
--run-once \
--monitor-only \
--notification-template 'Watchtower SMTP TEST - docker 10.10.8.5' \
grafana
Parameter:
--monitor-only
znamená, že Watchtower môže skontrolovať dostupnosť image, ale kontajner nebude zastavovať ani znovu vytvárať.
V logu som dostal:
Shoutrrr: Mail successfully sent to "updater@ibasterisk.eu"!
a testovací e-mail následne dorazil do mailboxu.
Rovnaký test môžeme spraviť pre prvý Docker host:
sudo docker exec \
-e WATCHTOWER_NOTIFICATION_TEMPLATE_FILE= \
watchtower-84 \
/watchtower \
--run-once \
--monitor-only \
--notification-template 'Watchtower SMTP TEST - docker 10.10.8.4' \
element
Takto vieme samostatne overiť:
SMTP spojenie
STARTTLS
SMTP autentifikáciu
App Password
doručenie správy
Watchtower notification backend
bez zásahu do produkčného kontajnera.
7.5 Overenie notifikačnej konfigurácie
Nastavené premenné môžeme overiť pomocou:
sudo docker inspect watchtower-84 \
--format '{{range .Config.Env}}{{println .}}{{end}}' |
grep -E '^WATCHTOWER_(NOTIFICATION_URL|NOTIFICATION_REPORT|NOTIFICATION_TEMPLATE_FILE|NO_STARTUP_MESSAGE|NOTIFICATIONS_HOSTNAME)='
Rovnako:
sudo docker inspect watchtower-85 \
--format '{{range .Config.Env}}{{println .}}{{end}}' |
grep -E '^WATCHTOWER_(NOTIFICATION_URL|NOTIFICATION_REPORT|NOTIFICATION_TEMPLATE_FILE|NO_STARTUP_MESSAGE|NOTIFICATIONS_HOSTNAME)='
Výstup by mal obsahovať napríklad:
WATCHTOWER_NOTIFICATION_URL=/notify/notification-url.txt
WATCHTOWER_NOTIFICATION_REPORT=true
WATCHTOWER_NOTIFICATION_TEMPLATE_FILE=/notify/update-template.txt
WATCHTOWER_NO_STARTUP_MESSAGE=true
WATCHTOWER_NOTIFICATIONS_HOSTNAME=docker-10.10.8.4
alebo pre druhú inštanciu:
WATCHTOWER_NOTIFICATIONS_HOSTNAME=docker-10.10.8.5
7.6 Test správy pri reálnej aktualizácii
Pri reálnej aktualizácii už používame štandardnú šablónu.
E-mail môže vyzerať napríklad:
Watchtower - docker-10.10.8.4
Scanned: 3
Updated: 1
Failed: 0
UPDATED: element
Image: vectorim/element-web:latest
Pri chybe:
Watchtower - docker-10.10.8.4
Scanned: 3
Updated: 0
Failed: 1
FAILED: changedetection
Image: dgtlmoon/changedetection.io
Error: ...
Takto viem bez prihlasovania na server okamžite zistiť:
ktorý Docker host vykonal kontrolu
koľko kontajnerov bolo skontrolovaných
ktoré kontajnery boli aktualizované
či aktualizácia skončila chybou
7.7 Pozor na prerušenie --run-once
Počas testovania som narazil aj na jednu praktickú situáciu.
Príkaz:
sudo docker exec watchtower-84 /watchtower --run-once
môže počas sťahovania väčšieho image určitý čas neprodukovovať žiadny nový výstup.
To neznamená, že proces stojí.
Najmä image ako:
browserless/chrome
môže byť väčší a jeho sťahovanie chvíľu trvá.
Preto nie je dobré test prerušiť pomocou:
Ctrl+C
a následne okamžite spustiť druhý --run-once.
Pri mojom teste som presne toto urobil a následný Watchtower proces narazil na:
removal of container ... is already in progress
Dôvodom nebola chyba aplikácie, ale to, že pôvodný Watchtower proces už začal kontajner aktualizovať a druhý proces sa pokúsil pracovať s rovnakým kontajnerom.
7.8 Ako overiť stav po prerušenej aktualizácii
Pred ďalším zásahom som najskôr skontroloval kontajnery:
sudo docker ps -a
Následne konkrétny kontajner:
sudo docker inspect changedetection \
--format 'Status={{.State.Status}} Image={{.Image}}'
A aktuálny image:
sudo docker image inspect \
dgtlmoon/changedetection.io:latest \
--format '{{.Id}}'
Ak ID kontajnera zodpovedá aktuálnemu image a kontajner má stav:
running
aktualizácia mohla v skutočnosti prebehnúť úspešne aj napriek tomu, že druhý Watchtower proces nahlásil chybu.
V mojom prípade ChangeDetection už používal nový image a aplikácia normálne fungovala.
Preto pri podobnom stave neodporúčam kontajner okamžite mazať alebo znova vytvárať.
Najskôr treba overiť jeho reálny stav.
7.9 Kontrola aplikácie po aktualizácii
Úspešný Watchtower log ešte automaticky neznamená, že aplikácia funguje správne.
Po každom prvom automatickom update konkrétnej služby preto odporúčam skontrolovať:
stav kontajnera
aplikačné logy
webové rozhranie
persistentné dáta
závislosti na ostatných službách
Kontrola logov napríklad:
sudo docker logs element --tail 50
alebo:
sudo docker logs grafana --tail 50
Prípadne:
sudo docker logs changedetection --tail 50
Aplikáciu následne overím rovnakým spôsobom ako používateľ.
Až keď toto prebehne bez problémov, nechávam kontajner zaradený do automatických aktualizácií.
7.10 Výsledok testovania
Po dokončení testov mám overené všetky hlavné časti riešenia:
mTLS komunikácia OK
Docker API OK
Watchtower remote connection OK
label-only režim OK
automatická aktualizácia OK
cleanup starých image OK
SMTP cez STARTTLS OK
App Password OK
e-mail pri aktualizácii OK
e-mail pri chybe OK
bez e-mailu pri bežnej kontrole OK
Tým je základné nasadenie hotové.
Watchtower teraz každý deň automaticky vykoná:
03:00 → kontrola 10.10.8.4
03:30 → kontrola 10.10.8.5
8. Troubleshooting a najčastejšie problémy
Pri nasadzovaní riešenia sa môže problém objaviť na viacerých miestach — od Docker daemonu, cez TLS certifikáty až po samotný Watchtower alebo SMTP notifikácie.
V tejto kapitole sú najčastejšie problémy, na ktoré som pri nasadení narazil, spolu s postupom kontroly.
8.1 Docker API na porte 2376 nepočúva
Ako prvé je vhodné overiť, či Docker daemon vôbec počúva na zabezpečenom TCP porte:
sudo ss -lntp | grep 2376
Správny výsledok by mal obsahovať napríklad:
LISTEN ... 10.10.8.4:2376 ... dockerd
alebo:
LISTEN ... 10.10.8.5:2376 ... dockerd
Ak sa nič nezobrazí, skontrolujem stav Docker služby:
sudo systemctl status docker --no-pager
a následne výsledný ExecStart:
sudo systemctl show docker -p ExecStart --no-pager
Mal by obsahovať:
-H fd://
-H tcp://10.10.8.x:2376
--tlsverify
Ak som menil systemd override, nezabudnem na:
sudo systemctl daemon-reload
sudo systemctl restart docker
Log Docker daemonu môžem skontrolovať:
sudo journalctl -u docker -n 100 --no-pager
8.2 Docker po zmene konfigurácie nenabehne
Ak sa Docker po zmene daemon.json alebo systemd override nespustí, prvým krokom je overenie syntaxe:
sudo dockerd --validate \
--config-file=/etc/docker/daemon.json
Správny výsledok:
configuration OK
Následne:
sudo systemctl status docker --no-pager
a:
sudo journalctl -u docker -n 100 --no-pager
Častým problémom môže byť napríklad konflikt medzi:
/etc/docker/daemon.json
a parametrami použitými priamo v:
ExecStart=
V mojom prípade som preto parameter hosts nepridával do daemon.json, ale TCP listener som definoval cez systemd override.
8.3 tlsv13 alert certificate required
Ak pri teste:
curl \
--cacert docker-ca.pem \
https://10.10.8.4:2376/_ping
dostanem:
tlsv13 alert certificate required
nie je to chyba.
Práve naopak — znamená to, že Docker daemon vyžaduje klientsky certifikát.
Správny test musí obsahovať:
curl \
--cacert docker-ca.pem \
--cert watchtower-cert.pem \
--key watchtower-key.pem \
https://10.10.8.4:2376/_ping
Výsledok:
ok
Tým máme potvrdené, že mTLS funguje.
8.4 Chyba certifikátu alebo nesprávny SAN
Ak Watchtower alebo curl odmietne serverový certifikát, skontrolujem jeho Subject Alternative Name:
openssl x509 \
-in docker-10.10.8.4-cert.pem \
-noout -text |
grep -A2 "Subject Alternative Name"
Pre prvý host očakávam napríklad:
IP Address:10.10.8.4
DNS:mail-ibasterisk
Pre druhý:
IP Address:10.10.8.5
DNS:mon.ibasterisk.local
Ak sa Watchtower pripája cez IP adresu:
10.10.8.4
musí byť táto IP uvedená v SAN.
Nestačí mať iba:
CN=10.10.8.4
8.5 Overenie, že certifikát patrí k privátnemu kľúču
Pri pochybnostiach môžem porovnať verejný kľúč certifikátu:
sudo openssl x509 \
-in /etc/docker/tls/server-cert.pem \
-pubkey -noout |
sha256sum
s verejným kľúčom odvodeným z privátneho kľúča:
sudo openssl pkey \
-in /etc/docker/tls/server-key.pem \
-pubout |
sha256sum
Obidve hodnoty musia byť rovnaké.
8.6 Watchtower sa nevie pripojiť k vzdialenému Docker hostu
Najskôr otestujem Docker API priamo z centrálneho servera:
curl \
--cacert /etc/watchtower-pki/docker-ca.pem \
--cert /etc/watchtower-pki/watchtower-cert.pem \
--key /etc/watchtower-pki/watchtower-key.pem \
https://10.10.8.4:2376/_ping
Ak dostanem:
ok
sieťová a TLS vrstva funguje.
Potom skontrolujem Watchtower:
sudo docker logs watchtower-84 --tail 100
a environment:
sudo docker inspect watchtower-84 \
--format '{{range .Config.Env}}{{println .}}{{end}}' |
grep -E '^DOCKER_'
Očakávam:
DOCKER_HOST=tcp://10.10.8.4:2376
DOCKER_TLS_VERIFY=1
DOCKER_CERT_PATH=/certs
8.7 Kontrola certifikátov namountovaných do Watchtoweru
Bind mounty môžem skontrolovať pomocou:
sudo docker inspect watchtower-84 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
Očakávam napríklad:
/etc/watchtower-pki/docker-ca.pem -> /certs/ca.pem
/etc/watchtower-pki/watchtower-cert.pem -> /certs/cert.pem
/etc/watchtower-pki/watchtower-key.pem -> /certs/key.pem
Rovnako by mali byť pripojené aj notifikačné súbory:
/etc/watchtower-notify/notification-url.txt -> /notify/notification-url.txt
/etc/watchtower-notify/update-template.txt -> /notify/update-template.txt
8.8 docker exec watchtower-84 sh nefunguje
Pri debugovaní som skúsil:
sudo docker exec watchtower-84 sh
ale Watchtower image neobsahuje klasický shell.
Preto sa konfigurácia nekontroluje vstupom do kontajnera, ale pomocou:
docker inspect
sudo docker inspect watchtower-84
alebo cielene:
sudo docker inspect watchtower-84 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
8.9 Watchtower kontajner neaktualizuje
Ak sa kontajner neaktualizuje, najskôr skontrolujem jeho label:
sudo docker inspect element \
--format '{{ index .Config.Labels "com.centurylinklabs.watchtower.enable" }}'
Správny výsledok:
true
Ak sa nezobrazí nič, Watchtower ho pri zapnutom:
WATCHTOWER_LABEL_ENABLE: "true"
ignoruje.
Label musí byť pridaný priamo do Compose konfigurácie:
labels:
- "com.centurylinklabs.watchtower.enable=true"
a následne treba kontajner znovu vytvoriť:
sudo docker compose up -d
8.10 Watchtower hlási Current container not cached for cleanup
Pri vzdialenom Docker daemon-e sa v logu môže objaviť upozornenie podobné:
Current container not cached for cleanup
V tomto nasadení je to očakávané.
Watchtower samotný beží na:
10.10.8.6
ale Docker daemon, ktorý spravuje, je napríklad:
10.10.8.4
Watchtower teda na vzdialenom Docker hoste nenájde svoj vlastný kontajner.
Táto hláška neznamená, že:
WATCHTOWER_CLEANUP: "true"
nefunguje.
Staré image aktualizovaných aplikácií môže Watchtower na vzdialenom Docker hoste naďalej odstrániť.
8.11 removal of container ... is already in progress
Pri manuálnom testovaní som narazil na chybu:
removal oStalo sa to po tom, čo som počas:f container ... is already in progress
Stalo sa to po tom, čo som počas:
sudo docker exec watchtower-84 /watchtower --run-once
proces prerušil cez:
Ctrl+C
a následne okamžite spustil nový --run-once.
Pôvodný proces už medzitým začal aktualizáciu a druhý proces sa pokúsil pracovať s rovnakým kontajnerom.
Preto je dôležité:
neprerušovať --run-once iba preto, že chvíľu nevypisuje nový text.
Sťahovanie väčších image môže trvať niekoľko minút.
Pred ďalším zásahom skontrolujem:
sudo docker ps -a
potom konkrétny kontajner:
sudo docker inspect changedetection \
--format 'Status={{.State.Status}} Image={{.Image}}'
a aktuálny image:
sudo docker image inspect \
dgtlmoon/changedetection.io:latest \
--format '{{.Id}}'
Ak kontajner:
Status=running
a používa aktuálny image, aktualizácia už pravdepodobne prebehla.
8.12 Watchtower chvíľu nič nevypisuje
To nemusí znamenať, že zamrzol.
Pri väčšom image, napríklad:
browserless/chrome
môže sťahovanie trvať dlhšie.
V inom termináli môžem sledovať napríklad:
sudo docker ps
alebo stav image na vzdialenom Docker hoste:
sudo docker images
Najbezpečnejšie je nechať --run-once dokončiť.
8.13 E-mailová notifikácia nepríde
Najskôr overím SMTP server ešte mimo Watchtoweru:
openssl s_client \
-starttls smtp \
-connect mail.ibasterisk.eu:587 \
-servername mail.ibasterisk.eu \
-verify_return_error
TLS spojenie musí prebehnúť úspešne.
Následne skontrolujem existenciu secretu:
sudo ls -l \
/etc/watchtower-notify/notification-url.txt
Práva by mali byť:
-rw------- root root
Obsah súboru nepublikujem ani nevypisujem, pretože obsahuje SMTP App Password.
Skontrolujem mount:
sudo docker inspect watchtower-84 \
--format '{{range .Mounts}}{{println .Source "->" .Destination}}{{end}}'
a environment:
sudo docker inspect watchtower-84 \
--format '{{range .Config.Env}}{{println .}}{{end}}' |
grep '^WATCHTOWER_NOTIFICATION'
8.14 Bezpečný SMTP test
SMTP môžem otestovať bez reálnej aktualizácie kontajnera:
sudo docker exec \
-e WATCHTOWER_NOTIFICATION_TEMPLATE_FILE= \
watchtower-84 \
/watchtower \
--run-once \
--monitor-only \
--notification-template 'Watchtower SMTP TEST - docker 10.10.8.4' \
element
Pri úspechu sa v logu objaví napríklad:
Shoutrrr: Mail successfully sent to "updater@ibasterisk.eu"!
Pre druhý host:
sudo docker exec \
-e WATCHTOWER_NOTIFICATION_TEMPLATE_FILE= \
watchtower-85 \
/watchtower \
--run-once \
--monitor-only \
--notification-template 'Watchtower SMTP TEST - docker 10.10.8.5' \
grafana
Parameter:
--monitor-only
zabezpečí, že počas testu nedôjde k aktualizácii kontajnera.
8.15 Po aktualizácii aplikácia nefunguje
Watchtower môže technicky vykonať aktualizáciu úspešne, ale samotná aplikácia môže mať problém s novou verziou.
Preto kontrolujem:
sudo docker ps
a log aplikácie:
sudo docker logs <container> --tail 100
Napríklad:
sudo docker logs element --tail 100
alebo:
sudo docker logs grafana --tail 100
Dôležité je tiež overiť samotnú aplikáciu cez webové rozhranie.
Práve preto automaticky neaktualizujem kritické služby a databázy, pri ktorých môže nová verzia vyžadovať migráciu.
8.16 Kontajner používa nový image?
Aktuálny image použitý kontajnerom:
sudo docker inspect changedetection \
--format '{{.Image}}'
ID image dostupného pod tagom:
sudo docker image inspect \
dgtlmoon/changedetection.io:latest \
--format '{{.Id}}'
Ak sú hodnoty rovnaké, kontajner používa aktuálne stiahnutý image.
8.17 Rýchla diagnostika
Ak niečo nefunguje, praktický postup je kontrolovať riešenie postupne od najnižšej vrstvy:

Takýto postup je lepší ako náhodne meniť viacero častí konfigurácie naraz.
Výsledok
Po vyriešení týchto bodov máme pomerne jednoducho diagnostikovateľné riešenie.
Najdôležitejšie je oddeliť jednotlivé vrstvy:

Ak každú vrstvu otestujeme samostatne, väčšina problémov sa dá lokalizovať pomerne rýchlo.
9. Záver
Cieľom celého riešenia bolo vytvoriť centrálne a zároveň bezpečne riadené automatické aktualizácie Docker kontajnerov bez toho, aby som musel nasadzovať Watchtower na každý Docker host samostatne.
Výsledná architektúra používa centrálny server 10.10.8.6, na ktorom bežia dve nezávislé Watchtower inštancie. Tie sa cez mTLS pripájajú k vzdialeným Docker daemon-om na 10.10.8.4 a 10.10.8.5.
Automatické aktualizácie sú povolené iba pre explicitne označené kontajnery pomocou labelu:
labels:
- "com.centurylinklabs.watchtower.enable=true"
Vďaka tomu zostávajú kritické služby, databázy a infraštruktúrne komponenty mimo automatického update procesu a môžu byť aktualizované riadeným spôsobom po kontrole release notes a vytvorení zálohy.
Komunikácia s Docker API je zabezpečená pomocou vlastnej certifikačnej autority a obojstranne overovaného TLS spojenia. Docker daemon teda neprijme klienta bez platného certifikátu, čo výrazne znižuje riziko zneužitia vzdialeného API.
Súčasťou riešenia sú aj SMTP notifikácie. Watchtower neposiela e-mail pri každej kontrole, ale iba v prípade úspešnej aktualizácie alebo chyby. Výsledkom je menej zbytočných správ a zároveň okamžitá informácia v prípade, že sa na niektorom Docker hoste niečo zmení.
Výsledný proces je teda:

Takto mám aktualizácie vybraných Docker služieb automatizované, ale zároveň zostáva zachovaná kontrola nad tým, čo sa môže aktualizovať automaticky a čo musí zostať manuálne spravované.
Celé riešenie kombinuje:
Docker
Watchtower
mTLS
vlastnú Docker CA
label-only aktualizácie
cleanup starých image
SMTP cez STARTTLS
App Password
e-mailové reporty
Výsledkom je jednoduchý, centrálne spravovaný a relatívne bezpečný spôsob automatizácie aktualizácií Docker kontajnerov vo vlastnej infraštruktúre.