Docker — контейнеризация приложений

Docker позволяет упаковывать приложение вместе с его окружением в изолированный контейнер и запускать его одинаково на любой машине. Это решает проблему «работает на моей машине».

Уровень: начальный 19 разделов ⏱ 3–4 часа

1. Что такое Docker и зачем он нужен

У любого приложения есть зависимости: нужные версии Python, системные библиотеки, конфигурационные файлы, базы данных. Когда приложение переезжает на другую машину, что-то обязательно ломается. Это классическая проблема «работает на моей машине».

Docker решает её с помощью контейнеров — лёгких изолированных окружений, в которые упаковывается приложение вместе со всеми зависимостями. Контейнер содержит всё необходимое и работает одинаково на компьютере разработчика и на сервере.

Контейнеры vs виртуальные машины

Часто контейнеры путают с виртуальными машинами (ВМ). Принципиальная разница в том, что ВМ запускают отдельную операционную систему целиком, а контейнеры используют ядро хост-системы.

ХарактеристикаКонтейнерВиртуальная машина
Операционная системаобщая с хостомполная своя ОС
Размермегабайтыгигабайты
Время запускасекундыминуты
Потребление ресурсовминимальноевысокое
Изоляцияна уровне процессовполная

Образы и контейнеры

В Docker есть два ключевых понятия:

Проводи аналогию: образ — это установочный файл программы, а контейнер — сама запущенная программа.

2. Установка Docker

Самый простой способ установить Docker на Windows или macOS — Docker Desktop. Это приложение с графическим интерфейсом, которое включает сам движок Docker и удобное управление контейнерами.

На Windows Docker Desktop использует подсистему WSL2 (Windows Subsystem for Linux). Если WSL2 не установлена, установщик предложит сделать это автоматически. После установки перезагрузи компьютер.

После установки проверь, что Docker работает:

docker --version
# Docker version 27.0.3, build 7d4bcd8

docker run hello-world

Команда docker run hello-world скачает тестовый образ и запустит его. Если в терминале появилось приветственное сообщение, значит, установка прошла успешно.

В PowerShell или командной строке нужно запускать команду docker от имени обычного пользователя. Ошибка вида «Cannot connect to the Docker daemon» чаще всего означает, что Docker Desktop не запущен.

3. Образы и реестры

Образы хранятся в реестрах — серверах для распространения контейнеров. Самый известный реестр — Docker Hub (hub.docker.com), где собраны тысячи готовых образов: Python, Node.js, PostgreSQL, Nginx и другие.

Скачивание образов

docker pull python:3.12-slim
docker pull nginx:alpine

Посмотреть локальные образы можно командой docker images:

docker images
# REPOSITORY   TAG       IMAGE ID       CREATED       SIZE
# python      3.12-slim 88bd7dbe0e15   2 weeks ago   147MB
# nginx       alpine    3f8a86689cx9   3 weeks ago   42MB

Теги образов

У образов есть теги — версии. Запись nginx:alpine означает образ nginx с тегом alpine (лёгкая редакция на базе Alpine Linux). Если тег не указан, по умолчанию берётся latest.

Для Python выбирай официальные образы со суффиксами -slim и -alpine — они заметно меньше по размеру.

Загрузка своих образов

Если хочешь поделиться своим образом, сначала создай аккаунт на Docker Hub, затем войди и выполни команды:

docker login
docker push yourname/myapp:1.0

4. Запуск контейнеров

Основная команда для запуска контейнера — docker run. Разберём её флаги на примере веб-сервера Nginx:

docker run -d -p 8080:80 --name my-web nginx:alpine

Теперь сайт доступен по адресу http://localhost:8080.

Тома и переменные окружения

Для передачи переменных окружения используется флаг --env (или короткий -e):

docker run -d -p 5432:5432 --name my-db \
  -e POSTGRES_USER=admin \
  -e POSTGRES_PASSWORD=secret \
  postgres:16

Чтобы данные не терялись при пересоздании контейнера, монтируют тома флагом -v:

docker run -d -v pgdata:/var/lib/postgresql/data postgres:16

Управление контейнерами

docker ps                  # список запущенных контейнеров
docker ps -a               # список всех контейнеров
docker stop my-web         # остановить
docker start my-web        # снова запустить
docker rm my-web           # удалить контейнер
У каждого контейнера есть идентификатор (первые символы hex-строки) и имя. Дальше в командах можно использовать любой из них.

