Bezpečný Docker na Ubuntu Serveru

Co je Docker a jak ho na serveru bezpečně nainstalovat a nastavit, krok za krokem

Instalace z ověřeného zdroje, přístup jen pro správce, porty, které neobejdou firewall, a kontejnery s minimem práv. Jako alternativa režim, ve kterém Docker neběží s právy správce vůbec.

Vladimír NeporAutorIng. Vladimír Nepor
Aktualizováno
Bezpečný Docker: balíčky z oficiálního repozitáře s ověřeným podpisovým klíčem, démon běžící jako root ovládá jen sudo (bez skupiny docker) a kontejnery publikují porty na 127.0.0.1 s pojistkou DOCKER-USER ve firewallu.APT repozitářdownload.docker.comSigned-By: docker.ascověřený klíč…8D81 803C 0EBF CD88dockerdsudo docker …daemon.json ✓jen přes sudo,bez skupiny dockerkontejnery127.0.0.1:8080:8080ve výchozím stavu skrytéDOCKER-USER: DROP04 · INSTALACEOficiální APT repozitářkontrola otisku klíče, žádné curl | shkapitola →06 · DÉMONBezpečné výchozí hodnotydaemon.json: logy, porty, oprávněníkapitola →07 · SÍŤPorty, které respektují firewall127.0.0.1 + DOCKER-USERkapitola →08 · KONTEJNERYZabezpečený Composebez roota, jen pro čtení, limitykapitola →
Obsah návodu

00 · Přehled

Bezpečný Docker v kostce

Návod vysvětlí, co je Docker, a krok za krokem ho bezpečně nainstaluje a nastaví na Ubuntu Serveru. Žádný hromadný skript: u každého kroku je napsané, co dělá, proč je potřeba a jak ověřit, že se povedl.

Celé nastavení v kostce

  1. Ověřená instalace - Docker z oficiálního zdroje, pravost balíčků ověřená podpisovým klíčem
  2. Přístup jen pro správce - Docker ovládá jen sudo, skupina docker zůstane prázdná
  3. Bezpečné výchozí nastavení - logy nezaplní disk, porty kontejnerů se otevírají jen pro server samotný
  4. Firewall i pro kontejnery - omylem otevřený port zůstane zvenku zavřený
  5. Kontejnery s minimem práv - bez práv správce, bez možnosti měnit vlastní soubory, s omezeným výkonem a pamětí

Co potřebujete vyřešit?

OtázkaKde najdete odpověď
Co je Docker a potřebuji ho?Co je Docker →
Proč je Docker bezpečnostní riziko?Bezpečnostní model →
Jak ho bezpečně nainstalovat?Instalace →
Můžu se přidat do skupiny docker?Kdo smí Docker ovládat →
Proč je kontejner dostupný z internetu, i když ho firewall blokuje?Síť a firewall →
Jak má vypadat bezpečný compose.yaml?Kontejnery →
Jde to úplně bez práv správce?Rootless režim →
Něco nefunguje.Řešení problémů →
Rozsah

Ověřeno podle oficiální dokumentace k 27. 10. 2026: Docker Engine 29.9, Docker Compose 5.6, Ubuntu Server 22.04, 24.04 a 26.04 LTS. Docker Desktop a Swarm návod neřeší.

01 · Docker

Co je Docker a k čemu slouží

Docker spouští aplikace v kontejnerech. Kontejner obsahuje aplikaci a vše, co ke svému běhu potřebuje (knihovny, nastavení). Na serveru běží jako běžný program, vidí ale jen své soubory a svou síť a má omezený výkon a paměť. Aplikace proto běží všude stejně a navzájem si nepřekáží.

Kontejner vs. virtuální stroj

Kontejner (Docker)Virtuální stroj
Operační systémSdílí jádro systému se serveremMá vlastní celý systém
NáročnostStart za sekundy, minimum paměti navícStart v desítkách sekund, stovky MB až GB paměti
Oddělení od serveruSlabší, záleží na správném nastaveníSilnější
Hodí se proAplikace a služby: web, databáze, automatizaceJiný operační systém, nedůvěryhodný kód

Kontejner tedy neodděluje aplikaci od serveru tak pevně jako virtuální stroj. Jeho izolace je dobrá, ale jen když je správně nastavená. O tom je zbytek návodu.

Pojmy, které budete potřebovat

PojemVýznam
ImageŠablona, ze které se kontejner spouští (např. nginx:1.29). Stahuje se z registry, typicky Docker Hub.
DigestJedinečný otisk konkrétní verze image (sha256:…). Na rozdíl od označení verze (tagu) se nikdy nezmění.
KontejnerSpuštěná image. Co aplikace zapíše dovnitř, zmizí se smazáním kontejneru.
VolumeÚložiště pro data, která mají přežít smazání kontejneru (např. databáze).
Démon (dockerd)Služba na pozadí, která kontejnery spouští a spravuje. Příkaz docker jí jen posílá pokyny.
Socket (docker.sock)Soubor, přes který příkaz docker s démonem komunikuje. Kdo do něj smí zapisovat, ovládá Docker.
Root, sudoRoot je správce serveru s neomezenými právy. sudo spustí jeden příkaz s právy roota po ověření hesla.
PortČíslo „dveří“ na serveru, na kterých služba přijímá spojení (web 80 a 443, SSH 22).
Publikovaný portPort serveru, který Docker přesměruje do kontejneru. Bez něj je kontejner zvenku nedostupný.
Firewall (UFW)Pravidla, která rozhodují, která spojení na server pustit. UFW je firewall Ubuntu.
CapabilitiesJednotlivá dílčí práva roota (např. měnit síť). Kontejner by jich měl mít co nejméně.
Docker ComposePopis kontejnerů a jejich nastavení v souboru compose.yaml, spouští se příkazem docker compose.

