Модуль 1.2: Основи Docker
Складність:
[СЕРЕДНЯ]— потрібна практична роботаЧас на виконання: 45–50 хвилин
Передумови: Модуль 1 (Що таке контейнери?)
Що ви зможете зробити
Розділ «Що ви зможете зробити»Після завершення цього модуля ви зможете:
- Зібрати образ Docker з Dockerfile та пояснити призначення кожної інструкції
- Запустити контейнери з прокиданням портів (port mapping), змінними середовища та монтуванням томів (volume mounts)
- Діагностувати несправний контейнер, читаючи логи та виконуючи команди всередині нього через exec
- Пояснити систему шарів образу та чому порядок шарів важливий для швидкості збірки
Чому це важливо
Розділ «Чому це важливо»Docker є найпоширенішим інструментом для збірки образів контейнерів. Попри те, що Kubernetes більше не використовує Docker як середовище виконання (runtime), Docker залишається стандартом для:
- Збірки образів контейнерів
- Локальної розробки
- Тестування та діагностики
Вам знадобиться “достатньо знань” про Docker, щоб розуміти Kubernetes, — не обов’язково бути його гуру.
Встановлення Docker
Розділ «Встановлення Docker»macOS
Розділ «macOS»# Встановіть Docker Desktop# Завантажте з https://docker.com/products/docker-desktop# Або скористайтеся Homebrew:brew install --cask dockerLinux (Ubuntu/Debian)
Розділ «Linux (Ubuntu/Debian)»# Офіційне встановленняcurl -fsSL https://get.docker.com -o get-docker.shsudo sh get-docker.shsudo usermod -aG docker $USER# Вийдіть і знову зайдіть у систему, щоб зміни груп набрали чинностіПеревірка встановлення
Розділ «Перевірка встановлення»docker --version# Docker version 24.x.x, build xxxxx
docker run hello-world# Має з'явитися повідомлення "Hello from Docker!"Ваш перший контейнер
Розділ «Ваш перший контейнер»Зупиніться та подумайте: Коли ви запускаєте команду нижче, що станеться, якщо порт 8080 на вашій хост-машині вже зайнятий іншою програмою? Команда завершиться помилкою, оскільки Docker не зможе прив’язатися до вже зайнятого порту хоста. Вам доведеться обрати інший порт, наприклад
-p 8081:80.
# Запустити nginx (вебсервер)docker run -d -p 8080:80 nginx
# Що відбулося:# - Завантажено образ nginx з Docker Hub# - Створено контейнер із цього образу# - Запущено контейнер у фоновому режимі (-d)# - Прокинуто порт 8080 (хост) на порт 80 (контейнер)# Тестуванняcurl http://localhost:8080# Повертає HTML-код вітальної сторінки nginx
# Перегляд запущених контейнерівdocker ps# CONTAINER ID IMAGE COMMAND STATUS PORTS NAMES# a1b2c3d4e5f6 nginx "/docker-entrypoint.…" Up 10 seconds 0.0.0.0:8080->80/tcp tender_tesla
# Зупинити контейнерdocker stop a1b2c3d4e5f6
# Видалити контейнерdocker rm a1b2c3d4e5f6Основні команди Docker
Розділ «Основні команди Docker»Життєвий цикл контейнера
Розділ «Життєвий цикл контейнера»# Запустити контейнерdocker run [OPTIONS] IMAGE [COMMAND]
# Поширені опції:docker run -d nginx # Фоновий режим (background)docker run -it ubuntu bash # Інтерактивний терміналdocker run -p 8080:80 nginx # Прокидання портів (port mapping)docker run -v /host/path:/container/path nginx # Монтування тому (volume mount)docker run --name myapp nginx # Контейнер із власною назвоюdocker run -e MY_VAR=value nginx # Змінна середовищаdocker run --rm nginx # Видалити після зупинки
# Керування контейнерамиdocker ps # Список запущених контейнерівdocker ps -a # Список усіх контейнерівdocker stop CONTAINER # Коректна зупинкаdocker kill CONTAINER # Примусова зупинкаdocker rm CONTAINER # Видалення зупиненого контейнераdocker rm -f CONTAINER # Примусове видалення (stop + rm)Інспектування контейнерів
Розділ «Інспектування контейнерів»Підхід до діагностики: Коли контейнер працює неправильно, ці три команди — ваш інструментарій для налагодження, саме в такому порядку:
docker logs(що сталося?),docker exec -it ... bash(погляну всередину),docker inspect(покажи повну конфігурацію). Ця ж логіка згодом застосовуватиметься в Kubernetes:kubectl logs,kubectl exec,kubectl describe.
# Перегляд логівdocker logs CONTAINERdocker logs -f CONTAINER # Відстежувати (tail)docker logs --tail 100 CONTAINER # Останні 100 рядків
# Виконання команди в запущеному контейнеріdocker exec -it CONTAINER bash # Інтерактивна оболонкаdocker exec CONTAINER ls /app # Запуск окремої команди
# Перегляд деталей контейнераdocker inspect CONTAINER # Повна інформація у форматі JSONdocker stats # Використання ресурсівdocker top CONTAINER # Запущені процесиКерування образами
Розділ «Керування образами»# Завантаження образівdocker pull nginxdocker pull nginx:1.25docker pull gcr.io/project/image:tag
# Примітка: Образи Docker адресуються за вмістом. SHA256-хеш образу є його справжнім ідентифікатором. Теги — це лише зрозумілі для людей псевдоніми.
# Список образівdocker images
# Видалення образівdocker rmi nginxdocker image prune # Видалити невикористовувані образи
# Збірка образів (розглянемо далі)docker build -t myapp:v1 .Збірка образів контейнерів
Розділ «Збірка образів контейнерів»Dockerfile
Розділ «Dockerfile»Dockerfile — це текстовий файл з інструкціями для збірки образу:
# Базовий образFROM python:3.11-slim
# Робочий каталогWORKDIR /app
# Копіювання файлу залежностейCOPY requirements.txt .
# Встановлення залежностейRUN pip install --no-cache-dir -r requirements.txt
# Копіювання коду застосункуCOPY . .
# Відкриття порту (документація)EXPOSE 8000
# Команда за замовчуваннямCMD ["python", "app.py"]Збірка та запуск
Розділ «Збірка та запуск»# Зібрати образdocker build -t myapp:v1 .
# Запустити контейнер із образуdocker run -d -p 8000:8000 myapp:v1Інструкції Dockerfile
Розділ «Інструкції Dockerfile»| Інструкція | Призначення |
|---|---|
FROM | Базовий образ, на основі якого будується новий |
WORKDIR | Встановлює робочий каталог |
COPY | Копіює файли з хоста в образ |
ADD | Схожа на COPY, але може розпаковувати архіви та завантажувати URL |
RUN | Виконує команду під час збірки |
ENV | Встановлює змінну середовища |
EXPOSE | Документує, який порт використовує застосунок |
CMD | Команда за замовчуванням при старті контейнера |
ENTRYPOINT | Команда, що виконується завжди (CMD стає аргументами) |
Практичний приклад: вебзастосунок на Python
Розділ «Практичний приклад: вебзастосунок на Python»Створимо простий застосунок на Python:
app.py
from flask import Flaskimport os
app = Flask(__name__)
@app.route('/')def hello(): name = os.getenv('NAME', 'World') return f'Hello, {name}!'
if __name__ == '__main__': app.run(host='0.0.0.0', port=8000)requirements.txt
flask==3.0.0Історія з життя: контекст Docker на 5 ГБ Одного разу розробник ввів
docker build .у своєму домашньому каталозі замість каталогу проєкту. Оскільки в нього не було файлу.dockerignore, Docker сумлінно спробував скопіювати всі його папкиDocuments,DownloadsтаPicturesу контекст збірки Docker. Збірка “зависла” на 20 хвилин, перш ніж вичерпати всю пам’ять комп’ютера. Завжди використовуйте файл.dockerignore, щоб виключити.git,node_modulesта локальні файли середовища — це зробить контекст збірки малим і швидким.
Dockerfile
FROM python:3.11-slim
WORKDIR /app
# Спочатку встановлюємо залежності (краще кешування)COPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txt
# Копіюємо код застосункуCOPY app.py .
EXPOSE 8000
CMD ["python", "app.py"]Збірка та запуск
# Збіркаdocker build -t hello-flask:v1 .
# Запускdocker run -d -p 8000:8000 -e NAME=Docker hello-flask:v1
# Тестуванняcurl http://localhost:8000# Hello, Docker!
# Очищенняdocker rm -f $(docker ps -q --filter ancestor=hello-flask:v1)Кешування шарів
Розділ «Кешування шарів»Зупиніться та подумайте: Поміркуйте над цим — якщо ви зміните один рядок у коді Python, чи хочете ви, щоб Docker знову встановлював усі залежності (що може тривати 2 хвилини)? Чи достатньо просто скопіювати змінений файл (що займає 1 секунду)? Відповідь повністю залежить від ПОРЯДКУ інструкцій у вашому Dockerfile. Прочитайте ПОГАНИЙ та ХОРОШИЙ приклади нижче та спробуйте зрозуміти, чому порядок має значення.
Docker кешує шари для швидшої збірки. Порядок важливий:
# ПОГАНО: зміна коду анулює кеш залежностейFROM python:3.11-slimWORKDIR /appCOPY . . # Будь-яка зміна скидає кешRUN pip install -r requirements.txt # Перевстановлюється щоразу!CMD ["python", "app.py"]
# ДОБРЕ: залежності кешуються окремоFROM python:3.11-slimWORKDIR /appCOPY requirements.txt . # Змінюється лише при зміні depsRUN pip install -r requirements.txt # Кешується, якщо deps не зміненіCOPY . . # Зміни в коді не скидають кеш pipCMD ["python", "app.py"]Docker Compose (локальна робота з кількома контейнерами)
Розділ «Docker Compose (локальна робота з кількома контейнерами)»Зупиніться та подумайте: Якщо у вас є вебзастосунок та база даних, чому поганою ідеєю буде помістити їх обох в один Dockerfile та контейнер? Як би ви масштабували вебзастосунок незалежно від бази даних, якби вони були жорстко пов’язані?
Для локальної розробки з кількома сервісами використовується:
docker-compose.yml
version: '3.8'
services: web: build: . ports: - "8000:8000" environment: - DATABASE_URL=postgres://db:5432/mydb depends_on: - db
db: image: postgres:15 environment: - POSTGRES_DB=mydb - POSTGRES_PASSWORD=secret volumes: - db_data:/var/lib/postgresql/data
volumes: db_data:# Запустити всі сервісиdocker compose up -d
# Перегляд логівdocker compose logs -f
# Зупинити всі сервісиdocker compose down
# Зупинити та видалити томи (volumes)docker compose down -vПримітка: Docker Compose призначений для локальної розробки. Для продакшну його замінює Kubernetes.
Візуалізація: робочий процес Docker
Розділ «Візуалізація: робочий процес Docker»┌─────────────────────────────────────────────────────────────┐│ РОБОЧИЙ ПРОЦЕС DOCKER │├─────────────────────────────────────────────────────────────┤│ ││ 1. НАПИСАННЯ ││ ┌─────────────┐ ││ │ Dockerfile │ ││ └──────┬──────┘ ││ │ ││ ▼ ││ 2. ЗБІРКА ││ ┌─────────────┐ ││ │docker build │──────► Образ (myapp:v1) ││ └──────┬──────┘ ││ │ ││ ▼ ││ 3. ПУБЛІКАЦІЯ (опційно) ││ ┌─────────────┐ ││ │docker push │──────► Реєстр (Docker Hub, ECR...) ││ └──────┬──────┘ ││ │ ││ ▼ ││ 4. ЗАПУСК ││ ┌─────────────┐ ││ │docker run │──────► Контейнер (екземпляр, що працює)││ └─────────────┘ ││ │└─────────────────────────────────────────────────────────────┘Найкращі практики
Розділ «Найкращі практики»Порівняння базових образів
Розділ «Порівняння базових образів»Вибір правильного базового образу критично важливий для безпеки та розміру:
| Тип базового образу | Приклад | Розмір | Поверхня атаки | Найкраще для |
|---|---|---|---|---|
| Повна ОС | ubuntu:22.04 | ~70MB | Велика (містить усі утиліти) | Складні застарілі застосунки з багатьма системними залежностями |
| Slim | python:3.11-slim | ~40MB | Середня (спрощений Debian) | Більшість стандартних застосунків, баланс розміру та сумісності |
| Alpine | python:3.11-alpine | ~15MB | Мала (використовує musl замість glibc) | Надмалі образи, але можуть виникати проблеми з компіляцією C-розширень |
| Distroless | gcr.io/distroless/static | ~2MB | Мінімальна (без оболонки чи менеджера пакетів) | Продакшн-розгортання компільованих мов (Go, Rust) |
Розмір образу
Розділ «Розмір образу»# ПОГАНО: повна ОС, величезний образFROM ubuntu:22.04RUN apt-get update && apt-get install -y python3 python3-pipCOPY . .RUN pip3 install -r requirements.txt
# ДОБРЕ: база slim, менший образFROM python:3.11-slimCOPY requirements.txt .RUN pip install --no-cache-dir -r requirements.txtCOPY . .
# ЩЕ КРАЩЕ: Alpine (крихітна база)FROM python:3.11-alpine# Примітка: Alpine використовує apk замість apt, та musl замість glibcБезпека
Розділ «Безпека»# ПОГАНО: запуск від імені rootFROM python:3.11-slimCOPY . .CMD ["python", "app.py"] # Запускається як root!
# ДОБРЕ: користувач без прав rootFROM python:3.11-slimRUN useradd -m appuserWORKDIR /appCOPY --chown=appuser:appuser . .USER appuserCMD ["python", "app.py"]Один процес на контейнер
Розділ «Один процес на контейнер»# ПОГАНО: кілька сервісів в одному контейнеріКонтейнер: nginx + python app + redis
# ДОБРЕ: окремі контейнериКонтейнер 1: nginxКонтейнер 2: python appКонтейнер 3: redis(Використовуйте Docker Compose або Kubernetes для оркестрації)Чи знали ви?
Розділ «Чи знали ви?»- Багатоетапна збірка (multi-stage builds) суттєво зменшує розмір образу. Збірка відбувається на одному етапі, а на фінальний копіюються лише готові артефакти.
- Тег
latestне є особливим. Docker не оновлює його автоматично. Це просто домовленість, яка означає “те, що було завантажено останнім”. - Docker Desktop — це не те саме, що Docker. Docker (інструмент) безплатний. Docker Desktop (GUI/VM для Mac/Windows) має ліцензійні вимоги для бізнесу.
- BuildKit — це стандартний інструмент збірки. Починаючи з Docker 23.0, двигун BuildKit є стандартним, він пропонує паралельне виконання, краще кешування та вищу продуктивність порівняно зі старим builder.
Типові помилки
Розділ «Типові помилки»| Помилка | Чому це шкодить | Рішення |
|---|---|---|
Використання тегу latest | Непередбачувані версії | Використовуйте конкретні теги (:1.25.3) |
| Запуск від імені root | Ризик для безпеки | Додайте інструкцію USER |
| Ігнорування порядку шарів | Повільна перезбірка | Ставте елементи, що часто змінюються, в кінець |
| Копіювання всього підряд | Великі образи, витік секретів | Використовуйте .dockerignore |
| Відсутність очищення | Диск переповнюється | Регулярно запускайте docker system prune |
| Жорстке кодування секретів | Паролі залишаються в образі | Використовуйте build secrets або ін’єкцію під час виконання |
| Використання ADD замість COPY | Неочікуване розпакування архівів | Використовуйте COPY за замовчуванням |
| Ігнорування multi-stage builds | Компілятори залишаються в продакшн-образі | Відокремлюйте інструменти збірки від середовища виконання |
Вправа: знайдіть баги
Розділ «Вправа: знайдіть баги»Зупиніться та подумайте: Подивіться на наступний Dockerfile. Тут є щонайменше три серйозні антипатерни або баги. Чи зможете ви їх знайти до того, як прочитаєте відповіді?
FROM node:18COPY . .RUN npm installCMD npm startПереглянути відповіді
- Відсутній .dockerignore або вибірковий COPY: Копіювання
.у.перед запускомnpm installозначає, що локальна папкаnode_modulesможе бути скопійована, а вона зазвичай завелика та специфічна для певної платформи. Також будь-яка зміна коду скидає кешnpm install. - Ігнорування кешування шарів: Спочатку слід зробити
COPY package*.json ./, потімRUN npm install, а вже після цьогоCOPY . .. Це гарантує, що залежності перевстановлюватимуться лише при змініpackage.json. - Використання повної ОС як бази: Образ
node:18займає майже 1 ГБ. Краще використатиnode:18-slimабоnode:18-alpine, щоб зменшити поверхню атаки та час завантаження образу. - Запуск від імені root: Користувач
USERне вказаний, що означає запуск процесу Node з правами root всередині контейнера — це ризик для безпеки.
Контрольні запитання
Розділ «Контрольні запитання»-
Сценарій: Ви розробляєте Node.js застосунок. Щоразу, коли ви змінюєте один рядок CSS, Docker витрачає 3 хвилини на перезбірку образу, бо перевстановлює всі NPM-пакети. Як переписати Dockerfile, щоб виправити це?
Відповідь
Слід відокремити копіювання файлів залежностей від коду застосунку. Спочатку виконайте `COPY package.json` та `RUN npm install`. Потім використовуйте окрему команду `COPY . .` для решти коду. Це активує кеш шарів Docker: шар `npm install` повторно виконуватиметься лише при зміні `package.json`, а зміна CSS відбуватиметься миттєво. -
Сценарій: У вас локально запущений контейнер із базою даних, але після його перезапуску всі збережені користувачі зникають. Якого прапорця команди docker run вам бракує?
Відповідь
Вам бракує монтування тому (volume mount), зокрема прапорця `-v` або `--mount` (наприклад, `-v db_data:/var/lib/postgresql/data`). За замовчуванням контейнери ефемерні — будь-які дані, записані в шар контейнера, видаляються разом із ним. Том зберігає дані поза життєвим циклом контейнера безпосередньо на хост-машині. -
Сценарій: Ваш вебзастосунок успішно запускається в контейнері, але при переході на
localhost:8080ви отримуєте помилку 500 Internal Server Error. Які дві команди Docker ви використаєте першими для діагностики?Відповідь
Першою має бути `docker logs`, щоб переглянути стандартний вивід застосунку на предмет помилок або звітів про збої. Якщо це не допомогло, наступний крок — `docker exec -it sh`, щоб отримати доступ до командної оболонки всередині контейнера. Там можна перевірити конфігураційні файли, змінні середовища або виконати локальний запит curl, щоб перевірити, чи слухає процес потрібний порт. -
Сценарій: Розробник намагається зменшити розмір образу, додавши
RUN rm -rf /tmp/large-dataодразу після крокуRUN curl -o /tmp/large-data https://example.com/file. Однак розмір фінального образу не змінився. Чому?Відповідь
Образи Docker будуються за допомогою об’єднаної файлової системи (union file system), де кожна інструкція (як `RUN`, `COPY`) створює новий незмінний шар. Коли файл було завантажено, він назавжди зафіксувався в цьому конкретному шарі. Наступна команда `RUN rm` створює *новий* шар, який лише позначає файл як видалений, приховуючи його, але дані залишаються в попередньому шарі. Щоб заощадити місце, завантаження та видалення мають бути в одній інструкції `RUN`, поєднані через `&&`. -
Сценарій: Ви розгортаєте компільований Go-бінарний файл. Команда безпеки вимагає, щоб образ не містив жодних оболонок (як
bashчиcurl), щоб мінімізувати поверхню атаки. Який тип образу (Full OS, Slim, Alpine чи Distroless) ви оберете?Відповідь
Правильним вибором буде образ Distroless (наприклад, `gcr.io/distroless/static`). Такі образи містять лише застосунок та його залежності, без менеджерів пакетів, оболонок та стандартних утиліт Unix. Alpine хоч і малий, але містить менеджер пакетів (`apk`) та оболонку (`sh`), якими може скористатися зловмисник. -
Сценарій: У вас є файл Docker Compose з сервісами
frontendтаbackend. Фронтенд намагається отримати дані за адресоюhttp://localhost:5000/api, але з’єднання відхиляється. Чомуlocalhostтут не працює і що треба використовувати замість нього?Відповідь
У контексті контейнера `localhost` вказує лише на внутрішній мережевий інтерфейс самого контейнера. Контейнер фронтенду шукає бекенд всередині себе, де його немає. Замість цього фронтенд має використовувати `http://backend:5000/api`, оскільки Docker Compose автоматично налаштовує внутрішню мережу DNS, де назви сервісів перетворюються на відповідні IP-адреси контейнерів. -
Сценарій: Контейнер зі скриптом обробки даних завершився з помилкою Out of Memory і тепер має статус
Exited. Ви хочете запуститиdocker exec -it <container_id> bash, щоб переглянути тимчасові файли. Що станеться?Відповідь
Команда `docker exec` не спрацює, бо вона може запускати нові процеси лише в контейнерах зі статусом `Running`. Ви не можете запустити оболонку в зупиненому контейнері. Щоб отримати доступ до файлів, скористайтеся `docker cp:/path/to/files ./local-dir` або створіть новий образ із зупиненого контейнера за допомогою `docker commit`. -
Сценарій: У вас є локальний файл
archive.tar.gzз ассетами, які треба розпакувати в/var/www/htmlобразу. Ви використаєтеADD archive.tar.gz /var/www/html/чиCOPY archive.tar.gz /var/www/html/?Відповідь
У цьому випадку краще використати `ADD`, оскільки він має функцію автоматичного розпакування локальних tar-архівів. Якщо ви використаєте `COPY`, архів буде просто скопійовано як стиснутий файл, і вам знадобиться додаткова команда `RUN tar -xzf` (та наявність утиліти `tar` в образі) для його розпакування. Проте для звичайного копіювання файлів `COPY` є кращим вибором через свою прозорість.
Практична вправа
Розділ «Практична вправа»Рівень 1: Основи (Збірка та запуск)
Розділ «Рівень 1: Основи (Збірка та запуск)»Завдання: Зібрати та запустити власний образ вебсервера.
- Створіть каталог і всередині нього файл
index.htmlіз текстом “Hello KubeDojo!”. - Створіть
Dockerfile, що використовуєnginx:alpineяк базовий образ. - Скопіюйте ваш
index.htmlу/usr/share/nginx/html/index.htmlвсередині образу. - Зберіть образ із назвою
dojo-web:v1. - Запустіть контейнер у фоновому режимі, прокинувши порт 8080 вашого хоста на порт 80 контейнера.
- Перевірте результат за допомогою
curl http://localhost:8080.
Рівень 2: Середній (Середовище та логи)
Розділ «Рівень 2: Середній (Середовище та логи)»Завдання: Діагностувати несправний контейнер за допомогою логів та змінних середовища.
- Запустіть
docker run -d --name db postgres:15. - Перевірте його статус за допомогою
docker ps -a. Ви побачите, що він одразу зупинився. - Дізнайтеся причину зупинки через
docker logs db. (Підказка: база скаржиться на відсутність пароля). - Видаліть несправний контейнер.
- Запустіть його знову, передавши необхідну змінну середовища
POSTGRES_PASSWORD=secret. - Переконайтеся, що він продовжує працювати.
Рівень 3: Просунутий (Оптимізація та Exec)
Розділ «Рівень 3: Просунутий (Оптимізація та Exec)»Завдання: Оптимізувати збірку та дослідити запущений контейнер.
- Напишіть Dockerfile, який встановлює
curlу базовий образubuntu. - Переконайтеся, що ви використовуєте
apt-get update && apt-get install -y curlв одній інструкціїRUN. Поясніть, чому це важливо для кешування шарів. - Зберіть та запустіть контейнер інтерактивно (
-it) з командоюbash. - Всередині контейнера підтвердьте, що ви працюєте від імені root, ввівши
whoami. - Введіть
exit, щоб вийти. Помітьте, що контейнер зупинився. Як би ви залишили його працювати у фоновому режимі, щоб пізніше підключитися через exec?
Підсумок
Розділ «Підсумок»Основи Docker для Kubernetes:
Команди:
docker run— запуск контейнерівdocker ps— список контейнерівdocker logs— перегляд виводуdocker exec— виконання команд у контейнерахdocker build— створення образівdocker push/pull— обмін образами
Основи Dockerfile:
FROM— базовий образCOPY— додавання файлівRUN— виконання під час збіркиCMD— команда запуску за замовчуванням
Найкращі практики:
- Використовуйте конкретні теги образів
- Оптимізуйте кешування шарів
- Запускайте не від імені root
- Один процес на один контейнер
Наступний модуль
Розділ «Наступний модуль»Модуль 1.3: Що таке Kubernetes? — Огляд оркестрації контейнерів високого рівня.