5. Dockerfile

Dockerfile — текстовый файл с инструкциями по сборке образа. Он определяет базовый образ, копирует код, устанавливает зависимости и задаёт команду запуска. Создадим образ для простого Python-приложения.

FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .

EXPOSE 8000

CMD ["python", "app.py"]

Ключевые инструкции

ИнструкцияНазначение
FROMбазовый образ, с которого начинается сборка
WORKDIRрабочая директория внутри контейнера
COPYкопирование файлов из хоста в образ
RUNвыполнение команды во время сборки
EXPOSEуказать порт, который слушает приложение (документация)
CMDкоманда по умолчанию при запуске контейнера
ENTRYPOINTфиксированная команда, которую нельзя легко переопределить

Разница между CMD и ENTRYPOINT: CMD можно переопределить при запуске (docker run image команда), а ENTRYPOINT всегда выполняется, а аргументы лишь добавляются к нему.

6. Сборка образа

Образ собирается командой docker build. Параметр -t задаёт имя и тег, а точка в конце указывает на директорию с Dockerfile:

docker build -t myapp:1.0 .

Если нужно пересобрать без использования кэша:

docker build --no-cache -t myapp:1.0 .

Слои и кэширование

Каждая инструкция в Dockerfile создаёт слой (layer). Docker кэширует слои, поэтому при повторной сборке пересобираются только изменённые шаги, а неизменённые берутся из кэша. Это сильно ускоряет сборку.

Располагай редко изменяемые инструкции (установку зависимостей) в начале Dockerfile, а часто изменяемые (копирование кода) — в конце. Тогда при каждом изменении кода зависимости не будут переустанавливаться.
docker build -t myapp:1.0 .
docker build -t myapp:1.1 .
# Вторая сборка использует кэш, пока не дойдёт до COPY app.py

7. Тома и сети

Том (volume) — специальное хранилище, которое переживает жизнь контейнера. Именно туда стоит класть базы данных и пользовательские файлы.

Создать именованный том и подключить его к контейнеру:

docker volume create mydata
docker run -d -v mydata:/app/data myapp:1.0

Можно монтировать и обычную папку с хоста (bind mount) — удобно при разработке, когда код меняется без пересборки образа:

docker run -v /полный/путь/на/хосте:/app myapp:1.0

Сети

Контейнеры могут общаться между собой по сети. По умолчанию Docker создаёт сеть bridge. Посмотреть сети и создать новую:

docker network ls
docker network create mynet
docker run -d --network mynet --name web myapp:1.0
docker run -d --network mynet --name db postgres:16
Внутри созданной сети контейнеры обращаются друг к другу по именам: контейнер web может подключиться к db по адресу db:5432.

8. Логи и отладка

Просмотр логов контейнера — первое, что нужно сделать при диагностике. Флаг -f следит за логами в реальном времени:

docker logs my-web
docker logs -f my-web        # читать логи непрерывно
docker logs --tail 100 my-web  # последние 100 строк

Запуск команд внутри контейнера

Чтобы зайти в запущенный контейнер и выполнять команды, используется docker exec:

docker exec -it my-web bash
docker exec my-web python app.py --help
Флаг -it (интерактивный режим + выделенный псевдотерминал) нужен для входа в оболочку. Без него команда не начнёт интерактивную сессию.

Анализ образа и ресурсов

docker inspect показывает подробную информацию о контейнере или образе в формате JSON, а docker stats — живые показатели использования CPU и памяти:

docker inspect my-web
docker stats

9. docker-compose

Приложение часто состоит из нескольких сервисов: веб-сервер, база данных, кэш. Запускать их по отдельности неудобно. docker-compose позволяет описать все сервисы в одном файле и управлять ими одной командой.

Файл docker-compose.yml для приложения Python с PostgreSQL и Redis:

version: "3.9"

services:
  web:
    build: .
    ports:
      - "8000:8000"
    depends_on:
      - db
      - redis

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: admin
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

  redis:
    image: redis:alpine

volumes:
  pgdata:

Основные команды compose

docker-compose up -d     # запустить все сервисы в фоне
docker-compose down      # остановить и удалить контейнеры
docker-compose logs -f   # логи всех сервисов
docker-compose exec web bash  # команда внутри сервиса web
docker-compose ps        # статус сервисов
Секция depends_on указывает порядок запуска: web начнётся только после db и redis. Имена сервисов используются как DNS-имена внутри сети compose.