Kdy Docker na serveru dává smysl

Docker ano

  • Aplikace, které výrobce dodává jako image (Home Assistant, n8n, Nextcloud…)
  • Víc aplikací na jednom serveru, které potřebují různé verze knihoven
  • Nastavení uložené v souborech, snadno přenositelné a verzovatelné v Gitu

Spíš klasická instalace

  • Jedna služba, kterou Ubuntu nabízí jako balíček (nginx, postgresql) – aktualizuje se sama
  • Nedůvěryhodný kód nebo potřeba silného oddělení – patří do virtuálního stroje

02 · Rizika

Kde je Docker zranitelný

Docker má čtyři slabá místa. Každý další krok návodu řeší některé z nich.

  1. Docker běží s právy správce (roota). Kdo mu může zadávat příkazy, převezme celý server: stačí spustit kontejner, který vidí všechny soubory serveru.
  2. Přístup k Dockeru = práva správce. Skupina docker proto dává plná práva bez hesla. Socket docker.sock nesmí do kontejnerů ani na síť.
  3. Docker obchází firewall. Spojení na publikovaný port přesměruje do kontejneru dřív, než ho UFW zkontroluje. A port ve výchozím stavu otevře na všech adresách serveru, tedy i do internetu.
  4. Kontejnery sdílí jádro serveru. Výchozí omezení jsou rozumná, ale volby jako privileged nebo network_mode: host je ruší.
Příchozí paket z internetu nebo LAN přijde na eth0. V nat PREROUTING Docker přepíše cíl publikovaného portu na IP kontejneru (DNAT). Provoz pro host jde řetězcem INPUT s pravidly UFW k procesu na hostu, např. sshd. Provoz pro kontejner jde řetězcem FORWARD: nejdřív DOCKER-USER (vaše pravidla), pak pravidla Dockeru, pak kontejner. Pravidla UFW v INPUT publikované porty kontejnerů nikdy nevidí.internet / LANklient se připojuje na port serverueth0nat PREROUTING (Docker)publikovaný port → DNAT na IP kontejnerupro hostpro kontejnerINPUT: UFWallow / deny podle ufw statusFORWARD: DOCKER-USERvaše pravidla → pravidla Dockeruproces na hostusshd, reverse proxy na hostukontejnerpublikovaný portPravidla UFW (INPUT) publikované porty kontejnerů nevidí.
UFW kontroluje jen spojení určená serveru (řetězec INPUT). Spojení do kontejneru jde jinudy (FORWARD), kam pravidla UFW nedosáhnou. Pravidla pro kontejnery proto patří do řetězce DOCKER-USER (kapitola 07).
Shrnutí
  • Instalace z ověřeného zdroje (kapitola 04).
  • Docker ovládá jen správce přes sudo, nebo běží bez práv správce (kapitoly 05 a 09).
  • Bezpečné výchozí nastavení a firewall i pro kontejnery (kapitoly 06 a 07).
  • Každý kontejner s minimem práv (kapitola 08).

03 · Než začnete

Předpoklady a volba režimu

Co připravit předem, jaký režim zvolit a které hodnoty v příkazech nahradit svými.

Běžný, nebo rootless režim?

Běžný (rootful)Rootless
Docker běží jakosprávce (root)obyčejný uživatel
Když útočník prolomí kontejnerzíská celý serverzíská jen uživatele <DOCKER_USER>
Firewall UFWporty kontejnerů obchází (řeší kapitola 07)platí i pro kontejnery
Omezenížádnáporty pod 1024 až po úpravě, některé image nefungují, kontejner nevidí skutečnou IP návštěvníka
Kdy zvolitVětšina serverů, funguje se všímChcete co nejmenší dopad případného útoku a omezení vám nevadí

Instalace (kapitola 04) je pro oba režimy stejná. Kapitoly 05–08 popisují běžný režim, rootless navazuje v kapitole 09.

Hodnoty, které nahradíte svými

