Docker и Docker Compose для разработчиков
Полное практическое руководство по Docker и Docker Compose — от основ контейнеризации до production-ready конфигураций с углубленной безопасностью и troubleshooting
Оглавление
Docker и Docker Compose для разработчиков
Docker решает проблему «у меня работает, а у тебя нет». Если вы устали от фразы «works on my machine» — этот материал для вас.
Как пользоваться материалом
- Спешишь? Читай введение, раздел про Dockerfile и Docker Compose basics.
- Разрабатываешь приложение? Сосредоточься на примерах с веб-приложениями и базами данных.
- Готовишься к деплою? Изучи разделы про multi-stage builds, volumes и production best practices.
- Работаешь с микросервисами? Смотри раздел про networks и оркестрацию нескольких сервисов.
- Не знаешь терминов? Начни с глоссария ниже.
Глоссарий
Container (Контейнер) — Изолированная среда выполнения приложения со всеми зависимостями. Легковесная альтернатива виртуальной машине.
Image (Образ) — Шаблон для создания контейнеров. Содержит ОС, код приложения, библиотеки и конфигурацию.
Dockerfile — Текстовый файл с инструкциями для создания Docker образа.
Docker Compose — Инструмент для управления multi-container приложениями через YAML-конфигурацию.
Volume (Том) — Механизм для постоянного хранения данных вне контейнера.
Network (Сеть) — Виртуальная сеть для связи между контейнерами.
Registry (Реестр) — Хранилище Docker образов (Docker Hub, GitHub Container Registry, частные реестры).
Layer (Слой) — Каждая инструкция в Dockerfile создает слой. Слои кешируются для ускорения сборки.
Build Context — Директория, содержимое которой отправляется Docker daemon при сборке образа.
Container Orchestration (Оркестрация) — Автоматизация развертывания, масштабирования и управления контейнерами (Docker Swarm, Kubernetes).
Введение: Зачем нужен Docker?
Проблема без Docker
Разработчик: "У меня работает!"
Тестировщик: "У меня не запускается, ошибка с Python 3.9"
DevOps: "На сервере вообще другая версия PostgreSQL"
Клиент: "А у меня macOS, ничего не работает"
Часы потрачены на отладку окружения вместо разработки...Решение с Docker
Разработчик: "Вот Dockerfile и docker-compose.yml"
Тестировщик: docker compose up → всё работает
DevOps: docker compose up → всё работает
Клиент: docker compose up → всё работает
Все используют идентичное окружение!Когда использовать Docker
✅ Используйте Docker:
- Веб-приложения с зависимостями (Node.js, Python, Ruby)
- Микросервисы
- CI/CD pipelines
- Разработка в команде (единое окружение)
- Продакшн деплой
- Локальное поднятие инфраструктуры (БД, Redis, Elasticsearch)
❌ НЕ обязательно использовать Docker:
- Простые CLI-скрипты
- Desktop GUI приложения
- Когда команда не готова к обучению
- Проекты с экстремально низкими требованиями к ресурсам
Docker в контексте других инструментов
Docker — не единственное решение для контейнеризации. Понимание альтернатив поможет выбрать правильный инструмент для вашего проекта.
Сравнение инструментов контейнеризации:
| Критерий | Docker | Docker Compose | Docker Swarm | Kubernetes | Podman |
|---|---|---|---|---|---|
| Сложность | Низкая | Низкая | Средняя | Высокая | Низкая |
| Кривая обучения | 1-2 дня | 1 неделя | 2-4 недели | 2-6 месяцев | 1-2 дня |
| Локальная разработка | ✅ | ✅✅ | ⚠️ | ❌ | ✅ |
| Production (малый проект) | ⚠️ | ⚠️ | ✅ | ⚠️ (overkill) | ✅ |
| Production (enterprise) | ❌ | ❌ | ⚠️ | ✅✅ | ⚠️ |
| Автомасштабирование | ❌ | ❌ | ✅ | ✅✅ | ❌ |
| Self-healing | ❌ | ❌ | ✅ | ✅✅ | ❌ |
| Multi-host кластер | ❌ | ❌ | ✅ | ✅ | ⚠️ |
| Управление секретами | ❌ | ⚠️ | ✅ | ✅✅ | ⚠️ |
| Мониторинг out-of-box | ❌ | ❌ | ⚠️ | ✅ | ❌ |
| Сообщество | ✅✅ | ✅✅ | ✅ | ✅✅✅ | ✅ |
Когда использовать каждый инструмент:
Docker (одиночные контейнеры):
- Локальное тестирование контейнеров
- CI/CD pipelines (сборка образов)
- Быстрые эксперименты
- Обучение основам контейнеризации
Docker Compose (этот материал!):
- ✅ Локальная разработка — идеальный выбор
- Простые production проекты (1 сервер)
- Staging окружения
- Быстрый прототип микросервисной архитектуры
Docker Swarm:
- Prod проекты с 3-10 серверами
- Когда Kubernetes избыточен
- Нужна простая оркестрация
- Миграция с Docker Compose (совместимый синтаксис)
Kubernetes:
- ✅ Enterprise production — стандарт индустрии
- Проекты с > 10 серверов
- Сложная микросервисная архитектура
- Нужны advanced features (auto-scaling, service mesh)
- Мультиоблачность (AWS + GCP + on-premise)
Podman:
- Rootless контейнеры (безопасность)
- Замена Docker без daemon
- Совместимость с docker командами
- Compliance требования (no daemon)
Эволюция проекта:
Стадия 1: MVP на ноутбуке
└─ Docker Compose ← Вы здесь после прочтения этого материала
Стадия 2: Первые пользователи (1 сервер)
└─ Docker Compose в production
Стадия 3: Растущий бизнес (2-5 серверов)
└─ Docker Swarm или managed Kubernetes (EKS, GKE)
Стадия 4: Scale-up (10+ серверов)
└─ Kubernetes с полным monitoring stack
Для 90% разработчиков Docker Compose — идеальный баланс между простотой и функциональностью. Начните с него, а Kubernetes изучите когда действительно понадобится.
Основы Docker
Главный принцип Docker: один контейнер = одна зона ответственности (один сервис). Не запускайте в одном контейнере и веб-сервер, и базу данных, и очередь — это антипаттерн!
Почему "один контейнер = один процесс"?
Этот принцип часто неправильно понимают. Точнее: один контейнер = одна служба (сервис), а не буквально один process ID.
6 причин следовать этому принципу:
1. Изоляция отказов
# ❌ ПЛОХО: Всё в одном контейнере
services:
monolith:
# Nginx + App + PostgreSQL + Redis
# Если упадёт PostgreSQL → упадёт ВСЁ! 💀
# ✅ ХОРОШО: Изолированные сервисы
services:
nginx:
image: nginx
app:
image: my-app
db:
image: postgres
redis:
image: redis
# Если упадёт Redis → остальное работает ✅2. Независимое масштабирование
# ❌ Монолит: невозможно масштабировать только app
docker compose up --scale monolith=3
# 3 копии всего (3 БД конфликтуют!)
# ✅ Микросервисы: масштабируем что нужно
docker compose up --scale app=10 --scale celery=5
# 10 app, 5 workers, 1 БД3. Простая отладка
# ❌ Монолит: все логи вместе
docker logs monolith # Nginx + App + DB + Redis в куче
# ✅ Микросервисы: логи по сервисам
docker logs app # Только приложение
docker logs db # Только БД4. Zero-downtime deployments
# ❌ Монолит: БД упадёт при обновлении app
docker stop monolith && docker start monolith-v2
# ✅ Микросервисы: БД работает при обновлении app
docker compose up -d app-v2 # Только app перезапустился5. Переиспользование образов
# ❌ Монолит: 2GB кастомный образ
FROM ubuntu
RUN apt-get install nginx postgresql redis python
# ✅ Микросервисы: официальные образы
FROM postgres:16-alpine # 50MB, переиспользуется
FROM redis:7-alpine # 30MB, переиспользуется
FROM python:3.11-slim # 150MB, переиспользуется6. Разные ресурсы для разных задач
# PostgreSQL: много RAM, persistent volumes
# Nginx: минимум ресурсов
# App: горизонтальное масштабирование
# В одном контейнере настроить это невозможно!Исключения из правила (когда несколько процессов — OK):
# ✅ Nginx + PHP-FPM (тесно связанные процессы одного сервиса)
FROM php:8.2-fpm
RUN apt-get install nginx supervisor
# ✅ App + Log shipper (sidecar паттерн)
# Main: приложение, Sidecar: fluentd
# ✅ App + Tini (init для zombie процессов)
ENTRYPOINT ["/usr/bin/tini", "--"]
CMD ["python", "app.py"]Итого: "Один контейнер = один процесс" на самом деле означает "один контейнер = одна зона ответственности". Это даёт изоляцию отказов, масштабируемость, простоту отладки и возможность orchestration. Не следовать этому — значит потерять все преимущества контейнеризации!
Архитектура Docker
┌─────────────────────────────────────────┐
│ Docker CLI (команды) │
│ docker build, run, push, pull... │
└──────────────┬──────────────────────────┘
│
▼
┌─────────────────────────────────────────┐
│ Docker Daemon (движок) │
│ Управляет контейнерами, образами │
└──────────────┬──────────────────────────┘
│
┌─────────┴──────────┬───────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│Container1│ │Container2│ │Container3│
│ (nginx) │ │(postgres)│ │ (app) │
└──────────┘ └──────────┘ └──────────┘
Установка Docker
macOS / Windows:
# Скачайте Docker Desktop с официального сайта
https://www.docker.com/products/docker-desktop
# После установки проверьте
docker --version
docker compose version # Compose встроен в Docker CLILinux (Ubuntu/Debian):
# Удалите старые версии
sudo apt-get remove docker docker-engine docker.io containerd runc
# Установите зависимости
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release
# Добавьте официальный GPG ключ Docker
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
# Добавьте репозиторий
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Установите Docker
sudo apt-get update
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Проверьте установку
docker --version
docker compose version # Обратите внимание: compose без дефисаОсновные команды Docker
# ========================================
# Работа с образами
# ========================================
# Скачать образ из Docker Hub
docker pull nginx:latest
# Список локальных образов
docker images
# Удалить образ
docker rmi nginx:latest
# Собрать образ из Dockerfile
docker build -t my-app:1.0 .
# ========================================
# Работа с контейнерами
# ========================================
# Запустить контейнер
docker run -d --name my-nginx -p 80:80 nginx
# Опции:
# -d запустить в фоне (detached)
# --name имя контейнера
# -p 80:80 проброс портов host:container
# -v /path:/path проброс volumes
# --rm удалить контейнер после остановки
# -e KEY=VALUE переменные окружения
# Список запущенных контейнеров
docker ps
# Список всех контейнеров (включая остановленные)
docker ps -a
# Остановить контейнер
docker stop my-nginx
# Запустить остановленный контейнер
docker start my-nginx
# Перезапустить контейнер
docker restart my-nginx
# Удалить контейнер
docker rm my-nginx
# Удалить все остановленные контейнеры
docker container prune
# ========================================
# Логи и отладка
# ========================================
# Посмотреть логи контейнера
docker logs my-nginx
# Следить за логами в реальном времени
docker logs -f my-nginx
# Войти в shell контейнера
docker exec -it my-nginx bash
# Посмотреть процессы в контейнере
docker top my-nginx
# Статистика использования ресурсов
docker stats
# Инспектировать контейнер (подробная информация)
docker inspect my-nginx
# ========================================
# Очистка системы
# ========================================
# Удалить неиспользуемые образы, контейнеры, сети
docker system prune
# Удалить вообще всё неиспользуемое (включая volumes)
docker system prune -a --volumesDockerfile: Создание образов
Dockerfile — это рецепт для создания Docker образа. Каждая инструкция создает новый слой, который кешируется. Правильный порядок инструкций ускоряет сборку в разы.
Структура Dockerfile
# ========================================
# Базовый образ
# ========================================
FROM python:3.11-slim
# Метаданные
LABEL maintainer="your-email@example.com"
LABEL version="1.0"
LABEL description="My Python application"
# ========================================
# Переменные окружения
# ========================================
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
# ========================================
# Рабочая директория
# ========================================
WORKDIR /app
# ========================================
# Установка системных зависимостей
# ========================================
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# ========================================
# Копирование и установка Python зависимостей
# ========================================
# Сначала копируем только requirements (для кеширования)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# ========================================
# Копирование кода приложения
# ========================================
COPY . .
# ========================================
# Создание пользователя (безопасность)
# ========================================
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
# ========================================
# Проброс порта
# ========================================
EXPOSE 8000
# ========================================
# Команда запуска
# ========================================
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]Инструкции Dockerfile
FROM — Базовый образ
# Официальные образы
FROM python:3.11-slim # Python (минимальный)
FROM node:20-alpine # Node.js (Alpine Linux — самый легкий)
FROM nginx:alpine # Nginx
FROM postgres:16 # PostgreSQL
FROM redis:7-alpine # Redis
# Выбор версии ОС
FROM ubuntu:22.04 # Ubuntu
FROM debian:bookworm-slim # Debian (минимальный)
FROM alpine:3.18 # Alpine (самый легкий, ~5MB)
# Multi-stage builds (базовый этап)
FROM golang:1.21 AS builderWORKDIR — Рабочая директория
WORKDIR /app
# Все последующие команды (RUN, COPY, CMD) будут выполняться из /app
# Если директория не существует, она будет созданаCOPY vs ADD
# COPY — простое копирование файлов (рекомендуется)
COPY requirements.txt .
COPY src/ /app/src/
COPY . .
# ADD — копирование + распаковка tar.gz + скачивание URL (избегайте)
ADD archive.tar.gz /app/ # Автоматически распакует
ADD https://example.com/file.txt /app/ # Скачает файлИспользуйте COPY вместо ADD. ADD имеет скрытую логику (распаковка, скачивание), что делает Dockerfile менее предсказуемым.
RUN — Выполнение команд при сборке
# ❌ Плохо: каждый RUN = новый слой
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get clean
# ✅ Хорошо: объединяйте команды через &&
RUN apt-get update && \
apt-get install -y --no-install-recommends curl && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# Форматирование для читаемости
RUN apt-get update && \
apt-get install -y \
build-essential \
libpq-dev \
libssl-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*ENV — Переменные окружения
# Один вариант на строку
ENV NODE_ENV=production
ENV PORT=3000
# Или несколько в одной инструкции
ENV NODE_ENV=production \
PORT=3000 \
API_URL=https://api.example.comEXPOSE — Документирование портов
EXPOSE 8000
# Не открывает порт! Только документирует.
# Реальный проброс делается через docker run -p 8000:8000CMD vs ENTRYPOINT
# CMD — команда по умолчанию (можно переопределить)
CMD ["python", "app.py"]
# docker run my-image → запустит python app.py
# docker run my-image bash → переопределит, запустит bash
# ENTRYPOINT — фиксированная команда
ENTRYPOINT ["python"]
CMD ["app.py"]
# docker run my-image → python app.py
# docker run my-image test.py → python test.py (только аргументы меняются)
# Полная форма
ENTRYPOINT ["python", "app.py"]
# docker run my-image --debug → python app.py --debugПример: Dockerfile для Python FastAPI приложения
# ========================================
# Базовый образ
# ========================================
FROM python:3.11-slim AS base
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PIP_NO_CACHE_DIR=1
WORKDIR /app
# ========================================
# Этап 1: Зависимости для сборки
# ========================================
FROM base AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
libpq-dev && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# ========================================
# Этап 2: Production образ
# ========================================
FROM base AS production
# Копируем установленные пакеты из builder
COPY --from=builder /root/.local /root/.local
# Обновляем PATH
ENV PATH=/root/.local/bin:$PATH
# Копируем код приложения
COPY . .
# Создаем непривилегированного пользователя
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 8000
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD python -c "import requests; requests.get('http://localhost:8000/health')"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]Пример: Dockerfile для Node.js приложения
# ========================================
# Этап 1: Сборка
# ========================================
FROM node:20-alpine AS builder
WORKDIR /app
# Копируем только package*.json для кеширования
COPY package*.json ./
# Устанавливаем зависимости
RUN npm ci --only=production
# Копируем исходный код
COPY . .
# Если есть build step (например, TypeScript)
# RUN npm run build
# ========================================
# Этап 2: Production образ
# ========================================
FROM node:20-alpine AS production
WORKDIR /app
# Копируем node_modules из builder
COPY --from=builder /app/node_modules ./node_modules
# Копируем собранное приложение
COPY --from=builder /app .
# Создаем пользователя
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "server.js"].dockerignore — Ускорение сборки
# .dockerignore — аналог .gitignore для Docker
# Git
.git
.gitignore
# Зависимости (устанавливаются внутри контейнера)
node_modules
__pycache__
*.pyc
venv
env
# IDE
.vscode
.idea
*.swp
# Тесты (не нужны в production)
tests/
*.test.js
*.spec.js
# Документация
README.md
docs/
# CI/CD
.github
.gitlab-ci.yml
# Docker
Dockerfile
docker-compose.yml
.dockerignore
# Логи и временные файлы
*.log
tmp/
temp/
*.tmp
# OS
.DS_Store
Thumbs.dbОптимизация Dockerfile
Правильный порядок инструкций — ключ к быстрой сборке. Docker кеширует слои, поэтому часто меняющиеся файлы должны копироваться в конце.
# ❌ ПЛОХО: Код приложения копируется ДО установки зависимостей
FROM python:3.11-slim
WORKDIR /app
COPY . . # ← При любом изменении кода весь кеш сбрасывается
RUN pip install -r requirements.txt # ← Пересборка зависимостей при каждом изменении!
CMD ["python", "app.py"]
# ✅ ХОРОШО: Сначала зависимости, потом код
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt . # ← Кеш сбрасывается только при изменении requirements.txt
RUN pip install -r requirements.txt # ← Используется кеш если requirements.txt не менялся
COPY . . # ← Меняется часто, но зависимости уже в кеше
CMD ["python", "app.py"]
# Время сборки:
# Плохой вариант: 60 секунд при каждом изменении
# Хороший вариант: 60 секунд первый раз, потом 2-3 секундыDocker Compose: Оркестрация multi-container приложений
Docker Compose превращает набор docker run команд в один понятный YAML-файл. Идеально для локальной разработки и простых деплоев.
Важно: С 2023 года Docker Compose встроен в Docker CLI как подкоманда.
Используйте docker compose (через пробел) вместо устаревшего
docker-compose (через дефис). Файл по-прежнему называется
docker-compose.yml, а поле version: больше не требуется.
Структура docker-compose.yml
# docker-compose.yml
# Примечание: поле version устарело и больше не требуется
services:
# ========================================
# Веб-приложение
# ========================================
web:
build:
context: .
dockerfile: Dockerfile
image: my-app:latest
container_name: my-app-web
restart: unless-stopped
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
- REDIS_URL=redis://redis:6379/0
env_file:
- .env
volumes:
- ./app:/app
- static_volume:/app/static
depends_on:
- db
- redis
networks:
- app-network
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# ========================================
# База данных PostgreSQL
# ========================================
db:
image: postgres:16-alpine
container_name: my-app-db
restart: unless-stopped
environment:
POSTGRES_DB: mydb
POSTGRES_USER: user
POSTGRES_PASSWORD: pass
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d mydb"]
interval: 10s
timeout: 5s
retries: 5
# ========================================
# Redis
# ========================================
redis:
image: redis:7-alpine
container_name: my-app-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- app-network
command: redis-server --appendonly yes
# ========================================
# Volumes (постоянное хранение данных)
# ========================================
volumes:
postgres_data:
redis_data:
static_volume:
# ========================================
# Networks (сети для связи контейнеров)
# ========================================
networks:
app-network:
driver: bridgeКоманды Docker Compose
# ========================================
# Запуск и остановка
# ========================================
# Запустить все сервисы
docker compose up
# Запустить в фоне (detached mode)
docker compose up -d
# Пересобрать образы перед запуском
docker compose up --build
# Запустить конкретный сервис
docker compose up web
# Остановить все сервисы
docker compose down
# Остановить и удалить volumes
docker compose down -v
# ========================================
# Сборка
# ========================================
# Собрать образы
docker compose build
# Собрать без кеша
docker compose build --no-cache
# Собрать конкретный сервис
docker compose build web
# ========================================
# Логи и мониторинг
# ========================================
# Посмотреть логи всех сервисов
docker compose logs
# Следить за логами в реальном времени
docker compose logs -f
# Логи конкретного сервиса
docker compose logs -f web
# ========================================
# Выполнение команд
# ========================================
# Выполнить команду в запущенном контейнере
docker compose exec web bash
# Выполнить разовую команду (создаст новый контейнер)
docker compose run web python manage.py migrate
# Выполнить команду без создания новых контейнеров
docker compose exec web python manage.py createsuperuser
# ========================================
# Масштабирование
# ========================================
# Запустить 3 экземпляра сервиса
docker compose up --scale web=3
# ========================================
# Прочее
# ========================================
# Посмотреть запущенные сервисы
docker compose ps
# Перезапустить сервис
docker compose restart web
# Пауза и возобновление
docker compose pause
docker compose unpause
# Валидация docker-compose.yml
docker compose configПример: Full-stack приложение
# docker-compose.yml для веб-приложения
services:
# ========================================
# Frontend (React/Next.js)
# ========================================
frontend:
build:
context: ./frontend
dockerfile: Dockerfile.dev
container_name: app-frontend
restart: unless-stopped
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules # Исключаем node_modules из синхронизации
environment:
- NEXT_PUBLIC_API_URL=http://localhost:8000
networks:
- app-network
depends_on:
- backend
# ========================================
# Backend (FastAPI/Django)
# ========================================
backend:
build:
context: ./backend
dockerfile: Dockerfile.dev
container_name: app-backend
restart: unless-stopped
ports:
- "8000:8000"
volumes:
- ./backend:/app
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
- REDIS_URL=redis://redis:6379/0
- CELERY_BROKER_URL=redis://redis:6379/1
env_file:
- ./backend/.env
networks:
- app-network
depends_on:
db:
condition: service_healthy
redis:
condition: service_started
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
# ========================================
# Celery Worker (фоновые задачи)
# ========================================
celery_worker:
build:
context: ./backend
dockerfile: Dockerfile.dev
container_name: app-celery-worker
restart: unless-stopped
volumes:
- ./backend:/app
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
- CELERY_BROKER_URL=redis://redis:6379/1
- CELERY_RESULT_BACKEND=redis://redis:6379/1
networks:
- app-network
depends_on:
- db
- redis
command: celery -A tasks worker --loglevel=info
# ========================================
# PostgreSQL
# ========================================
db:
image: postgres:16-alpine
container_name: app-db
restart: unless-stopped
environment:
POSTGRES_DB: app_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
- ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
ports:
- "5432:5432"
networks:
- app-network
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
# ========================================
# Redis
# ========================================
redis:
image: redis:7-alpine
container_name: app-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
networks:
- app-network
command: redis-server --appendonly yes
# ========================================
# Nginx (reverse proxy + static files)
# ========================================
nginx:
image: nginx:alpine
container_name: app-nginx
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
- ./nginx/ssl:/etc/nginx/ssl:ro
- static_volume:/var/www/static:ro
networks:
- app-network
depends_on:
- frontend
- backend
volumes:
postgres_data:
redis_data:
static_volume:
networks:
app-network:
driver: bridgeEnvironment Variables и .env файлы
# .env файл в корне проекта
# Database
DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
POSTGRES_DB=app_db
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
# Redis
REDIS_URL=redis://redis:6379/0
CELERY_BROKER_URL=redis://redis:6379/1
# Application
SECRET_KEY=your-secret-key-here
DEBUG=True
ALLOWED_HOSTS=localhost,127.0.0.1
# API Keys
STRIPE_API_KEY=sk_test_xxxxx
AWS_ACCESS_KEY_ID=AKIAXXXXX# docker-compose.yml
services:
backend:
# Вариант 1: Подключить весь .env файл
env_file:
- .env
# Вариант 2: Явно указать переменные
environment:
- DATABASE_URL=${DATABASE_URL}
- SECRET_KEY=${SECRET_KEY}
- DEBUG=${DEBUG:-False} # Значение по умолчанию
# Вариант 3: Hardcode (не рекомендуется для секретов)
environment:
- NODE_ENV=developmentНИКОГДА не коммитьте .env файлы с секретами в Git! Добавьте .env в .gitignore и создайте .env.example с примерами значений.
Volumes: Постоянное хранение данных
Контейнеры эфемерны — при удалении контейнера все данные внутри теряются. Volumes решают эту проблему, сохраняя данные вне контейнера.
Типы монтирования
services:
app:
volumes:
# ========================================
# 1. Named Volume (рекомендуется для продакшн)
# ========================================
- db_data:/var/lib/postgresql/data
# Docker управляет хранением (обычно /var/lib/docker/volumes/)
# Не привязано к файловой системе хоста
# ========================================
# 2. Bind Mount (удобно для разработки)
# ========================================
- ./app:/app
# Синхронизация с локальной файловой системой
# Изменения в ./app мгновенно видны в контейнере
# Исключение поддиректорий (анонимный volume)
- /app/node_modules # Не синхронизировать node_modules
# Read-only монтирование
- ./config:/config:ro
# ========================================
# 3. tmpfs (в памяти, для временных данных)
# ========================================
- type: tmpfs
target: /tmp
tmpfs:
size: 100m
volumes:
db_data: # Объявление named volumeИсключение node_modules: Как это работает?
Паттерн - /app/node_modules — это один из самых частых источников путаницы в
Docker. Давайте разберем, что происходит под капотом.
Проблема без исключения:
# ❌ Так делать НЕ надо для Node.js проектов
services:
frontend:
volumes:
- ./frontend:/app # Монтируем всю директориюЧто происходит:
Ваш компьютер (macOS/Windows):
./frontend/
├── package.json
├── src/
└── node_modules/ ← Скомпилированы для вашей ОС
└── (бинарники для macOS/Windows)
Docker контейнер (Linux):
/app/
├── package.json
├── src/
└── node_modules/ ← Перезаписаны с хоста!
└── (бинарники для macOS/Windows в Linux контейнере) ❌
Результат: Приложение падает с ошибками "module not found" или "incompatible binary"
Решение: Анонимный volume для node_modules
# ✅ Правильный способ
services:
frontend:
volumes:
- ./frontend:/app # Монтируем все файлы
- /app/node_modules # Исключаем node_modulesЧто происходит теперь:
1. Docker создает контейнер и устанавливает зависимости:
RUN npm install → /app/node_modules (Linux бинарники)
2. Монтирование volumes происходит в таком порядке:
Шаг 1: - ./frontend:/app
/app/ ← перезаписывается содержимым ./frontend/
Шаг 2: - /app/node_modules
/app/node_modules ← создается анонимный volume, который "маскирует"
./frontend/node_modules
3. Финальная структура в контейнере:
/app/
├── package.json (с хоста)
├── src/ (с хоста, hot-reload работает!)
└── node_modules/ (из контейнера, не с хоста!)
└── (Linux бинарники работают)
Визуализация слоев:
┌─────────────────────────────────────┐
│ Слой 3: Anonymous Volume │ Приоритет: 3 (самый высокий)
│ - /app/node_modules │
│ (из контейнера после npm install) │
└─────────────────────────────────────┘
▲ "маскирует"
┌─────────────────────────────────────┐
│ Слой 2: Bind Mount │ Приоритет: 2
│ - ./frontend:/app │
│ (с вашего компьютера) │
└─────────────────────────────────────┘
▲ "перезаписывает"
┌─────────────────────────────────────┐
│ Слой 1: Образ контейнера │ Приоритет: 1 (самый низкий)
│ (из Dockerfile) │
└─────────────────────────────────────┘
Альтернативный синтаксис (именованный volume):
services:
frontend:
volumes:
- ./frontend:/app
- node_modules:/app/node_modules # Именованный volume
volumes:
node_modules: # Переиспользуется между перезапускамиКогда использовать:
# Node.js приложения
- ./app:/app
- /app/node_modules
# Python приложения (аналогично)
- ./app:/app
- /app/__pycache__ # Исключаем кеш Python
- /app/.venv # Если используете venv внутри контейнера
# Go приложения
- ./app:/app
- /app/vendor # Исключаем vendor
# Общий паттерн:
# Монтируем весь проект, но исключаем директории с бинарниками/кешемПроверка, что это работает:
# Запустите контейнер
docker compose up -d frontend
# Войдите внутрь
docker compose exec frontend sh
# Проверьте node_modules
ls -la /app/node_modules
# Должны быть Linux бинарники (из npm install в контейнере)
# Проверьте, что изменения в src/ работают
# Измените файл в ./frontend/src/
# Он должен мгновенно отобразиться в контейнере:
cat /app/src/your-file.jsПримеры использования
Разработка (hot reload):
services:
web:
volumes:
- ./src:/app/src # Код синхронизируется
- /app/node_modules # node_modules остаются в контейнереProduction (база данных):
services:
db:
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
driver: localКонфигурация (read-only):
services:
nginx:
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:roУправление volumes
# Список всех volumes
docker volume ls
# Создать volume
docker volume create my-volume
# Инспектировать volume
docker volume inspect my-volume
# Удалить volume
docker volume rm my-volume
# Удалить все неиспользуемые volumes
docker volume prune
# Backup volume (PostgreSQL пример)
docker run --rm \
-v postgres_data:/data \
-v $(pwd):/backup \
alpine tar czf /backup/db-backup.tar.gz /data
# Restore volume
docker run --rm \
-v postgres_data:/data \
-v $(pwd):/backup \
alpine tar xzf /backup/db-backup.tar.gz -C /Networks: Связь между контейнерами
services:
frontend:
networks:
- frontend-network
backend:
networks:
- frontend-network
- backend-network
db:
networks:
- backend-network
networks:
frontend-network:
backend-network:Связь через имена сервисов:
# backend/app.py
import requests
# Вместо localhost используем имя сервиса из docker-compose.yml
response = requests.get('http://db:5432')depends_on и порядок запуска контейнеров
depends_on только определяет порядок запуска контейнеров, но НЕ ждет,
пока сервис будет готов принимать подключения. База данных может быть
запущена, но еще не готова!
Базовый depends_on
services:
web:
build: .
depends_on:
- db
- redis
# Docker запустит db и redis ПЕРЕД web
# Но НЕ будет ждать готовности БД!
db:
image: postgres:16-alpine
redis:
image: redis:7-alpineЧто происходит:
1. Docker запускает db
2. Docker запускает redis
3. Docker запускает web (даже если db еще инициализируется)
4. web пытается подключиться к db → ошибка connection refused ❌
depends_on с условиями (рекомендуется)
services:
web:
build: .
depends_on:
db:
condition: service_healthy # Ждать health check
redis:
condition: service_started # Ждать только запуска
environment:
DATABASE_URL: postgresql://postgres:postgres@db:5432/myapp
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
# Теперь web запустится только когда БД готова ✅
redis:
image: redis:7-alpine
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5Health checks для популярных сервисов
# PostgreSQL
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
# MySQL
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
# Redis
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
# MongoDB
healthcheck:
test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
interval: 10s
timeout: 5s
retries: 5
# Web приложение
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s # Дать время на инициализациюАльтернатива: wait-for скрипты
Если health checks не подходят, используйте wait-for скрипты:
# Dockerfile
FROM python:3.11-slim
# Установите wait-for-it
ADD https://raw.githubusercontent.com/vishnubob/wait-for-it/master/wait-for-it.sh /wait-for-it.sh
RUN chmod +x /wait-for-it.sh
COPY . /app
WORKDIR /app
# Используйте wait-for-it в command# docker-compose.yml
services:
web:
build: .
command: >
sh -c "/wait-for-it.sh db:5432 --timeout=30 --strict --
python manage.py migrate &&
python manage.py runserver 0.0.0.0:8000"
depends_on:
- dbBest Practices: Перезапуск и обновление
Частая ошибка: docker compose restart НЕ применяет изменения из
docker-compose.yml! Он только останавливает и запускает существующие
контейнеры.
Когда использовать какую команду
# ========================================
# 1. Изменили docker-compose.yml (env, ports, volumes)
# ========================================
# ❌ НЕ РАБОТАЕТ
docker compose restart web
# Перезапускает контейнер со старой конфигурацией
# ✅ ПРАВИЛЬНО
docker compose up -d web
# Пересоздает контейнер с новой конфигурацией
# ========================================
# 2. Изменили Dockerfile или код приложения
# ========================================
# ❌ НЕ РАБОТАЕТ
docker compose restart web
# Использует старый образ
# ✅ ПРАВИЛЬНО
docker compose up -d --build web
# Пересобирает образ и пересоздает контейнер
# Или в два этапа:
docker compose build web
docker compose up -d web
# ========================================
# 3. Изменили переменные окружения в .env
# ========================================
# ❌ НЕ РАБОТАЕТ
docker compose restart web
# Не перечитывает .env файл
# ✅ ПРАВИЛЬНО
docker compose up -d --force-recreate web
# Пересоздает контейнер с новыми env переменными
# ========================================
# 4. Просто нужно перезапустить сервис (без изменений)
# ========================================
# ✅ РАБОТАЕТ
docker compose restart web
# Быстрый stop + start существующего контейнера
# ========================================
# 5. Обновили образ на Docker Hub
# ========================================
# ✅ ПРАВИЛЬНО
docker compose pull web # Скачать новый образ
docker compose up -d web # Пересоздать контейнер
# ========================================
# 6. Полное обновление всех сервисов
# ========================================
# ✅ ПРАВИЛЬНО (продакшн)
docker compose pull # Обновить все образы
docker compose up -d # Пересоздать изменившиеся контейнеры
docker image prune -f # Очистить старые образы
# ✅ ПРАВИЛЬНО (разработка)
docker compose down # Остановить все
docker compose build # Пересобрать образы
docker compose up -d # Запустить зановоЧто делает каждая команда
# restart — только stop + start контейнера
# ✅ Когда: сервис завис, нужен быстрый перезапуск
# ❌ НЕ применяет: изменения конфигурации, новый код, новые env
docker compose restart web
# up -d — создает/пересоздает контейнеры по необходимости
# ✅ Когда: изменили docker-compose.yml, .env, volumes
# ✅ Применяет: новую конфигурацию, новые env
# ❌ НЕ применяет: изменения в Dockerfile (нужен --build)
docker compose up -d web
# up -d --build — пересобирает образ и пересоздает контейнер
# ✅ Когда: изменили Dockerfile, код приложения
# ✅ Применяет: все изменения в коде и конфигурации
docker compose up -d --build web
# up -d --force-recreate — принудительно пересоздает контейнер
# ✅ Когда: изменили .env, нужна гарантия пересоздания
# ✅ Применяет: новые env, конфигурацию
docker compose up -d --force-recreate web
# down + up — полный пересоздание с очисткой
# ✅ Когда: радикальные изменения, проблемы с сетями/volumes
# ⚠️ Удаляет: контейнеры, сети (volumes остаются)
docker compose down
docker compose up -d
# down -v + up — полная очистка включая данные
# ✅ Когда: нужно сбросить БД, начать с чистого листа
# ⚠️ Удаляет: контейнеры, сети, volumes (ВСЕ ДАННЫЕ!)
docker compose down -v
docker compose up -dWorkflow для разработки
# ========================================
# Первый запуск проекта
# ========================================
docker compose up -d
docker compose logs -f # Проверить логи
# ========================================
# Изменили код приложения
# ========================================
# Если есть hot-reload (volumes монтированы) — ничего не делать
# Если нет hot-reload:
docker compose up -d --build web
# ========================================
# Изменили зависимости (package.json, requirements.txt)
# ========================================
docker compose build web # Пересобрать с новыми зависимостями
docker compose up -d web
# ========================================
# Изменили docker-compose.yml (порты, env)
# ========================================
docker compose up -d # Применить изменения
# ========================================
# Добавили новый сервис
# ========================================
docker compose up -d # Запустит только новый сервис
# ========================================
# Полная перезагрузка (если что-то сломалось)
# ========================================
docker compose down
docker compose up -d --build
# ========================================
# Очистить все (включая данные БД)
# ========================================
docker compose down -v
docker compose up -d --buildЧек-лист перед применением изменений
# 1. Проверить синтаксис docker-compose.yml
docker compose config
# 2. Проверить, какие сервисы будут пересозданы (dry-run)
docker compose up -d --dry-run
# 3. Посмотреть логи перед изменениями
docker compose logs --tail=50 web
# 4. Создать backup БД (если есть важные данные)
docker compose exec db pg_dump -U postgres mydb > backup.sql
# 5. Применить изменения
docker compose up -d
# 6. Проверить, что все запустилось
docker compose ps
docker compose logs -f web
# 7. Проверить health checks
docker compose ps # STATUS должен быть healthyЧастые ошибки
# ❌ ОШИБКА: Изменили код, но не пересобрали образ
docker compose restart web
# Решение:
docker compose up -d --build web
# ❌ ОШИБКА: Изменили .env, но не пересоздали контейнер
docker compose restart web
# Решение:
docker compose up -d --force-recreate web
# ❌ ОШИБКА: Приложение стартует раньше БД
depends_on:
- db # Недостаточно!
# Решение: добавить healthcheck
depends_on:
db:
condition: service_healthy
# ❌ ОШИБКА: Volumes не обновляются
docker compose down
docker compose up -d # Volumes остались старыми
# Решение (если нужно очистить):
docker compose down -v # ⚠️ Удалит все данные!Production Best Practices
Разработка и продакшн — разные миры. Никогда не используйте --reload, bind mounts и debug режим в production!
Multi-stage Builds
# ========================================
# Stage 1: Builder (установка зависимостей)
# ========================================
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build
# ========================================
# Stage 2: Production (минимальный образ)
# ========================================
FROM node:20-alpine AS production
WORKDIR /app
# Копируем только необходимое из builder
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
COPY package.json .
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 3000
CMD ["node", "dist/server.js"]Результат:
Builder stage: 800MB
Production stage: 150MB ← В 5 раз меньше!
docker-compose.prod.yml
# docker-compose.prod.yml
services:
web:
build:
context: .
dockerfile: Dockerfile.prod
restart: always
environment:
- NODE_ENV=production
- DATABASE_URL=${DATABASE_URL}
# ❌ Убираем bind mounts
# ❌ Убираем --reload
# ✅ Добавляем health checks
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# ✅ Ограничиваем ресурсы
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
reservations:
cpus: "0.5"
memory: 512M
db:
image: postgres:16-alpine
restart: always
environment:
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- postgres_data:/var/lib/postgresql/data
secrets:
db_password:
file: ./secrets/db_password.txt
volumes:
postgres_data:
driver: localЗапуск:
docker compose -f docker-compose.prod.yml up -dSecurity Best Practices
Безопасность — не опциональная фича. Один взломанный контейнер может скомпрометировать весь хост. Следуйте этим практикам с самого начала.
1. Выбор базового образа
Сравнение типов образов:
| Тип | Размер | Безопасность | Use Case | Пример |
|---|---|---|---|---|
| Full | 800-1000MB | ⚠️ Низкая | Development, legacy apps | python:3.11 |
| Slim | 150-200MB | ✅ Средняя | Production apps | python:3.11-slim |
| Alpine | 50-80MB | ✅ Высокая | Минимал истичные apps | python:3.11-alpine |
| Distroless | 20-50MB | ✅✅ Очень высокая | Security-critical | gcr.io/distroless/python3 |
| Scratch | 0MB (бинарник) | ✅✅✅ Максимальная | Compiled apps (Go, Rust) | FROM scratch |
# ❌ Плохо: полный образ с ненужными пакетами
FROM python:3.11
# 1GB, содержит gcc, make, git и сотни других пакетов
# ✅ Хорошо: slim для баланса
FROM python:3.11-slim
# 150MB, минимальный набор для Python
# ✅✅ Отлично: distroless для максимальной безопасности
FROM gcr.io/distroless/python3-debian12
# 50MB, только Python runtime, нет shell, нет package managerКогда использовать:
- Full — только для development с активной отладкой
- Slim — оптимальный выбор для большинства production приложений
- Alpine — когда критичен размер (embedded, edge computing)
- Distroless — финансовые, медицинские, security-critical системы
- Scratch — statically compiled Go/Rust бинарники
2. Непривилегированный пользователь
Запуск от root — критическая уязвимость. Если атакующий получит доступ к
контейнеру, он сможет читать /etc/shadow, модифицировать системные файлы и
потенциально выйти из контейнера на хост.
Демонстрация проблемы:
# ❌ Опасно: запуск от root (по умолчанию)
FROM python:3.11-slim
COPY app.py .
CMD ["python", "app.py"]
# Проверка:
# docker exec -it container whoami # root 💀
# docker exec -it container cat /etc/shadow # работает! 💀Правильное решение (Debian/Ubuntu):
FROM python:3.11-slim
WORKDIR /app
# Установка зависимостей (требует root)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Копирование кода
COPY . .
# Создание непривилегированного пользователя
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
# Переключение на непривилегированного пользователя
USER appuser
CMD ["python", "app.py"]Для Alpine Linux:
FROM python:3.11-alpine
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
# Alpine использует adduser вместо useradd
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
chown -R appuser:appuser /app
USER appuser
CMD ["python", "app.py"]3. Сканирование уязвимостей
Trivy (рекомендуется — быстрый, бесплатный, точный):
# Установка
brew install trivy # macOS
# или
apt-get install trivy # Ubuntu/Debian
# Сканирование образа
trivy image python:3.11-slim
# Только критические и высокие
trivy image --severity HIGH,CRITICAL python:3.11-slim
# CI/CD: fail при наличии критических уязвимостей
trivy image --severity CRITICAL --exit-code 1 my-app:latest
# Сканирование Dockerfile (до сборки)
trivy config Dockerfile
# Игнорирование конкретных CVE (если нет fix)
trivy image --ignorefile .trivyignore my-app:latest
# .trivyignore
# CVE-2024-12345 (no fix available yet)GitHub Actions интеграция:
name: Security Scan
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t my-app:${{ github.sha }} .
- name: Run Trivy scanner
uses: aquasecurity/trivy-action@master
with:
image-ref: "my-app:${{ github.sha }}"
severity: "CRITICAL,HIGH"
exit-code: "1" # Fail build при уязвимостях
format: "sarif"
output: "trivy-results.sarif"
- name: Upload results to GitHub Security
uses: github/codeql-action/upload-sarif@v2
with:
sarif_file: "trivy-results.sarif"Docker Scout (встроенный в Docker Desktop):
# Включить Docker Scout
docker scout enable
# Сканирование
docker scout cves my-app:latest
# Рекомендации по исправлению
docker scout recommendations my-app:latest
# Сравнение версий
docker scout compare my-app:v1.0 my-app:v1.1Snyk (альтернатива с хорошим UI):
# Установка
npm install -g snyk
# Авторизация
snyk auth
# Сканирование
snyk container test my-app:latest
# Мониторинг (уведомления о новых CVE)
snyk container monitor my-app:latest4. Secrets Management
НИКОГДА не коммитьте секреты в код или Dockerfile. Даже если репозиторий приватный. Даже "временно". История Git помнит всё.
❌ Антипаттерны (что НЕ делать):
# ❌ 1. Hardcoded секреты
ENV DATABASE_PASSWORD=superSecret123
ENV API_KEY=sk_live_xxxxxxxxxxxx
# ❌ 2. ARG не защищает (остается в docker history)
ARG SECRET_KEY
ENV SECRET_KEY=$SECRET_KEY
# docker history показывает значение! 💀
# ❌ 3. COPY секретов в образ
COPY .env /app/.env
COPY secrets.json /app/config/
# docker cp может извлечь! 💀Проверка утечки:
# Проверить, нет ли секретов в образе
docker history my-app:latest
# Проверить environment variables
docker inspect my-app:latest | jq '.[].Config.Env'
# Поиск секретов в коде (truffleHog)
docker run --rm -v $(pwd):/pwd trufflesecurity/trufflehog:latest filesystem /pwd✅ Правильные подходы:
Development: .env файлы (НЕ коммитить в Git!)
# .gitignore
.env
.env.local
*.secret
# .env.example (коммитить)
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
SECRET_KEY=your-secret-key-here
API_KEY=your-api-key-here# docker-compose.yml
services:
app:
env_file:
- .env # Файл НЕ в Git!
environment:
- NODE_ENV=developmentProduction: Docker Secrets (Swarm/Compose)
# Создание secret
echo "my-db-password" | docker secret create db_password -
# Или из файла
docker secret create db_password ./db_password.txt# docker-compose.yml (Swarm mode)
services:
app:
secrets:
- db_password
- api_key
environment:
# Путь к secret файлу
DB_PASSWORD_FILE: /run/secrets/db_password
API_KEY_FILE: /run/secrets/api_key
secrets:
db_password:
external: true
api_key:
external: true# app.py
import os
def read_secret(secret_name):
try:
with open(f'/run/secrets/{secret_name}', 'r') as f:
return f.read().strip()
except FileNotFoundError:
# Fallback для development
return os.getenv(secret_name.upper())
db_password = read_secret('db_password')
api_key = read_secret('api_key')Enterprise: HashiCorp Vault
# Использование Vault
import hvac
client = hvac.Client(url='https://vault.example.com')
client.token = os.getenv('VAULT_TOKEN')
secret = client.secrets.kv.v2.read_secret_version(
path='prod/database',
)
db_password = secret['data']['data']['password']Cloud Secrets Manager (AWS/GCP/Azure)
# AWS Secrets Manager
import boto3
client = boto3.client('secretsmanager', region_name='us-east-1')
response = client.get_secret_value(SecretId='prod/db/password')
db_password = response['SecretString']Сравнение методов:
| Метод | Безопасность | Сложность | Стоимость | Use Case |
|---|---|---|---|---|
| .env файлы | ⚠️ Низкая | Простая | Free | Development only |
| Docker Secrets | ✅ Средняя | Средняя | Free | Swarm production |
| Vault | ✅✅ Высокая | Высокая | Self-hosted | Enterprise on-premise |
| AWS Secrets Manager | ✅✅ Высокая | Средняя | ~$0.40/secret/month | AWS production |
| GCP Secret Manager | ✅✅ Высокая | Средняя | ~$0.06/secret/month | GCP production |
5. Hardening образов
Multi-stage builds для минимизации attack surface:
# ========================================
# Stage 1: Builder с инструментами
# ========================================
FROM python:3.11 AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# ========================================
# Stage 2: Distroless production
# ========================================
FROM gcr.io/distroless/python3-debian12
WORKDIR /app
# Копируем только runtime зависимости
COPY --from=builder /root/.local /root/.local
COPY app.py .
ENV PATH=/root/.local/bin:$PATH
CMD ["python", "app.py"]
# Результат: 50MB вместо 1GB, нет shell, нет package managerRead-only filesystem:
services:
app:
image: my-app:latest
read_only: true # Файловая система только для чтения
tmpfs:
- /tmp:size=100M # Временные файлы в RAM
- /var/run:size=10M
volumes:
- app_uploads:/app/uploads # Только явные write volumesCapabilities (минимальные привилегии):
services:
app:
cap_drop:
- ALL # Удаляем ВСЕ capabilities
cap_add:
- NET_BIND_SERVICE # Добавляем только необходимые (порты < 1024)
security_opt:
- no-new-privileges:true # Запрещаем повышение привилегий6. Runtime Security
services:
app:
# Минимальные capabilities
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
# Security options
security_opt:
- no-new-privileges:true
- apparmor=docker-default # AppArmor profile
- seccomp=./seccomp-profile.json # Syscall filtering
# Resource limits (защита от DoS)
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
reservations:
cpus: "0.5"
memory: 512M
# PID limits
pids_limit: 100
# Read-only root filesystem
read_only: true
tmpfs:
- /tmp
- /var/run7. Production Security Checklist
Используйте этот чек-лист перед каждым production деплоем. Один пропущенный пункт может стоить безопасности всей системы.
Pre-Deployment (за 48 часов до релиза):
- Образы от официальных источников или внутреннего реестра
- Все образы просканированы Trivy/Snyk, критических CVE нет
- Нет секретов в
docker history my-app:latest - Нет секретов в
docker inspect my-app:latest - Используется непривилегированный пользователь (
USER appuser) - Базовый образ: slim/alpine/distroless (не full)
- Multi-stage build (отделен builder от runtime)
- Health checks настроены (
HEALTHCHECKили в compose) - Resource limits установлены (CPU, memory)
- Логирование в stdout/stderr (не в файлы)
- Секреты через Secrets Manager/Vault (не .env)
- Read-only filesystem где возможно
- Capabilities минимизированы (
cap_drop: ALL) .dockerignoreисключает .git, .env, secrets- SBOM сгенерирован и сохранен
Post-Deployment (первые 24 часа):
- Smoke tests прошли успешно
- Метрики в норме (CPU < 70%, Memory < 80%)
- Логи не содержат ERROR/CRITICAL
- Health checks работают (все контейнеры
healthy) - Backup/restore процедура протестирована
- Мониторинг алертов настроен (Prometheus/Grafana)
- Логи централизованы (ELK/Loki)
Weekly Maintenance:
- Обновление патч-версий базовых образов
- Повторное сканирование на уязвимости
- Ротация секретов (согласно политике)
- Проверка логов на подозрительную активность
- Обновление SBOM
8. Security Auditing Tools
# ========================================
# Docker Bench Security (CIS Benchmarks)
# ========================================
# Запуск аудита хоста
docker run --rm --net host --pid host --userns host --cap-add audit_control \
-v /var/lib:/var/lib \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /etc:/etc:ro \
docker/docker-bench-security
# Проверяет:
# - Конфигурацию Docker daemon
# - Настройки безопасности хоста
# - Конфигурацию контейнеров
# - Security operations
# ========================================
# Hadolint (Dockerfile linter)
# ========================================
# Установка
brew install hadolint
# Проверка Dockerfile
hadolint Dockerfile
# Автофикс (где возможно)
hadolint --format json Dockerfile | jq
# CI/CD интеграция
docker run --rm -i hadolint/hadolint < Dockerfile
# ========================================
# Dockle (Image linter)
# ========================================
# Установка
brew install goodwithtech/r/dockle
# Проверка образа
dockle my-app:latest
# Только критичные проблемы
dockle --exit-code 1 --exit-level fatal my-app:latest
# ========================================
# Docker Content Trust (подписи образов)
# ========================================
# Включить проверку подписей
export DOCKER_CONTENT_TRUST=1
# Теперь docker pull проверяет подписи
docker pull my-app:latest # Fail если нет подписи
# Подписать образ при push
docker trust sign my-app:latest9. SBOM (Software Bill of Materials)
SBOM — это список всех компонентов вашего софта. Критичен для отслеживания уязвимостей и compliance (NIST, EU Cyber Resilience Act).
# ========================================
# Syft — генерация SBOM
# ========================================
# Установка
brew install syft
# Генерация SBOM в SPDX формате
syft packages my-app:latest -o spdx-json > sbom.json
# CycloneDX формат
syft packages my-app:latest -o cyclonedx-json > sbom.cdx.json
# SBOM для Dockerfile (до сборки)
syft packages dir:. -o spdx-json > sbom-source.json
# ========================================
# Grype — проверка уязвимостей в SBOM
# ========================================
# Установка
brew install grype
# Проверка SBOM
grype sbom:sbom.json
# Проверка образа напрямую
grype my-app:latest
# Только HIGH/CRITICAL
grype --fail-on high my-app:latest
# ========================================
# CI/CD Pipeline
# ========================================
# GitHub Actions
- name: Generate SBOM
run: |
syft packages my-app:latest -o spdx-json > sbom.json
- name: Scan SBOM for vulnerabilities
run: |
grype sbom:sbom.json --fail-on critical
- name: Upload SBOM as artifact
uses: actions/upload-artifact@v3
with:
name: sbom
path: sbom.json10. Реальные примеры атак и защита
Эти атаки происходят в реальности. Каждый пример основан на реальных CVE и инцидентах.
Атака 1: Container Breakout через Docker socket
# ❌ КРИТИЧЕСКАЯ УЯЗВИМОСТЬ
services:
app:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
# Что может сделать атакующий:
# 1. Запустить привилегированный контейнер
# 2. Получить root на хосте
# 3. Читать файлы хоста
# 4. Запускать любые команды на хостеЭксплуатация:
# Внутри скомпрометированного контейнера
docker run -it --privileged --net=host --pid=host --ipc=host \
-v /:/host alpine chroot /host
# Теперь атакующий имеет root на хосте! 💀✅ Защита:
# НИКОГДА не монтируйте docker.sock в production
# Если необходимо (CI/CD runners):
services:
gitlab-runner:
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro # Read-only
# + используйте rootless Docker
# + изолируйте в отдельную сеть
# + мониторьте docker API вызовыАтака 2: Secrets в docker history
# ❌ Уязвимость
FROM python:3.11-slim
ENV API_KEY=sk_live_51234567890abcdef # Секрет в слое!
RUN pip install requests
CMD ["python", "app.py"]Эксплуатация:
docker history my-app:latest
# OUTPUT:
# ENV API_KEY=sk_live_51234567890abcdef # Видно в plain text! 💀
# Даже если удалить ENV позже, он останется в слоях:
docker save my-app:latest | tar -xO | grep "API_KEY"✅ Защита:
# Используйте multi-stage build + secrets mount
FROM python:3.11-slim
# Секрет передается через build-time secret (не остается в слоях)
RUN --mount=type=secret,id=api_key \
pip install requests
CMD ["python", "app.py"]# Build с секретом
echo "sk_live_51234567890abcdef" > /tmp/api_key.txt
docker build --secret id=api_key,src=/tmp/api_key.txt -t my-app .
rm /tmp/api_key.txt
# Секрет НЕ остается в образе
docker history my-app:latest # API_KEY не видно ✅Атака 3: Supply Chain Attack
# ❌ Опасно
FROM random-username/python:latest
# Проблемы:
# 1. Неизвестный автор (не Docker Official)
# 2. latest тег (может измениться)
# 3. Нет проверки подписиРеальный инцидент: В 2023 году атакующие загрузили malware в популярные Docker образы на Docker Hub. Образы скачали тысячи раз.
✅ Защита:
# 1. Официальные образы
FROM python:3.11.7-slim
# 2. Точный digest (immutable)
FROM python:3.11.7-slim@sha256:abc123def456...
# 3. Внутренний registry (proxy Docker Hub)
FROM registry.company.com/python:3.11.7-slim
# 4. Verify образы с Docker Content Trust# Включить проверку подписей
export DOCKER_CONTENT_TRUST=1
# Сканирование перед использованием
trivy image python:3.11-slim
# Проверка SBOM
syft packages python:3.11-slim | grep malwareЗолотое правило безопасности: Trust but verify. Даже официальные образы могут содержать уязвимости. Всегда сканируйте, всегда обновляйте, всегда минимизируйте.
Troubleshooting: Распространенные проблемы
90% проблем с Docker решаются через логи (docker logs) и inspect (docker inspect). Используйте дерево диагностики ниже для быстрой навигации.
Дерево диагностики
Что не работает?
│
├─ Контейнер не запускается / сразу падает
│ ├─ Exit code 0 → См. Проблема 1 (CMD завершился успешно, но это ошибка)
│ ├─ Exit code 1 → См. Проблема 1 (ошибка приложения, смотри logs)
│ ├─ Exit code 137 → См. Проблема 11 (OOM killed - не хватает памяти)
│ └─ Exit code 139 → Segmentation fault (проблема в коде приложения)
│
├─ Контейнер запущен, но приложение не работает
│ ├─ Не отвечает на запросы → См. Проблема 3 (порты)
│ ├─ Ошибки подключения к БД → См. Проблема 2, 9 (networks, health checks)
│ └─ Медленно работает → См. Проблема 5, 11 (I/O, ресурсы)
│
├─ docker-compose.yml не применяется
│ └─ Изменения не видны → См. Проблема 6 (restart vs up -d)
│
├─ Проблемы с данными
│ ├─ Данные пропали после restart → См. Проблема 4 (volumes)
│ ├─ Изменения кода не видны → См. Проблема 4, 8 (volumes, cache)
│ └─ Логи заполнили диск → См. Проблема 12 (log rotation)
│
└─ Другое
├─ Высокое потребление ресурсов → См. Проблема 11
├─ Контейнер "завис" → См. Проблема 10 (deadlock)
└─ Permission denied → См. Проблема 7 (CRLF, права)
Проблема 1: Контейнер сразу останавливается
# Посмотреть логи
docker compose logs web
# Типичные причины:
# - Ошибка в CMD/ENTRYPOINT
# - Приложение падает при запуске
# - Неправильные переменные окружения
# Проверить, запущен ли процесс
docker compose exec web ps auxПроблема 2: Контейнеры не видят друг друга
# Проверить, что контейнеры в одной сети
docker network inspect my-app_app-network
# Проверить DNS резолвинг
docker compose exec web ping db
# Типичные причины:
# - Неправильное имя сервиса в URL
# - Разные networks у сервисов
# - Сервис еще не запущен (нет depends_on)Проблема 3: Порт уже занят
# Error: port is already allocated
# Найти процесс на порту
lsof -i :8000
# Или использовать другой порт
ports:
- "8001:8000" # host:containerПроблема 4: Changes не отображаются
# 1. Проверить volumes
docker compose exec web ls -la /app
# 2. Пересобрать образ
docker compose build --no-cache web
# 3. Перезапустить с пересборкой
docker compose up --build
# 4. Проверить .dockerignore
cat .dockerignoreПроблема 5: Медленная работа (macOS/Windows)
# Проблема: Bind mounts медленные на macOS/Windows
# ❌ Медленно
volumes:
- ./app:/app
# ✅ Быстрее: delegated mode (macOS)
volumes:
- ./app:/app:delegated
# ✅ Или используйте named volume для node_modules
volumes:
- ./app:/app
- /app/node_modulesПроблема 6: docker-compose.yml не применяется
Симптомы:
- Изменили
docker-compose.yml(порты, env, volumes) - Запустили
docker compose restart - Изменения не применились
Причина:
docker compose restart только останавливает и запускает существующие контейнеры. Не пересоздает их с новой конфигурацией!
Решение:
# ❌ НЕ РАБОТАЕТ
docker compose restart web
# ✅ ПРАВИЛЬНО: пересоздать контейнер
docker compose up -d web
# Если изменили Dockerfile:
docker compose up -d --build web
# Если изменили .env:
docker compose up -d --force-recreate webПроблема 7: "No such file or directory" в Linux контейнере
Симптомы:
Error: /bin/sh: ./start.sh: No such file or directory
Причины:
- CRLF line endings (Windows → Linux)
- Permissions (файл не исполняемый)
Диагностика:
# Проверить line endings
file start.sh
# Должно быть: "ASCII text" (LF)
# НЕ должно быть: "ASCII text, with CRLF" (Windows)
# Проверить permissions
ls -la start.sh
# Должно быть: -rwxr-xr-x (executable)Решение:
# 1. Конвертировать CRLF → LF
dos2unix start.sh
# Или в Git (автоматически):
# .gitattributes
*.sh text eol=lf
*.py text eol=lf
# 2. Сделать исполняемым
chmod +x start.sh# В Dockerfile (гарантировать permissions)
COPY start.sh /app/
RUN chmod +x /app/start.sh
CMD ["/app/start.sh"]Проблема 8: Зависимости не обновились
Симптомы:
- Изменили
requirements.txt/package.json - Пересобрали образ (
docker compose build) - Старые зависимости все еще используются
Причина:
Docker использует кеш слоев. Если COPY . . выполняется ДО pip install, кеш не сбрасывается.
Решение:
# Пересобрать без кеша
docker compose build --no-cache web
# Или удалить старый образ
docker rmi my-app:latest
docker compose build webПравильный Dockerfile (для избежания проблемы):
# ✅ Сначала зависимости (кешируются)
COPY requirements.txt .
RUN pip install -r requirements.txt
# Потом код (меняется часто)
COPY . .Проблема 9: Health check failed
Симптомы:
docker compose ps
# STATUS: unhealthy
Диагностика:
# Посмотреть детали health check
docker inspect web | jq '.[].State.Health'
# Логи health check
docker inspect web | jq '.[].State.Health.Log'
# Попробовать health check вручную
docker exec web curl -f http://localhost:8000/healthТипичные причины:
- Приложение не слушает на 0.0.0.0 (слушает на 127.0.0.1)
- Health endpoint не существует
- Health check слишком быстрый (приложение не успело стартовать)
Решение:
services:
web:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s # Проверка каждые 30 секунд
timeout: 10s # Таймаут ответа
retries: 3 # Количество попыток
start_period: 40s # ← ВАЖНО: дать время на старт приложенияПроблема 10: Контейнер "завис" (deadlock)
Симптомы:
- Контейнер запущен (
docker psпоказывает STATUS: Up) - Приложение не отвечает
- CPU/Memory в норме
Диагностика:
# Проверить процессы внутри
docker exec web ps aux
# Проверить, слушает ли приложение на порту
docker exec web netstat -tulpn
# Проверить логи (может быть ошибка без crash)
docker logs web --tail 100
# Strace (если доступен)
docker exec web strace -p 1Решение:
# Перезапустить контейнер
docker compose restart web
# Если не помогает — пересоздать
docker compose up -d --force-recreate web
# Посмотреть внутрь (debugging)
docker exec -it web bashПроблема 11: Высокое потребление ресурсов
Симптомы:
- CPU 100% или Memory 90%+
- Exit code 137 (OOM killed)
Диагностика:
# Real-time мониторинг
docker stats web
# Лимиты контейнера
docker inspect web | jq '.[].HostConfig.Memory'
docker inspect web | jq '.[].HostConfig.NanoCpus'Решение:
services:
web:
deploy:
resources:
limits:
cpus: "2.0" # Максимум 2 CPU
memory: 2G # Максимум 2GB RAM
reservations:
cpus: "0.5" # Минимум 0.5 CPU
memory: 512M # Минимум 512MB RAMMemory leak detection:
# Мониторить memory во времени
watch -n 1 'docker stats web --no-stream'
# Профилировать приложение (Python example)
docker exec web python -m memory_profiler app.pyПроблема 12: Логи заполняют диск
Симптомы:
df -h # Диск заполнен
du -sh /var/lib/docker/containers/*/*-json.log # Огромные лог-файлы
Решение:
services:
web:
logging:
driver: "json-file"
options:
max-size: "10m" # Максимум 10MB на файл
max-file: "3" # Максимум 3 файла
# Итого: максимум 30MB логов на контейнерОчистка существующих логов:
# Найти большие лог-файлы
find /var/lib/docker/containers/ -name "*-json.log" -size +100M
# Очистить логи всех остановленных контейнеров
docker container prune
# Очистить логи конкретного контейнера
truncate -s 0 $(docker inspect --format='{{.LogPath}}' web)Debug команды и техники
# ========================================
# docker inspect (продвинутое использование)
# ========================================
# Получить IP адрес
docker inspect -f '{{range.NetworkSettings.Networks}}{{.IPAddress}}{{end}}' web
# Получить environment variables
docker inspect -f '{{.Config.Env}}' web
# Health check status
docker inspect -f '{{.State.Health.Status}}' web
# Все mounts
docker inspect -f '{{json .Mounts}}' web | jq
# Exit code последнего запуска
docker inspect -f '{{.State.ExitCode}}' web
# ========================================
# docker stats
# ========================================
# Real-time мониторинг
docker stats
# Без stream (один snapshot)
docker stats --no-stream web
# Кастомный формат
docker stats --format "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}"
# ========================================
# docker events
# ========================================
# Все события real-time
docker events
# Фильтр по типу
docker events --filter type=container
# Фильтр по контейнеру
docker events --filter container=web
# ========================================
# docker logs (продвинутые опции)
# ========================================
# Последние N строк
docker logs --tail 100 web
# Логи за период
docker logs --since 2024-01-01 web
docker logs --since 10m web
# С timestamps
docker logs --timestamps web
# Follow + grep
docker logs -f web | grep ERRORFAQ: Быстрые ответы
Q: Как полностью очистить Docker?
docker system prune -a --volumes
# ⚠️ Удалит ВСЁ: контейнеры, образы, volumes!Q: Как войти в контейнер от root?
docker exec -u root -it web bashQ: Как скопировать файлы из/в контейнер?
docker cp web:/app/logs/error.log ./
docker cp ./config.json web:/app/Q: Как узнать, почему контейнер упал?
docker logs web
docker inspect web | jq '.State'Q: Как проверить, что volume содержит данные?
docker volume inspect my-volume
docker run --rm -v my-volume:/data alpine ls -la /dataQ: Как ограничить размер логов?
logging:
options:
max-size: "10m"
max-file: "3"Q: Как узнать размер слоев образа?
docker history my-app:latest
# Или используйте dive:
dive my-app:latestДополнительные инструменты
# ctop — top для контейнеров (TUI)
docker run --rm -it \
-v /var/run/docker.sock:/var/run/docker.sock \
quay.io/vektorlab/ctop:latest
# dive — анализ слоев образов
brew install dive
dive my-app:latest
# lazydocker — TUI для управления Docker
brew install lazydocker
lazydocker
# docker-slim — минимизация образов
docker-slim build my-app:latestПрактические примеры
Пример 1: Django + PostgreSQL + Redis
# docker-compose.yml
services:
web:
build: .
command: python manage.py runserver 0.0.0.0:8000
volumes:
- .:/code
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgres://postgres:postgres@db:5432/django_db
- REDIS_URL=redis://redis:6379/0
depends_on:
- db
- redis
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: django_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:# Dockerfile
FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1
WORKDIR /code
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000Команды:
# Первый запуск
docker compose up -d
docker compose exec web python manage.py migrate
docker compose exec web python manage.py createsuperuser
# Разработка
docker compose logs -f web
# Остановка
docker compose downПример 2: Next.js + Node.js API + MongoDB
services:
frontend:
build: ./frontend
ports:
- "3000:3000"
volumes:
- ./frontend:/app
- /app/node_modules
environment:
- NEXT_PUBLIC_API_URL=http://localhost:4000
depends_on:
- backend
backend:
build: ./backend
ports:
- "4000:4000"
volumes:
- ./backend:/app
- /app/node_modules
environment:
- MONGODB_URI=mongodb://mongo:27017/myapp
- PORT=4000
depends_on:
- mongo
mongo:
image: mongo:7
ports:
- "27017:27017"
volumes:
- mongo_data:/data/db
environment:
- MONGO_INITDB_DATABASE=myapp
volumes:
mongo_data:Практические упражнения
Лучший способ изучить Docker — делать. Эти упражнения покрывают все ключевые концепции из материала. Решайте последовательно, проверяя каждый критерий.
Упражнение 1: Первый production-ready Dockerfile (Уровень: ⭐)
Задача: Создайте Dockerfile для простого Python скрипта, который выполняется раз в минуту и записывает текущее время в лог.
Требования:
- Базовый образ:
python:3.11-slim - Размер итогового образа < 200MB
- Непривилегированный пользователь (не root)
- Установлены только необходимые пакеты
- Правильный
.dockerignore
Критерии проверки:
# 1. Собрать образ
docker build -t my-cron-app .
# 2. Проверить размер
docker images my-cron-app
# Должен быть < 200MB
# 3. Проверить, что пользователь не root
docker inspect my-cron-app | jq '.[].Config.User'
# Должен быть "appuser" или "1000"
# 4. Запустить и проверить логи
docker run --name test-cron my-cron-app
docker logs test-cron
# Должен писать время каждую секунду
# 5. Очистка
docker rm -f test-cron💡 Подсказка
- Используйте
python:3.11-slim, неpython:3.11(slim на 500MB меньше) - Создайте пользователя с
useradd -m -u 1000 appuser - Не забудьте
USER appuserпередCMD - В
.dockerignoreдобавьте.git,__pycache__,*.pyc
✅ Решение
app.py:
import time
from datetime import datetime
while True:
print(f"[{datetime.now()}] Cron job running...")
time.sleep(60)Dockerfile:
FROM python:3.11-slim
WORKDIR /app
# Копируем только необходимые файлы
COPY app.py .
# Создаем непривилегированного пользователя
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
CMD ["python", "-u", "app.py"].dockerignore:
.git
.gitignore
__pycache__
*.pyc
*.pyo
.vscode
.idea
README.md
Объяснение:
python:3.11-slim— 150MB вместо 1GB-uфлаг в python — unbuffered output (логи видны сразу)useradd -m— создает home directorychown— даем права appuser на /app.dockerignore— ускоряет сборку и уменьшает образ
Упражнение 2: Docker Compose multi-container приложение (Уровень: ⭐⭐)
Задача: Создайте Flask API с PostgreSQL и Redis. API должен иметь 3 эндпоинта:
GET /health— health checkGET /visits— счетчик посещений (Redis)GET /users— список из БД (PostgreSQL)
Требования:
- Health checks для всех сервисов
depends_onс условиями (не просто- db)- Приложение не стартует, пока БД не готова
- Volumes для постоянства данных
- Один network для всех сервисов
Критерии проверки:
# 1. Запустить
docker compose up -d
# 2. Проверить health checks
docker compose ps
# Все сервисы должны быть healthy
# 3. Тестировать эндпоинты
curl http://localhost:5000/health
# {"status": "healthy"}
curl http://localhost:5000/visits
# {"visits": 1}
curl http://localhost:5000/visits
# {"visits": 2}
curl http://localhost:5000/users
# [{"id": 1, "name": "Alice"}, ...]
# 4. Проверить persist данных
docker compose down
docker compose up -d
curl http://localhost:5000/visits
# {"visits": 3} ← Счетчик сохранился!
# 5. Очистка
docker compose down -v💡 Подсказка
- Используйте
depends_on: db: condition: service_healthy - Health check для PostgreSQL:
pg_isready -U postgres - Health check для Redis:
redis-cli ping - Redis для счетчика:
INCR visits - PostgreSQL: создайте таблицу в
init.sql
✅ Решение
app.py:
from flask import Flask, jsonify
import psycopg2
import redis
import os
app = Flask(__name__)
# Redis connection
r = redis.from_url(os.getenv('REDIS_URL', 'redis://redis:6379/0'))
# PostgreSQL connection
def get_db():
return psycopg2.connect(os.getenv('DATABASE_URL'))
@app.route('/health')
def health():
try:
# Check Redis
r.ping()
# Check PostgreSQL
conn = get_db()
conn.close()
return jsonify({"status": "healthy"})
except:
return jsonify({"status": "unhealthy"}), 500
@app.route('/visits')
def visits():
count = r.incr('visits')
return jsonify({"visits": count})
@app.route('/users')
def users():
conn = get_db()
cur = conn.cursor()
cur.execute('SELECT id, name FROM users')
users = [{"id": row[0], "name": row[1]} for row in cur.fetchall()]
conn.close()
return jsonify(users)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)docker-compose.yml:
services:
web:
build: .
ports:
- "5000:5000"
environment:
- DATABASE_URL=postgresql://postgres:postgres@db:5432/app_db
- REDIS_URL=redis://redis:6379/0
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
networks:
- app-network
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: app_db
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- postgres_data:/var/lib/postgresql/data
- ./init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 3s
retries: 5
networks:
- app-network
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
command: redis-server --appendonly yes
networks:
- app-network
volumes:
postgres_data:
redis_data:
networks:
app-network:
driver: bridgeinit.sql:
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL
);
INSERT INTO users (name) VALUES ('Alice'), ('Bob'), ('Charlie');Объяснение:
condition: service_healthy— web ждет готовности БД- Health checks — гарантируют готовность сервисов
- Named volumes — данные переживают
docker compose down - Single network — все сервисы видят друг друга
Упражнение 3: Multi-stage Build (Уровень: ⭐⭐)
Задача: Оптимизируйте Node.js приложение с 800MB до < 150MB используя multi-stage build.
Исходный (плохой) Dockerfile:
FROM node:20
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]Требования:
- Итоговый образ < 150MB
- Только production зависимости
- Alpine Linux в production stage
- Непривилегированный пользователь
Критерии проверки:
# Собрать плохой вариант
docker build -t node-bad -f Dockerfile.bad .
docker images node-bad
# ~800MB 💀
# Собрать оптимизированный
docker build -t node-optimized .
docker images node-optimized
# ~120-140MB ✅
# Проверить, что работает
docker run -d -p 3000:3000 --name test-node node-optimized
curl http://localhost:3000
# Должен отвечать
docker rm -f test-node✅ Решение
# ========================================
# Stage 1: Builder
# ========================================
FROM node:20 AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
# ========================================
# Stage 2: Production
# ========================================
FROM node:20-alpine AS production
WORKDIR /app
# Копируем только node_modules и код
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app .
# Создаем пользователя
RUN addgroup -g 1000 appuser && \
adduser -D -u 1000 -G appuser appuser && \
chown -R appuser:appuser /app
USER appuser
EXPOSE 3000
ENV NODE_ENV=production
CMD ["node", "server.js"]Результат:
- Builder stage: ~800MB (выбрасывается!)
- Production stage: ~120MB ✅
- Экономия: ~680MB (85% меньше)
Почему это работает:
node:20(builder) — содержит build toolsnode:20-alpine(production) — только runtimenpm ci --only=production— без devDependenciesCOPY --from=builder— только необходимые файлы
Упражнение 4: Production Security Checklist (Уровень: ⭐⭐⭐)
Задача: Подготовьте Docker приложение к production деплою, следуя security best practices.
Исходный (небезопасный) docker-compose.yml:
services:
app:
build: .
ports:
- "8000:8000"
environment:
- SECRET_KEY=hardcoded-secret-12345
- DATABASE_PASSWORD=admin123Требования:
- Сканирование образа (Trivy)
- Секреты через Docker Secrets или .env (не hardcoded)
- Непривилегированный пользователь
- Health checks
- Resource limits
- Read-only filesystem (где возможно)
- Минимальные capabilities
Критерии проверки:
# 1. Сканировать образ
trivy image my-app:latest --severity HIGH,CRITICAL
# 0 критических уязвимостей
# 2. Проверить секреты
docker history my-app:latest | grep -i secret
docker history my-app:latest | grep -i password
# Ничего не найдено ✅
# 3. Проверить пользователя
docker inspect my-app:latest | jq '.[].Config.User'
# != "root" ✅
# 4. Проверить health check
docker compose ps
# STATUS: healthy ✅
# 5. Проверить resource limits
docker inspect app | jq '.[].HostConfig.Memory'
# Должен быть установлен лимит
# 6. Проверить capabilities
docker inspect app | jq '.[].HostConfig.CapDrop'
# ["ALL"] ✅✅ Решение
.env: (НЕ коммитить в Git!)
SECRET_KEY=generate-random-secret-key-here
DATABASE_PASSWORD=strong-password-here
.gitignore:
.env
*.secret
.env.example: (коммитить)
SECRET_KEY=your-secret-key-here
DATABASE_PASSWORD=your-db-password-here
docker-compose.yml:
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "8000:8000"
env_file:
- .env
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
deploy:
resources:
limits:
cpus: "2.0"
memory: 2G
reservations:
cpus: "0.5"
memory: 512M
cap_drop:
- ALL
cap_add:
- NET_BIND_SERVICE
security_opt:
- no-new-privileges:true
read_only: true
tmpfs:
- /tmp:size=100MDockerfile:
FROM python:3.11-slim@sha256:abc123... # Exact digest
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN useradd -m -u 1000 appuser && \
chown -R appuser:appuser /app
USER appuser
HEALTHCHECK --interval=30s --timeout=3s \
CMD curl -f http://localhost:8000/health || exit 1
CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:8000"]Чек-лист выполнен:
- ✅ Секреты в .env (не в коде)
- ✅ .env в .gitignore
- ✅ Unprivileged user
- ✅ Health checks
- ✅ Resource limits
- ✅ Read-only filesystem
- ✅ Minimal capabilities
- ✅ Exact image digest
- ✅ Security scan (Trivy)
Следующие шаги: Попробуйте объединить все упражнения в одно комплексное приложение. Создайте микросервисную архитектуру с несколькими сервисами, соблюдая все security best practices и используя multi-stage builds.
Заключение
Docker — это не просто инструмент, это новый способ мышления о деплое и окружении. Один раз настроив Docker, вы сэкономите сотни часов на «у меня не работает».
Ключевые выводы
- Docker решает проблему окружения — «works on my machine» больше не оправдание
- Dockerfile = рецепт образа — каждая инструкция создает слой
- Docker Compose = оркестрация — управление несколькими контейнерами через YAML
- Volumes сохраняют данные — контейнеры эфемерны, volumes постоянны
- Один контейнер = один процесс — не запускайте всё в одном контейнере
- Разработка ≠ Production — используйте разные конфигурации
- Оптимизация важна — правильный порядок инструкций ускоряет сборку в разы
Следующие шаги
- Контейнеризируйте существующий проект
- Изучите Docker Swarm или Kubernetes для оркестрации в продакшн
- Настройте CI/CD с автоматической сборкой образов
- Изучите мониторинг контейнеров (Prometheus, Grafana)
- Погрузитесь в безопасность Docker
Полезные ресурсы
Официальная документация:
Практика:
- Play with Docker — бесплатная песочница
- Docker Hub — репозиторий образов
Инструменты:
- Dive — анализ слоев Docker образов
- Hadolint — линтер для Dockerfile
- Docker Slim — минимизация образов
Помните: Docker — это инструмент. Он не сделает плохую архитектуру хорошей, но сделает хорошую архитектуру проще в деплое и масштабировании. Начните с малого, экспериментируйте и постепенно внедряйте в свой рабочий процесс.