10. Чистка и лучшие практики

Со временем накапливаются неиспользуемые образы, контейнеры и тома. Очистить систему помогает одна команда:

docker system prune -a
Флаг -a удаляет все неиспользуемые образы, включая те, на которые нет ссылок. Проверь, что не удаляешь нужное.

Рекомендации

Пример .dockerignore:

__pycache__/
*.pyc
.git/
venv/
.env

Мультистейдж сборка (кратко)

Мультистейдж собирает образ в несколько этапов: на первом этапе компилируется и устанавливается приложение со всеми зависимостями, а в итоговый образ копируются только необходимые артефакты. В результате финальный образ получается маленьким.

FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN go build -o app main.go

FROM alpine:latest
COPY --from=builder /src/app /app
CMD ["/app"]

11. Docker в CI/CD и рабочий процесс

Docker — ключевая часть современных пайплайнов непрерывной интеграции и доставки (CI/CD). Образ собирается автоматически при каждом изменении кода и затем разворачивается на серверах.

В GitLab пайплайн настраивается с помощью GitLab Runner и файла .gitlab-ci.yml. Типичный этап сборки и публикации образа:

build:
  image: docker:27
  services:
    - docker:27-dind
  script:
    - docker build -t registry.example.com/myapp:$CI_COMMIT_SHA .
    - docker push registry.example.com/myapp:$CI_COMMIT_SHA

После сборки образ загружается в реестр, откуда его забирают при деплое на сервер или в оркестратор.

Оркестрация

Запускать контейнеры в продакшене на десятках серверов вручную невозможно. Для этого существуют оркестраторы:

Начни с docker-compose для локальной разработки, затем изучи развёртывание на одном сервере, и только потом переходи к Kubernetes — это сложная, но мощная тема.

12. Переменные окружения и .env

Контейнеры не должны содержать секреты в образе. Конфигурацию передают переменными окружения:

docker run -e DATABASE_URL=postgres://db:5432/app myapp
docker run --env-file .env myapp

Файл .env описывает значения для запуска:

# .env — не коммить в репозиторий
DB_HOST=db
DB_PORT=5432
SECRET_KEY=super-secret

В compose переменные подставляются автоматически и доступны в контейнерах:

services:
  web:
    build: .
    env_file: .env
    ports: ["8080:80"]
Держи в репозитории шаблон .env.example с именами переменных без значений, а реальные секреты — вне git.
Хранение паролей и токенов в Dockerfile (ENV) делает их видимыми в истории образа. Пользуйтесь .env и secret-management на проде.

13. Healthcheck и политики перезапуска

Docker может проверять здоровье контейнера и перезапускать упавшие сервисы:

docker run -d \
  --restart unless-stopped \
  --health-cmd="curl -f http://localhost/health" \
  --health-interval=10s \
  --health-retries=5 \
  myapp
ПолитикаПоведение
noне перезапускать (по умолчанию)
on-failure[:n]только при ошибке, максимум n раз
alwaysперезапускать всегда
unless-stoppedкроме явной остановки
docker inspect --format "{{.State.Health.Status}}" myapp
Health-команда должна использовать бинарник, который есть в образе (curl/wget). Для полной надёжности здоровье проверяет и сам сервис.

14. Ресурсы и безопасность контейнеров

Ограничение ресурсов защищает хост от «жадного» приложения:

docker run -d \
  --memory=512m \
  --cpus=1.0 \
  --pids-limit=256 \
  --read-only \
  myapp
services:
  web:
    image: myapp
    mem_limit: 512m
    cpu_shares: 512
    read_only: true   # приложение может писать только в тома
Безопасность контейнерной инфраструктуры подробнее в курсе Безопасность веб-приложений.

15. Диагностика: events, stats, top

Когда что-то идёт не так, помогают живые команды Docker:

docker events                      # поток событий: старт/стоп/крэш
docker stats                       # CPU/память/сеть в реальном времени
docker top myapp                   # процессы внутри контейнера
docker inspect myapp               # полная конфигурация и Health
# последние логи с временными метками и фильтром по строке
docker logs -f --timestamps myapp | grep -i error
При утечке памяти смотри docker stats до и после нагрузки; рост RSS выше лимита — прямой сигнал к настройке ООМKill и ограничений памяти.
Логи и метрики собирай в единый стек (например, ELK из урока ELK), иначе на реальном проде диагностика превращается в хаос.