PlaceholderVýznamPříklad / kde ho zjistit
<ADMIN_USER>Váš uživatel s právem sudovladimir
<DOCKER_USER>Uživatel bez sudo pro rootless režim (jen kapitola 09)dockerapp
<EXT_IF>Síťové rozhraní serveru směrem do internetuip -4 route show default (slovo za dev)
<LAN_SUBNET>Vaše lokální síť (volitelné)192.168.88.0/24
<SERVER>Adresa, přes kterou se k serveru připojujete SSHTailscale IP, IP v lokální síti, hostname
<SSH_PORT>Port SSHsudo sshd -T | grep -i '^port'
<PUBLIC_IP>Veřejná IP serveru (u serveru doma IP routeru)panel poskytovatele / router
<PROJECT>Název projektu (složka v /opt/stacks/)web
<DIGEST>Otisk verze imagekapitola 08
Záložní přístup

Samotná instalace přístup k serveru neohrozí. Riziková je jen úprava firewallu v kapitole 07: chyba v souboru může zabránit načtení firewallu. Předem si proto ověřte jiný přístup k serveru (konzole poskytovatele, fyzický přístup, SSH z lokální sítě).

04 · Instalace

Instalace z oficiálního repozitáře

Docker instalujeme z jeho oficiálního repozitáře (zdroje balíčků pro APT). Má nejnovější verze a bezpečnostní opravy dostává jako první. Než mu systém začne věřit, ověříme podpisový klíč: ten zaručí, že balíčky opravdu pocházejí od Dockeru.

Nepoužíváme

  • Skript get.docker.com (curl | sh) – spustí cizí kód bez kontroly; Docker ho doporučuje jen pro testování
  • Snap docker – data jinde a vlastní aktualizace, návody s ním nepočítají
  • Balíček docker.io z Ubuntu spolu s balíčky Dockeru – navzájem kolidují

Používáme

  • Oficiální repozitář Dockeru
  • Podpisový klíč platný jen pro tento repozitář
  • Kontrolu klíče a původu balíčků před instalací

Krok 1: Odstraňte konfliktní balíčky

Starší neoficiální balíčky kolidují s oficiálními. První příkaz vypíše, které jsou nainstalované; když nevypíše nic, druhý přeskočte. Data kontejnerů zůstanou.

Terminál
# list unofficial packages that conflict with Docker's own (no output = nothing to remove)
dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc 2>/dev/null

# remove only those that are installed; /var/lib/docker (images, volumes) is kept
sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc docker-buildx podman-docker containerd runc 2>/dev/null | cut -f1)
Terminál
# only if Docker was installed as a snap (this deletes the snap's data - back it up first)
sudo snap remove docker

Krok 2: Stáhněte a ověřte podpisový klíč

Klíč stáhneme do dočasného souboru a porovnáme jeho otisk s oficiální hodnotou 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88. Výstup musí odpovídat druhému bloku.

Terminál
sudo apt update
sudo apt install ca-certificates curl gnupg

# download the key to a temporary file and inspect it before trusting it
curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /tmp/docker.asc
gpg --show-keys --with-fingerprint --keyid-format long /tmp/docker.asc
Výstup
pub   rsa4096/8D81803C0EBFCD88 2017-02-22 [SCEA]
      Key fingerprint = 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid                            Docker Release (CE deb) <docker@docker.com>
sub   rsa4096/7EA0A9C3F273FCD8 2017-02-22 [S]
Otisk nesouhlasí?

Nepokračujte. Smažte /tmp/docker.asc a zjistěte příčinu (proxy, podvržená adresa, změna klíče oznámená na docs.docker.com).

Krok 3: Uložte klíč

Klíč uložíme tak, aby mu systém věřil jen u repozitáře Dockeru, ne u ostatních.

Terminál
# keyring for locally added repositories; the key is trusted only by the repo that points to it
sudo install -d -m 0755 -o root -g root /etc/apt/keyrings
sudo install -m 0644 -o root -g root /tmp/docker.asc /etc/apt/keyrings/docker.asc
rm /tmp/docker.asc

Krok 4: Přidejte repozitář

Příkaz zjistí verzi Ubuntu a typ procesoru, vypíše je ke kontrole a zapíše repozitář. Druhý blok ověří, že se Docker bude instalovat odtud.

Terminál
CODENAME="$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")"
ARCH="$(dpkg --print-architecture)"
echo "$CODENAME $ARCH"     # e.g. noble amd64

sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $CODENAME
Components: stable
Architectures: $ARCH
Signed-By: /etc/apt/keyrings/docker.asc
EOF

cat /etc/apt/sources.list.d/docker.sources
Terminál
sudo apt update
apt-cache policy docker-ce
# Candidate: 5:29.x…  from  https://download.docker.com/linux/ubuntu <CODENAME>/stable

Ve výpisu musí být verze 5:29.x a adresa download.docker.com. Chyba NO_PUBKEY znamená, že nesedí cesta ke klíči.

Krok 5: Nainstalujte Docker a vyzkoušejte ho

Nainstaluje Docker, příkaz docker a rozšíření pro sestavování images a pro Compose. Docker se spustí sám a startuje se serverem. Testovací kontejner hello-world ověří, že vše funguje; pak ho smažeme.

Terminál
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Terminál
systemctl status docker containerd --no-pager
sudo docker version
sudo docker run --rm hello-world
sudo docker image rm hello-world

Po přechodu na novou verzi Ubuntu upravte v docker.sources řádek Suites: na její codename. Přechod cizí repozitáře vypíná.

