Ciele
- Naučiť sa používať Docker na spustenie programov v kontajneroch.
- Naučiť sa pracovať s dátami, sieťou, premennými prostredia v rámci Dockeru.
- Naučiť sa vytvárať vlastný Docker obraz.
- Pochopiť význam vrstiev v Docker obrazoch.
- Vyskúšať využitie nástrojov na správu závislostí.
Úvod
Docker je jedným z najpodstatnejších nástrojov pri vývoji a prevádzke softvéru. Umožňuje ľahko vytvoriť izolované prostredie pre beh programu, a tiež automatizovať jeho vytvorenie a distribúciu. Vďaka tomu môžeme presne kontrolovať prostredie, v ktorom vyvíjame softvér, testujeme ho a prevádzkujeme.
Na dnešnom cvičení si vyskúšate spúšťanie a základnú konfiguráciu Docker kontajnerov. Vyskúšate si tiež vytváranie vlastných Docker obrazov.
Postup
Krok 1: Hello, Docker!
Na spustenie kontajnera slúži príkaz docker run.
Úloha 1.1
Spustite jednoduchý Docker kontajner:
$ docker run hello-world
Tento príkaz zabezpečí, že Docker stiahne obraz hello-world (ak nebol stiahnutý skôr), vytvori na základe neho kontajner a spustí v ňom štandardný príkaz definovaný obrazom (hello). Tento príkaz len vypíše hlášku na štandardný výstup.
Úloha 1.2
Skontrolujte, že kontajner stále existuje, aj keď bol po vykonaní príkazy zastavený:
$ docker ps -a
$ docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6ed3b5658147 hello-world "/hello" 8 seconds ago Exited (0) 7 seconds ago cranky_swirles
Poznámka
docker ps vypíše všetky aktuálne spustené kontajnery. Prepínač -a (all) pridá aj zastavené.
Pokiaľ kontajner nepomenujete pomocou prepínača --name, Docker mu priradí náhodný názov, ktorý vidíte v stĺpci NAMES.
Úloha 1.3
Odstráňte vytvorený kontajner pomocou príkazu docker rm, za ktorým uveďte ID alebo názov kontajnera z výpisu. Napríklad
$ docker rm 6ed3b5658147
alebo
$ docker rm cranky_swirles
Overte, či kontajner zmizol z výpisu docker ps -a
Pri spúšťaní kontajnera tiež môžete použiť prepínač --rm, ktorý zabezpečí jeho automatické odstránenie po tom, ako sa zastaví.
Úloha 1.4
Overte, že obraz hello-world je stále k dispozícii na vašom počítači pomocou príkazu docker images.
Jeho výstup bude vyzerať približne takto:
$ docker images
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
hello-world:latest 1b44b5a3e06a 10.1kB 0B
Obrazu používané v existujúcich kontajneroch sú označené písmenom U v stĺpci EXTRA.
Krok 2: Interaktívny kontajner
Ak chceme v kontajnere spustiť interaktívny príkaz, musíme použiť prepínač -it, ktorý je vlastne kombináciou prepínačov -i (interactive) a -t (tty).
Úloha 2.1
Vytvorte kontajner s minimálnou verziu linuxovej distribúcie Ubuntu a spustite interpretátor príkazov v nej:
$ docker run --rm -it ubuntu:24.04 bash
Vyskúšajte spúšťať príkazy v kontajnere a vytvárať súbory. Potom ukončite prácu pomocou príkazu exit.
Úloha 2.2
Skontrolujte, že súbory, ktoré ste vytvorili, neexistujú mimo kontajnera. Keď znovu použijete docker run, vytvorí sa nový kontajner, v ktorom tiež nebudú žiadne zmeny, ktoré ste robili v pôvodom kontajnere.
Kontajnery sú len dočasné prostredia určené pre beh jedného programu. Preto vo väčšine prípadov nemá zmysel ponechávať zastavené kontajnery a s nimi spojené dáta.
Poznámka
Kým je kontajner spustený, môžete v inom okne terminála skontrolovať výstup docker ps. Mali by ste vidieť informáciu o bežiacom kontajnere (stav Up).
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
afe320fa9d8a ubuntu:24.04 "bash" About a minute ago Up About a minute vigilant_hodgkin
Krok 3: Mapovanie portov
Skúsme spustiť populárny webový server Nginx. Webový server dokáže reagovať na HTTP požiadavky prehliadača a posielať mu webové stránky, ktoré prehliadač zobrazí.
Nginx štandardne počúva na porte 80. Ak spustíme Nginx v Docker kontajneri, bude tento port dostupný len v rámci kontajnera. Aby sme mohli pristupovať k Nginx z nášho počítača, musíme namapovať port 80 z kontajnera na nejaký port na našom počítači. To sa robí pomocou prepínača -p (publish) pri spúšťaní kontajnera.
Úloha 3.1
Spustite kontajner s Nginxom a namapujte port 80 z kontajnera na port 8080 na vašom počítači:
$ docker run --rm -p 8080:80 nginx:latest
Otvorte webový prehliadač a zadejte adresu http://localhost:8080. Mali by ste vidieť uvítaciu stránku Nginxu.
Poznámka
Časť za dvojbodkou v názve obrazu (napr. nginx:latest) sa nazýva tag a slúži na označenie konkrétnej verzie obrazu. Ak tag neuvediete, Docker použije predvolený tag latest. V tomto prípade použijeme najnovšiu verziu Nginxu, ktorá je dostupná v Docker Hubu.
Ak by ste chceli použiť konkrétnu verziu Nginxu, môžete zadať napríklad nginx:1.25.3. Dostupné tagy a ich význam môžete nájsť v dokumentácii daného obrazu na Docker Hube.
Úloha 3.2
Zastavte kontajner pomocou klávesovej skratky Ctrl+C v terminále, kde je spustený. Teraz môžete znovu načítať stránku http://localhost:8080 a skontrolovať, že už nie je dostupná.
Krok 4: Zväzky (volumes)
Keďže kontajner je dočasné prostredie, všetky zmeny, ktoré v ňom urobíte, sa po jeho odstránení stratia. Ak chceme uchovávať dáta trvale, musíme použiť tzv. zväzky (volumes).
V tomto kroku použijeme obraz s jednoduchou webovou aplikáciou pre písanie poznámok, ktorá je dostupná na Docker Hube pod názvom kpituke/notes-app-example. Táto aplikácia umožňuje vytvárať, zobrazovať a mazať poznámky. Je napísaná v jazyky Python. Na ukladania dát môže používať súbor /data/notes.json, alebo databázový server PostgreSQL.
Úloha 4.1
Spustite kontajner s touto aplikáciou a namapujte port 5000 z kontajnera na port 5000 na vašom počítači. Použite príkaz docker run podobne ako v predchádzajúcom kroku, ale obrazom kpituke/notes-app-example:latest a portami 5000:5000.
Otvorte webový prehliadač a zadajte adresu http://localhost:5000. Mali by ste vidieť jednoduché rozhranie pre písanie poznámok. Skúste pridať niekoľko poznámok a potom znovu načítať stránku. Poznámky by mali byť stále zobrazené, pretože sú uložené v rámci kontajnera.
Riešenie
$ docker run --rm -p 5000:5000 kpituke/notes-app-example:latest
Úloha 4.2
Zastavte kontajner pomocou Ctrl+C a potom ho znovu spustite pomocou rovnakého príkazu docker run. Skontrolujte, že poznámky, ktoré ste vytvorili, zmizli.
Na to, aby sa poznámky uchovali aj po zastavení a odstránení kontajnera, musíme použít zväzok, ktorý namapuje adresár /data z kontajnera na trvalé úložisko na vašom počítači.
Úloha 4.3
Vytvorte adresár ./notes-data v aktuálnom adresári, kde budete spúšťať kontajner. Tento adresár bude slúžiť ako trvalé úložisko pre poznámky.
Spustite znovu kontajner s aplikáciou na poznámky, ale tentokrát použite prepínač -v (volume) na namapovanie adresára /data z kontajnera na adresár ./notes-data vo vašom počítači. Parameter pre zväzok bude vyzerať takto: -v $(pwd)/notes-data:/data.
Vytvorte poznámky v aplikácii a potom zastavte kontajner. Skontrolujte, že v adresári ./notes-data sa nachádza súbor notes.json, ktorý obsahuje vaše poznámky. Skontrolujte, že poznámky sú stále zobrazené, keď znovu spustíte kontajner s týmto zväzkom.
Riešenie
$ mkdir notes-data
$ docker run --rm -p 5000:5000 -v $(pwd)/notes-data:/data kpituke/notes-app-example:latest
Krok 5: Sieť
V predchádzajúcom kroku sme spustili aplikáciu, ktorá používa súborový systém na uchovávanie dát. Môžeme však použiť aj databázový server, ktorý bude bežať v inom kontajneri.
Keďže každý kontajner je izolované prostredie, nemôžu sa navzájom vidieť a komunikovať pomocou štandardných sieťových rozhraní. Nepomôže nám ani mapovanie portov, ktoré sme použili v predchádzajúcich krokoch, pretože to umožňuje komunikáciu len medzi kontajnerom a vonkajším svetom (našim počítačom), ale nie medzi kontajnermi navzájom.
Preto musíme na komunikáciu medzi kontajnermi vytvoriť Docker sieť.
Úloha 5.1
Vytvorte novú Docker sieť s názvom notes-network pomocou príkazu:
$ docker network create notes-network
Úloha 5.2
Spustite kontajner s PostgreSQL databázou a pripojte ho k sieti notes-network. Použite obraz postgres:17. Nastavenia serveru (názov databázy, používateľ, heslo) sa definujú pomocou premenných prostredia, ktoré zadáte pomocou prepínača -e. Celý príkaz bude vyzerať takto:
$ docker run -d --name notes-db --network notes-network -e POSTGRES_DB=notes -e POSTGRES_USER=notes_user -e POSTGRES_PASSWORD=notes_pass postgres:17
Prepínač -d (detached) zabezpečí, že kontajner bude spustený na pozadí a neblokuje váš terminál.
Prepínač --name zas umožňuje pomenovať kontajner. Toto meno využijeme na komunikáciu s ním z iných kontajnerov v tej istej sieti.
Úloha 5.3
Spustite kontajner s aplikáciou na poznámky a pripojte ho tiež k sieti notes-network. Použite obraz a mapovanie portov ako v predchádzajúcich krokoch. Navyše potrebujeme tiež nastaviť premenné prostredia, ktoré aplikácia používa na pripojenie k databáze. Celý príkaz bude vyzerať takto:
$ docker run -d --name notes-app -p 5000:5000 --network notes-network -e STORAGE_BACKEND=postgres -e DB_HOST=notes-db -e DB_NAME=notes -e DB_USER=notes_user -e DB_PASSWORD=notes_pass kpituke/notes-app-example:latest
Premennú DB_HOST sme nastavili na názov kontajnera s databázou (notes-db), pretože v rámci tej istej Docker siete môžeme komunikovať s inými kontajnermi pomocou ich názvov.
Úloha 5.4
Otvorte webový prehliadač a zadajte adresu http://localhost:5000. Skontrolujte, že aplikácia funguje správne. Všimnite si tiež nadpis „Storage: postgres“, ktorý indikuje, že aplikácia používa databázu namiesto súboru.
Úloha 5.5
Skontrolujte výstup docker ps, že oba kontajnery (aplikácia a databáza) sú spustené.
Poznámka
Keďže kontajnery nebežia na pozadí, nevidíte v terminále ich výstup. Môžete ho zobraziť pomocou príkazu docker logs za ktorým uveďte ID alebo názov kontajnera. Napríklad:
$ docker logs notes-app
Úloha 5.6
Odstráňte oba kontajnery pomocou príkazu docker rm -f za ktorým uveďte názvy kontajnerov:
$ docker rm -f notes-app notes-db
Odstrániť môžete aj sieť pomocou
$ docker network rm notes-network
Krok 6: Náš prvý obraz
Náš prvý obraz pripravíme pre webovú stránku z cvičenia 3.
Úloha 6.1
Stiahnite si projekt git-branch z GitLabu.
Obsahom tohto projektu je jednoduchá webová stránka. Našim cieľom je vytvoriť kontajner, ktorý bude po spustení počúvať na porte 80 na požiadavky prehliadača a odošle mu túto stránku. Preto ako základný obraz použijeme obraz s webovým serverom nginx.
Úloha 6.2
Preštudujte si dokumentáciu obrazu, konkrétne sekciu Hosting some simple static content. Vytvorte podľa nej súbor Dockerfile v adresári projektu. Namiesto static-html-directory vo vašom prípade môže byť aktuálny adresár .
Riešenie
FROM nginx
COPY . /usr/share/nginx/html
Úloha 6.3
Zostavte obraz:
$ docker build -t simple-web-page .
Spustite kontajner na základe neho vytvoreného obrazu a namapujte port 8000 na vašom počítači na port 80 v kontajneri. presný príkaz vytvorte podľa minulého cvičenia.
Úloha 6.4
Zadajte v prehliadači adresu http://localhost:8000/ a skontrolujte, či sa stránka zobrazila správne.
Gratulujeme! Práve ste vytvorili svoj prvý Docker obraz a spustili kontajner, ktorý z neho vznikol!
Krok 7: Zložitejší obraz
Skúsme vytvoriť obraz pre webovú aplikáciu pre poznámky z minulého cvičenia.
Úloha 7.1
Stiahnite si zdrojové kódy aplikácie a rozbaľte v adresári notes-app.
Aplikácia používa jazyk Python. Potrebujeme teda použiť základný obraz, ktorý obsahuje všetko potrebné pre beh aplikácií v tomto jazyku. V našom prípade to môže byť obraz python:3.14-slim. Ten obsahuje Python 3.14 a minimálnu (slim) verziu distribúcie Debian.
Úloha 7.2
Vytvorte jednoduchý Dockerfile:
FROM python:3.14-slim
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 5000
CMD ["python", "app.py"]
Deklarácia FROM špecifikuje základný obraz, WORKDIR – pracovný adresár, COPY kopíruje súbory z vášho počítača do obrazu.
Deklarácia RUN spúšťa príkaz v dočasnom kontajnere a všetky zmeny, ktoré vykoná, pridá ako ďalšiu vrstvu do obrazu. V tomto prípade je to spustenie príkazu pip (package installer for Python), ktorý nainštaluje všetky Python knižnice, ktoré náš program potrebuje. Ich zoznam je v súbor requirements.txt.
Deklarácia EXPOSE len dokumentuje, ktorý port používa kontajner a je možné ho teda mapovať. CMD špecifikuje, ktorý príkaz sa má spustiť, keď sa z obrazu vytvorí a spustí kontajner.
Úloha 7.3
Zostavte obraz a spustite kontajner:
$ docker build -t notes-app .
$ docker run --rm -p -p 5000:5000 -v notes-data:/data notes-app
Zadajte adresu http://localhost:5000 v prehliadači a uistite sa, že aplikácia pracuje správne.
Ak všetko funguje, tak práve ste vytvorili ďalší vlastný kontajner, spustili ste obraz založený na ňom, v ktorom beží jednoduchá webová aplikácia!
Krok 8: Optimalizácia zostavenia obrazu
Zostavovanie komplexných obrazov môže byť časovo dosť náročné. Je potrebné sťahovať závislosti z internetu, inštalovať ich, kompilovať program, atď. Preto sa Docker snaží optimalizovať tento proces pomocou vrstiev. Každý príkaz v Dockerfile vytvára novú vrstvu, ktorá sa môže znovu použiť, ak sa nezmenia dáta, z ktorých bola vytvorená alebo predchádzajúce vrstvy.
Napríklad príkaz COPY sa vykoná znovu len vtedy, ak sa zmení niektorý zo súborov, ktoré kopíruje, alebo niektoré z predchádzajúcich vrstiev. Príkaz RUN sa tiež vykoná znovu len vtedy, ak sa zmení niektorá z vrstiev, na ktorých závisí.
Úloha 8.1
Skúste urobiť zmenu v súbor app.py, napríklad zmeniť nadpis <h1>📝 Notes</h1> na <h1>📝 Poznámky</h1> na riadku 159. Keď znovu spustite docker build, všimnite si, že sa znovu spúšťa aj príkaz pip, aj keď závislosti sa nezmenili.
Je to spôsobené tým, že príkaz RUN závisí na vrstve vytvorenej príkazom COPY a tá závisí od všetkých súborov projektu.
To môžeme optimalizovať tak, že najprv skopírujeme len súbor requirements.txt a spustíme príkaz RUN na inštaláciu závislostí. Až potom skopírujeme zvyšok súborov. Vrstva so závislosťami sa tak bude znovu používať, pokiaľ sa nezmení súbor requirements.txt.
Úloha 8.2
Upravte Dockerfile nasledovne:
FROM python:3.14-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
EXPOSE 5000
CMD ["python", "app.py"]
Úloha 8.3
Znovu zostavte obraz a všimnite si, že sa vytvára viac vrstiev. Urobte zmenu v app.py a znovu zostavte obraz. Všimnite si, že príkaz RUN sa už nespúšťa, pretože závislosti sa nezmenili.
Krok 9: Vytvorenie obrazu s programom v jazyku C
Skúsme niečo zložitejšie. Pripravme obraz s programom v jazyku C. Použijeme modifikovaný príklad z cvičenia 2 – „jednorozmerné piškvorky“.
Úloha 9.1
Stiahnite si zdrojový kód hry a rozbaľte ho v adresári piskvorky.
Úloha 9.2
Vytvorte súbor Dockerfile. Ako základný obraz použijeme gcc, ktorý obsahuje prekladač GCC a iné nástroje pre preklad programov v jazyku C.
Nastavte pracovný adresár v obraze na /src pomocou príkazu WORKDIR.
Pridajte príkaz pre kopírovanie (COPY) zdrojových súborov do adresára /src v obraze.
Spustite preklad pomocou príkazu RUN.
Nastavte príkaz po spustení kontajnera (CMD) na ./piskvorky.
Úloha 9.3
Zostavte obraz a spustite kontajner. Nezabudnite pridať prepínač -it pre interaktívny režim, aby ste mohli hrať hru v termináli.
$ docker build -t piskvorky .
$ docker run --rm -it piskvorky
Doplňujúce úlohy
Docker Compose
Ako vidíte, spúšťanie viacerých kontajnerov a ich konfigurácia pomocou príkazu docker run môže byť dosť komplikovaná a neprehľadná. Na jej zjednodušenie výborne slúži nástroj Docker Compose, ktorý umožňuje definovať a spúšťať skupinu kontajnerov pomocou jedného konfiguračného súboru compose.yaml.
Úloha A.1
Vytvorte súbor compose.yaml s nasledujúcim obsahom (nahraďte ??? správnymi hodnotami z predchádzajúceho kroku):
services:
notes-db:
image: postgres:17
environment:
POSTGRES_DB: notes
POSTGRES_USER: notes_user
POSTGRES_PASSWORD: notes_pass
volumes:
- db-data:/var/lib/postgresql/data
notes-app:
image: ???
ports:
- "5000:5000"
environment:
STORAGE_BACKEND: postgres
DB_HOST: notes-db
???
depends_on:
- notes-db
volumes:
db-data:
Úloha A.2
Spustite oba kontajnery pomocou príkazu docker compose up v adresári, kde sa nachádza súbor compose.yaml. Tento príkaz načíta konfiguráciu z compose.yaml, vytvorí potrebné siete a spustí oba kontajnery.
Úloha A.3
Otvorte webový prehliadač a zadajte adresu http://localhost:5000. Skontrolujte, že aplikácia funguje správne.
Úloha A.4
Zastavte oba kontajnery pomocou Ctrl+C v terminále, kde je spustený docker compose up. Keď znovu spustíte docker compose up, všimnite si, že kontajnery sa spustia rýchlejšie, pretože už existujú a Docker ich znovu spustí. Na odstránenie kontajnerov, sietí a zväzkov vytvorených pomocou Docker Compose použite príkaz docker compose down.
Viacfázové zostavovanie
V niektorých prípadoch môže príprava a kompilácia programu vyžadovať viac závislostí a nástrojov, ktoré nie sú potrebné pre samotný beh programu. Napríklad preklad programu v jazyku C vyžaduje prekladač, vývojové nástroje, knižnice a hlavičkové súbory, ale výsledný spustiteľný súbor je často veľmi malý a nevyžaduje žiadne z týchto nástrojov. Preto by bolo zbytočné, aby ich obsahoval aj výsledný obraz.
Úloha A.5
Pozrite si aký veľký je obraz piskvorky pomocou príkazu docker images:
$ docker images piskvorky
IMAGE ID DISK USAGE CONTENT SIZE EXTRA
piskvorky:latest e9a4a55676b1 1.54GB 0B
Na spustenie piškvoriek v príkazovom riadku určite nepotrebujeme Docker obraz s veľkosťou 1.5 GB.
Pre riešenie tohto problému slúži viacfázové zostavovanie. Umožňuje nám použiť jeden obraz pre fázu zostavovania, v ktorej máme všetky nástroje a závislosti potrebné pre prípravu programu, a druhý obraz pre fázu behu, ktorý obsahuje len to, čo je potrebné pre spustenie programu.
Úloha A.6
Upravte Dockerfile pre hru „piškvorky“ nasledovne:
# Fáza zostavovania
FROM gcc AS builder
# ... zostávajúce príkazy pre zostavovanie programu ...
# Fáza behu
FROM debian:stable-slim
WORKDIR /app
COPY --from=builder /src/piskvorky .
CMD ["./piskvorky"]
Úloha A.7
Spustite kontajner a uistite sa, že hra stále funguje správne.
Úloha A.8
Znovu zostavte obraz a skontrolujte jeho veľkosť. O koľko je menší ako predtým?
Poznámka
Obraz sa dá ešte viac zmenšiť, ak by sme použili ešte menší základný obraz pre fázu behu, napríklad alpine, ktorý je založený na Alpine Linuxu a má veľkosť len 5 MB, alebo dokonca scratch, ktorý je úplne prázdny a neobsahuje žiadne súbory ani nástroje.
Nie každá aplikácia však bude fungovať v takto obmedzenom prostredí. V prípade našej hry „piškvorky“ by to fungoval ak by sme ju kompilovali staticky, teda bez závislosti na zdieľaných knižniciach. To by sme mohli dosiahnuť pridaním prepínača -static pri kompilácii.