16. Анализ состава образа и сканеры уязвимостей

Образ — это слои поверх базового образа. Посмотри, из чего он собран:

docker history myapp:latest      # слои и команды, которые их создали
docker image inspect myapp       # метаданные, env, entrypoint
trivy image --severity HIGH,CRITICAL myapp:latest
Сканеры находят известные уязвимости, но не заменяют обновления: следи, чтоб базовый образ обновлялся регулярно.

17. Разработка с горячей перезагрузкой в контейнере

Для быстрого цикла правка-перезагрузка не нужно пересобирать образ: смонтируй исходники в контейнер и дай процессу следить за файлами:

services:
  web:
    build: .
    volumes:
      - ./src:/app/src          # исходники с хоста
    command: npm run dev        # nodemon / --watch перезапускает сервис
docker compose watch            # докеризированный горячий reload
Разделяй образ dev (тома + debug-инструменты) и prod (только нужное, без dev-зависимостей).

18. Мультиархитектурные образы и buildx

Один тег образа должен работать на разных платформах: ARM-серверы и Raspberry Pi не всегда умеют x86. Сборка нескольких архитектур и публикация единого манифеста делается через buildx:

docker buildx build \
  --platform linux/amd64,linux/arm64 \
  -t user/myapp:latest --push .
docker buildx ls   # посмотри активные билд-стейджы
Если приложение использует бинарные зависимости (например, image-магики), собирай их под каждую платформу отдельно.

19. Путь к Kubernetes: от compose к манифестам

Compose удобен для одного хоста; рост нагрузки требует оркестратора. Начни с локального Kubernetes:

# kind: кластер в Docker за пару минут
kind create cluster --name dev
kubectl get nodes
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 2
  selector:
    matchLabels: { app: web }
  template:
    metadata:
      labels: { app: web }
    spec:
      containers:
        - name: web
          image: user/myapp:latest
Подробное устройство кластера — в курсе Kubernetes: pod, Service, Ingress и хранение состояния.

20. Секреты при сборке: BuildKit --secret

Ключи в Dockerfile оседают в истории слоёв — никогда так не делай. BuildKit передаёт секреты без попадания в слой:

# docker build --secret id=npm_token,src=./.npmrc.secret
RUN --mount=type=secret,id=npm_token \
    NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
Не дублируй секрет в ENTRYPOINT-скрипте: он окажется в конфиге контейнера, а не в безопасном контуре.

21. Docker-сети в деталях

Сеть решает, кто с кем говорит. Три драйвера покрывают 95% задач:

docker network create -d bridge mynet
docker network inspect mynet --format '{{json .Containers}}'
docker run --network mynet --name api app:1
# внутри сети: curl http://api:8080
«Connection refused» между контейнерами чаще всего значит: разные сети или порт слушает на 127.0.0.1.

22. База данных в контейнере: бэкап

PostgreSQL в контейнере — норма для разработки, но данные нужно беречь:

docker exec db pg_dump -U app mydb | gzip > backup.sql.gz
gunzip -c backup.sql.gz | docker exec -i db psql -U app mydb
Объём ≠ реплика: на боевых данных нужны снапшоты и репликация, а не «перезапусти контейнер».

23. Чек-лист: контейнер не запускается

Сценарий «docker run падает» отработан многими до тебя. Проходи по порядку:

  1. docker logs <id> — первый и главный источник;
  2. docker inspect: статус, exit code, переменные, mounts, health;
  3. запусти с -it --entrypoint /bin/sh — проверь окружение вручную;
  4. сверь порты: -p 3000:8080 — «хост : контейнер».
docker run --rm -it --entrypoint /bin/sh myimage
# вручную: env | grep и запуск процесса руками
90% падений — отсутствие .env или неверный CMD: исправь их, а не «пересобирай всё на всякий случай».

Что дальше

Ты освоил базовый набор Docker: образы, контейнеры, Dockerfile, тома, сети и docker-compose. Рекомендуемый следующий шаг — собрать и задеплоить своё Flask или FastAPI-приложение в контейнере, а затем настроить автоматическую сборку через GitLab.

← Вернуться к курсам