05 · Přístup

Kdo smí Docker ovládat

Mnoho návodů radí přidat se do skupiny docker, aby nebylo nutné psát sudo. Na serveru to nedělejte: člen skupiny docker má bez hesla plná práva správce. Stačí mu jeden příkaz:

Terminál
# anyone in the docker group gets a root shell on the host with this one line:
docker run --rm -it -v /:/host alpine chroot /host

Obejde tím heslo, 2FA i záznam v logu, které vyžaduje sudo. Útočníkovi stačí ukradený SSH klíč. Docker proto spouštějte přes sudo docker. Kdo chce Docker bez sudo, má použít rootless režim.

Skupina docker musí zůstat prázdná

Terminál
getent group docker      # → docker:x:<GID>:   (nobody after the last colon)

# if someone is there, remove them:
sudo gpasswd -d <USER> docker
Docker nikdy neovládejte přes síť

Nezapínejte vzdálený přístup k Dockeru (port 2375/2376, volba hosts, „Expose daemon on tcp“). Kdo se k němu připojí, ovládá server. Server spravujte přes SSH.

docker.sock do kontejneru nepatří

Některé nástroje (Portainer, Traefik, Watchtower, různé dashboardy) chtějí do svého kontejneru připojit docker.sock. Kdo pak ovládne takový kontejner, ovládne celý server. Pokud nástroj opravdu potřebujete, dejte mezi něj a socket prostředníka (socket proxy), který povolí jen čtení.

06 · Nastavení

Bezpečné výchozí nastavení

Výchozí nastavení Dockeru je pohodlné, ne bezpečné. Soubor /etc/docker/daemon.json ho změní tak, aby jedna chyba v nastavení kontejneru neotevřela port do internetu ani nezaplnila disk. Pokud soubor už existuje, hodnoty doplňte a nic nezdvojujte.

/etc/docker/daemon.json
sudo tee /etc/docker/daemon.json >/dev/null <<'EOF'
{
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "5"
  },
  "live-restore": true,
  "no-new-privileges": true,
  "icc": false,
  "ip": "127.0.0.1",
  "default-network-opts": {
    "bridge": {
      "com.docker.network.bridge.host_binding_ipv4": "127.0.0.1"
    }
  },
  "default-address-pools": [
    { "base": "172.30.0.0/16", "size": 24 }
  ]
}
EOF
sudo chmod 0644 /etc/docker/daemon.json
NastaveníCo dělá
log-driver, log-optsVýchozí logy se nikdy nemažou a časem zaplní disk. Takto se komprimují a mají nejvýš 5 × 10 MB na kontejner.
live-restoreKontejnery běží dál i při restartu nebo aktualizaci Dockeru.
no-new-privilegesProgram v kontejneru si nemůže sám zvýšit práva (např. přes sudo nebo su).
iccKontejnery ve výchozí síti spolu nemohou komunikovat. Sítě Compose řeší kapitola 08.
ipPorty kontejnerů se ve výchozím stavu otevřou jen pro server samotný (127.0.0.1), ne do internetu.
default-network-optsTotéž pro sítě, které vytváří Compose.
default-address-poolsRozsah adres pro sítě kontejnerů. Výchozí rozsah může kolidovat s vaší sítí nebo VPN a část sítě pak přestane fungovat.

Krok 1: Zkontrolujte soubor a restartujte Docker

Kontrola odhalí chybu dřív, než by kvůli ní Docker nenastartoval.

Terminál
sudo dockerd --validate --config-file=/etc/docker/daemon.json     # → configuration OK
sudo systemctl restart docker

sudo docker info --format '{{.LoggingDriver}} live-restore={{.LiveRestoreEnabled}}'
sudo docker info | grep -A5 'Security Options'     # apparmor, seccomp (builtin), cgroupns

V části Security Options musí být apparmor, seccomp a cgroupns – výchozí bezpečnostní omezení kontejnerů.

Krok 2: Vytvořte existující kontejnery znovu

Nové nastavení platí jen pro nově vytvořené kontejnery a sítě. Data ve volumes zůstanou.

Terminál
# daemon defaults apply only to newly created containers and networks
cd /opt/stacks/<PROJECT>
sudo docker compose down && sudo docker compose up -d     # volumes are kept
Image přestala fungovat?

Některé image při startu volají sudo nebo su a s no-new-privileges selžou. Povolte výjimku jen pro daný kontejner (security_opt: ["no-new-privileges:false"]), nebo najděte image, která práva správce nepotřebuje.

Pokročilá volba "userns-remap": "default" zařídí, že správce v kontejneru není správcem na serveru. Komplikuje ale práva k souborům a vylučuje některé volby. Stejnou ochranu dá jednodušeji rootless režim.

07 · Síť

Porty a firewall

Cíl: kontejner není z internetu dostupný, pokud to výslovně nechcete. A když se port otevře omylem, zastaví ho firewall.

Kam publikovat porty

Kdo má ke službě přistupovatNastavení v compose.yamlPoznámka
Jen jiné kontejnery (např. databáze)žádné ports:Kontejnery ve stejné síti se najdou podle názvu služby.
Jen server samotný"127.0.0.1:8080:80"Adresu pište vždy, i když je díky kapitole 06 výchozí.
Vy, vzdáleně (administrace)"127.0.0.1:8080:80" + SSH tunelPort se nikam neotevírá, viz níže.
Kdokoli z internetu (web)"0.0.0.0:443:443"Jen u jednoho kontejneru, který web zpřístupňuje (reverse proxy), a s výjimkou v DOCKER-USER.

Ke službě na 127.0.0.1 se vzdáleně dostanete přes SSH tunel. Port se tak nikam neotevírá a funguje to i s Tailscale, který povoluje jen SSH:

Terminál
# on your PC: forward local port 8080 to 127.0.0.1:8080 on the server
ssh -N -L 8080:127.0.0.1:8080 -p <SSH_PORT> <ADMIN_USER>@<SERVER>
# then open http://127.0.0.1:8080 in the browser on your PC
Publikování na Tailscale adresu

Jde to, ale při startu serveru může být Docker rychlejší než Tailscale a kontejner pak nenastartuje (chyba cannot assign requested address). SSH tunel tento problém nemá.

Nepoužívejte network_mode: host (každý port kontejneru je pak port serveru) ani "iptables": false (rozbije síť kontejnerů a zpřístupní je z lokální sítě).

Firewall i pro kontejnery: řetězec DOCKER-USER

Pravidla v řetězci DOCKER-USER platí pro spojení do kontejnerů a Docker je nikdy nepřepíše. Pravidla níže zahodí každé spojení zvenku do kontejneru kromě portů, které výslovně povolíte. Kontejnery se přitom ven připojit mohou (aktualizace, API). Uložíme je do /etc/ufw/after.rules, takže je UFW načte při každém startu.

Krok 1: Zjistěte síťové rozhraní a zálohujte soubor

Terminál
ip -4 route show default          # → default via … dev <EXT_IF> …
sudo cp -a /etc/ufw/after.rules /etc/ufw/after.rules.bak

Krok 2: Zapněte pojistku

Za 10 minut vrátí původní soubor a pravidla smaže. Když vše funguje, v kroku 5 ji zrušíte.

Terminál
# safety net: in 10 minutes restore the original file and empty DOCKER-USER
sudo systemd-run --unit=rollback-docker-user --on-active=10min \
  /bin/sh -c 'cp -a /etc/ufw/after.rules.bak /etc/ufw/after.rules; iptables -F DOCKER-USER; ufw reload'

Krok 3: Přidejte pravidla

Blok vložte na úplný konec /etc/ufw/after.rules (sudo nano /etc/ufw/after.rules) a <EXT_IF> nahraďte názvem rozhraní. Řádky s porty 80 a 443 nechte jen pro veřejný web; bez nich není zvenku dostupný žádný kontejner.

/etc/ufw/after.rules
# BEGIN DOCKER-USER
*filter
:DOCKER-USER - [0:0]
# replies to existing connections (including those a container opened to the outside)
-A DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j RETURN
# explicitly allowed public ports (original destination port, before DNAT)
-A DOCKER-USER -i <EXT_IF> -p tcp -m conntrack --ctorigdstport 80 --ctdir ORIGINAL -j RETURN
-A DOCKER-USER -i <EXT_IF> -p tcp -m conntrack --ctorigdstport 443 --ctdir ORIGINAL -j RETURN
# optional: your whole LAN may reach published ports
# -A DOCKER-USER -i <EXT_IF> -s <LAN_SUBNET> -j RETURN
# drop everything else coming from the external interface into containers
-A DOCKER-USER -i <EXT_IF> -j DROP
COMMIT
# END DOCKER-USER

Krok 4: Načtěte pravidla a zkontrolujte je

Terminál
sudo ufw reload
sudo ufw status verbose            # must say "Status: active", no errors
sudo iptables -S DOCKER-USER
sudo iptables -S FORWARD | head -5 # the first rules jump to DOCKER-USER

UFW musí hlásit Status: active a výpis DOCKER-USER musí obsahovat vaše pravidla.

Krok 5: Ověřte přihlášení a zrušte pojistku

V novém okně se přihlaste přes SSH. Když to funguje, pojistku zrušte, jinak za 10 minut pravidla smaže.

Terminál
sudo systemctl stop rollback-docker-user.timer
systemctl list-timers rollback-docker-user.timer   # → 0 timers listed
IPv6

Pravidla platí pro IPv4. Sítě kontejnerů mají IPv6 ve výchozím stavu vypnuté; pokud ho zapnete, přidejte stejný blok i do /etc/ufw/after6.rules. Nepřepínejte Docker na experimentální nftables – řetězec DOCKER-USER pak neexistuje.

08 · Kontejnery

Bezpečné spouštění kontejnerů

Kontejnery popisujte v souboru compose.yaml, ne dlouhými příkazy. Nastavení je pak přehledné, dá se zkontrolovat a uložit do Gitu. Každý projekt má vlastní složku, do které smí zapisovat jen root: kdo může soubor změnit, ovládá kontejner.

Terminál
sudo install -d -m 0750 -o root -g root /opt/stacks/<PROJECT>
cd /opt/stacks/<PROJECT>

Příklad: zabezpečený kontejner

Webový server (nginx) s omezeními, která se hodí pro většinu kontejnerů. Komentáře vysvětlují jednotlivé řádky:

/opt/stacks/<PROJECT>/compose.yaml
services:
  web:
    # fixed version + digest: the image never changes without you knowing
    image: nginxinc/nginx-unprivileged:1.29-alpine@sha256:<DIGEST>
    user: "101:101"               # do not run as root inside the container
    read_only: true               # root filesystem is read-only
    tmpfs:
      - /tmp                      # writable only where the app needs it
    cap_drop:
      - ALL                       # no Linux capabilities
    security_opt:
      - no-new-privileges:true    # also in daemon.json; keep it visible here
    ports:
      - "127.0.0.1:8080:8080"     # server only / SSH tunnel
    mem_limit: 256m
    cpus: 0.50
    pids_limit: 200
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://127.0.0.1:8080/"]
      interval: 30s
      timeout: 5s
      retries: 3
    networks:
      - frontend

networks:
  frontend: {}

Digest pro řádek image: zjistíte prvním příkazem. Druhý blok nastavení zkontroluje a kontejner spustí:

Terminál
# the "Digest:" line is the value for @sha256:…
sudo docker buildx imagetools inspect nginxinc/nginx-unprivileged:1.29-alpine
Terminál
sudo docker compose config      # prints the resolved file and reports errors
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs --tail=50

Pravidla pro každý kontejner

PravidloProč
Image jen od důvěryhodného vydavatele (Docker Official Image, Verified Publisher, výrobce aplikace)Image je program, který spouštíte. Neznámá může obsahovat malware.
Pevná verze s digestem, nikdy latestImage se nezmění bez vašeho vědomí (kapitola 10).
Bez práv správce (user:)Když útočník kontejner prolomí, nezíská práva správce.
cap_drop: [ALL]Kontejner nemá žádná dílčí práva roota. Většina aplikací žádná nepotřebuje.
read_only: trueÚtočník nemůže v kontejneru nic uložit ani přepsat. Zápis jen tam, kde ho aplikace potřebuje.
Limity mem_limit, cpus, pids_limitJedna aplikace nevyčerpá celý server.
Oddělené sítě, databáze v síti internal: trueKontejner vidí jen služby, které potřebuje. Interní síť nemá přístup do internetu.
Hesla v souborech (secrets:)Hesla v proměnných prostředí jdou snadno vypsat a končí v logech.
Tyto volby vypínají izolaci

privileged: true, network_mode: host, pid: host, cap_add: [SYS_ADMIN], apparmor:unconfined, seccomp:unconfined a připojení složek /, /etc, /root, /home nebo /var/run/docker.sock. Pokud je návod aplikace vyžaduje, zvažte jiné řešení (balíček z APT, virtuální stroj).

Hesla

Compose předá heslo kontejneru jako soubor /run/secrets/<jméno>. Většina oficiálních image ho umí načíst přes proměnnou s koncovkou _FILE:

Terminál
# password file readable by root only, never typed into the shell history
sudo install -d -m 0700 /opt/stacks/<PROJECT>/secrets
openssl rand -base64 32 | sudo tee /opt/stacks/<PROJECT>/secrets/db_password >/dev/null
sudo chmod 0600 /opt/stacks/<PROJECT>/secrets/db_password
compose.yaml
services:
  db:
    image: postgres:18-alpine@sha256:<DIGEST>
    environment:
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - db-data:/var/lib/postgresql
    networks:
      - backend

networks:
  backend:
    internal: true      # no internet access, no published ports

volumes:
  db-data: {}

secrets:
  db_password:
    file: ./secrets/db_password

Soubor s heslem musí umět přečíst uživatel, pod kterým běží aplikace v kontejneru. Když ho nepřečte, změňte vlastníka souboru na tohoto uživatele; práva pro všechny nenastavujte.

09 · Rootless

Varianta: Docker bez práv správce

V rootless režimu běží Docker i kontejnery pod obyčejným uživatelem bez práv správce. Kdo prolomí kontejner, nezíská server, jen tohoto uživatele. Použijeme samostatného uživatele bez sudo, aby útočník nezískal ani vaše SSH klíče.

Krok 1: Připravte balíčky a uživatele

Potřebuje repozitář z kapitoly 04. Příkazy vypnou běžný Docker a vytvoří uživatele pro kontejnery. Spusťte je jako <ADMIN_USER>:

Terminál
sudo apt install uidmap dbus-user-session docker-ce-rootless-extras systemd-container

# stop the system-wide (rootful) daemon - rootless does not need it
sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sock

# a dedicated user for containers: no password, no sudo
sudo adduser --disabled-password --gecos "" <DOCKER_USER>
grep '^<DOCKER_USER>:' /etc/subuid /etc/subgid     # at least 65536 IDs each

Ubuntu 24.04 a novější vyžaduje pro rootless režim výjimku v AppArmoru. Balíček docker-ce-rootless-extras ji nainstaluje sám.

Krok 2: Zapněte start se serverem

Docker uživatele se spustí se serverem, i když není nikdo přihlášený.

Terminál
# start the user's daemon at boot, even when nobody is logged in
sudo loginctl enable-linger <DOCKER_USER>

Krok 3: Nainstalujte Docker pod uživatelem

Do účtu uživatele se přepněte přes machinectl shell. Přes sudo -iu nebo su by nefungovala správa služeb.

Terminál
# a full systemd session for the user (sudo -iu / su - do not create one)
sudo machinectl shell <DOCKER_USER>@

# --- now as <DOCKER_USER> ---
dockerd-rootless-setuptool.sh install
systemctl --user enable docker
docker context use rootless
docker info | grep -Ei 'context|rootless|cgroup'

Výpis musí obsahovat Context: rootless a Cgroup Version: 2. Nastavení tohoto Dockeru je v ~/.config/docker/daemon.json; převezměte do něj z kapitoly 06 logy, no-new-privileges a default-address-pools.

Krok 4: Povolte limity výkonu

Bez tohoto kroku systém limity procesoru a disku u rootless kontejnerů tiše ignoruje. Spusťte jako <ADMIN_USER>:

Terminál
sudo install -d -m 0755 /etc/systemd/system/user@.service.d
sudo tee /etc/systemd/system/user@.service.d/delegate.conf >/dev/null <<'EOF'
[Service]
Delegate=cpu cpuset io memory pids
EOF
sudo systemctl daemon-reload
# takes effect once the user's systemd instance restarts (simplest: reboot)

Porty a firewall

V rootless režimu platí firewall UFW i pro kontejnery a řetězec DOCKER-USER není potřeba. Port, který má být dostupný zvenku, proto povolte v UFW (sudo ufw allow 443/tcp). Porty pod 1024 (např. 80 a 443) obyčejný uživatel otevřít nesmí; povolte to jen programu rootlesskit:

Terminál
# allow ports below 1024 only for rootlesskit, not for every user
sudo setcap cap_net_bind_service=ep "$(command -v rootlesskit)"

# as <DOCKER_USER>:
systemctl --user restart docker

Po každé aktualizaci balíčku docker-ce-rootless-extras příkaz zopakujte, aktualizace oprávnění smaže.

Další omezení

  • Kontejner ve výchozím stavu nevidí skutečnou IP návštěvníka (vadí např. u logů webu). Řešení popisuje dokumentace.
  • Volba privileged nedává plná práva a ping v kontejneru nemusí fungovat.
  • Data jsou v ~/.local/share/docker uživatele, ne v /var/lib/docker. Myslete na to u záloh.

10 · Aktualizace

Aktualizace Dockeru a aplikací

Aktualizovat je potřeba dvě věci: samotný Docker a images aplikací. Automatické aktualizace Ubuntu řeší jen první.

Docker – varianta A: automaticky

Přidá Docker do automatických aktualizací. Kontejnery díky live-restore aktualizaci přežijí bez restartu.

Terminál
sudo tee /etc/apt/apt.conf.d/52unattended-upgrades-docker >/dev/null <<'EOF'
Unattended-Upgrade::Origins-Pattern {
        "site=download.docker.com";
};
EOF

# dry run: Docker packages must show up as allowed
sudo unattended-upgrade --dry-run --debug 2>&1 | grep -iE 'docker|containerd'

Docker – varianta B: ručně

Pro server, kde restarty plánujete sami. Sledujte přehled vydání a bezpečnostní oznámení. Aktualizujte pravidelně (např. jednou měsíčně) a hned po bezpečnostní opravě.

Terminál
sudo apt update
apt list --upgradable 2>/dev/null | grep -E 'docker|containerd'
sudo apt install --only-upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Images aplikací

  1. Sledujte nové verze aplikace a bezpečnostní oznámení výrobce.
  2. V compose.yaml přepište verzi a digest.
  3. Stáhněte novou image, nasaďte ji, zkontrolujte stav a smažte staré images:
Terminál
cd /opt/stacks/<PROJECT>
# after changing tag + digest in compose.yaml:
sudo docker compose pull
sudo docker compose up -d
sudo docker compose ps
sudo docker image prune

Nástroje, které kontejnery aktualizují automaticky, potřebují přístup k docker.sock a nasadí cokoli nového bez kontroly. Na server nepatří. Známé zranitelnosti v images odhalí skener (Trivy, Docker Scout).

11 · Ověření

Kontrola nastavení

Kontrola po instalaci a po každé větší změně. Komentáře u příkazů říkají, co má vyjít.

Terminál
# 1) nobody in the docker group, no API on the network
getent group docker
sudo ss -tlnp | grep -E ':2375|:2376'          # → nothing

# 2) daemon security options
sudo docker info | grep -A5 'Security Options'
sudo docker info --format '{{.LoggingDriver}} live-restore={{.LiveRestoreEnabled}}'

# 3) where ports are published (0.0.0.0 / [::] only for ports meant to be public)
sudo docker ps --format 'table {{.Names}}\t{{.Ports}}'
sudo ss -tlnp | grep -E 'docker-proxy|rootlesskit'

# 4) firewall safety net
sudo iptables -S DOCKER-USER

# 5) settings of every running container
sudo docker inspect --format '{{.Name}} user={{.Config.User}} ro={{.HostConfig.ReadonlyRootfs}} priv={{.HostConfig.Privileged}} caps={{.HostConfig.CapDrop}} sec={{.HostConfig.SecurityOpt}}' $(sudo docker ps -q)

Test zvenku

Z počítače mimo vaši síť (jiný server, telefon na mobilních datech) zjistěte, které porty jsou otevřené. Otevřené (open) smí být jen ty, které jste povolili, typicky 80 a 443 u webu:

Terminál
# from a machine OUTSIDE your network
nmap -Pn -p 80,443,2375,2376,5432,6379,8080,<SSH_PORT> <PUBLIC_IP>

Firewall pro kontejnery vyzkoušíte kontejnerem, který schválně otevře port do internetu. Zvenku musí být port filtered (zablokovaný):

Terminál
# on the server: publish a test port on all addresses on purpose
sudo docker run --rm -d --name test-leak -p 0.0.0.0:8081:8080 nginxinc/nginx-unprivileged:alpine

# from outside: port 8081 must be "filtered", not "open"
nmap -Pn -p 8081 <PUBLIC_IP>

# on the server: clean up
sudo docker stop test-leak

Podrobný kontrolní seznam nabízí CIS Docker Benchmark. Na malém serveru není nutné splnit vše.

12 · Problémy

Když něco nefunguje

Nejčastější potíže a jejich řešení.

Co vidítePříčinaCo udělat
permission denied … docker.socknejste ve skupině docker (záměr)použijte sudo docker
Docker po úpravě daemon.json nenastartujechyba v souboru, nebo stejné nastavení i v systemdsudo dockerd --validate --config-file=/etc/docker/daemon.json
Služba není zvenku dostupnáport je jen na 127.0.0.1, nebo ho blokuje DOCKER-USERveřejný port povolte vědomě (porty, firewall)
Kontejner nemá internetsíť má internal: true, nebo provoz blokuje VPNzkontrolujte síť v compose.yaml a sudo iptables -S DOCKER-USER
Po spuštění kontejneru nefunguje část sítěadresy kontejnerů kolidují s vaší sítí nebo VPNdefault-address-pools (kapitola 06), pak sítě vytvořte znovu
Disk se plnístaré images, mezipaměť sestavování, logydocker system df, image prune, builder prune
Image nefunguje s read_only nebo cap_dropaplikace potřebuje někam zapisovat nebo jedno dílčí právoz logů zjistěte co a povolte jen to (tmpfs, cap_add)
Rootless: Failed to connect to buspřepnutí přes sudo -iu nebo susudo machinectl shell <DOCKER_USER>@
Rootless: po restartu serveru nic neběžíchybí start se serveremkapitola 09, krok 2
Po přechodu na novou verzi Ubuntu nejde Docker aktualizovatpřechod cizí repozitáře vypínáSuites: v docker.sources na nový codename
Terminál
sudo dockerd --validate --config-file=/etc/docker/daemon.json
journalctl -u docker -b --no-pager | tail -50
Terminál
sudo docker system df
sudo docker image prune          # unused images
sudo docker builder prune        # build cache
Shrnutí
  • Docker jen z oficiálního repozitáře s ověřeným klíčem, nikdy curl | sh.
  • Skupina docker = správce bez hesla. Na serveru sudo docker, nebo rootless.
  • Porty kontejnerů obchází firewall: otevírat je na 127.0.0.1 a pojistit pravidly v DOCKER-USER.
  • Každý kontejner bez práv správce, jen pro čtení, s limity a pevnou verzí image.

13 · Odinstalace

Odinstalace

Smaže všechna data

Smazáním /var/lib/docker přijdete o všechny images, kontejnery a volumes včetně databází. Nejdřív zálohujte.

Terminál
sudo apt purge docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-ce-rootless-extras
sudo rm -rf /var/lib/docker /var/lib/containerd
sudo rm -f /etc/apt/sources.list.d/docker.sources /etc/apt/keyrings/docker.asc
sudo rm -f /etc/apt/apt.conf.d/52unattended-upgrades-docker
sudo rm -rf /etc/docker
sudo apt update

Z /etc/ufw/after.rules smažte blok BEGIN DOCKER-USER … END DOCKER-USER a spusťte sudo ufw reload. Rootless režim odinstalujte ještě před apt purge:

Terminál
# as <DOCKER_USER>, before apt purge:
dockerd-rootless-setuptool.sh uninstall
rootlesskit rm -rf ~/.local/share/docker

# as <ADMIN_USER>:
sudo loginctl disable-linger <DOCKER_USER>

Zdroje

Oficiální dokumentace a další čtení

Potřebujete bezpečně nastavit server?

Navrhneme, zabezpečíme a zdokumentujeme sítě i servery.

Servery a sítě