Docker для production-веб-приложений
Docker позволяет надежно запускать веб-приложения в production, но одной команды docker compose up -d недостаточно, чтобы считать инфраструктуру production-ready. Сложность заключается не в запуске контейнеров, а в том, чтобы их можно было предсказуемо пересобирать, чтобы они переживали сбои, не получали лишних привилегий, сохраняли важные данные, не превышали выделенные ресурсы, создавали пригодные для анализа логи и безопасно заменялись при деплое.
Для небольшого или среднего веб-приложения на одном сервере Docker Compose может быть вполне разумной production-моделью. Docker официально описывает использование Compose для production-развертывания на одном хосте. Однако при переходе к инфраструктуре из нескольких машин планирование контейнеров, failover, service discovery, rolling deployment и оркестрация становятся отдельными архитектурными задачами.
Краткий ответ
Docker подходит для production, если контейнеры рассматриваются как заменяемые runtime-компоненты, а не как миниатюрные серверы. Используйте immutable-образы, запускайте приложения с минимально необходимыми привилегиями, храните состояние вне файловой системы контейнера, настраивайте health checks и restart policies, ограничивайте CPU и память, ротируйте или экспортируйте логи, защищайте секреты, открывайте только необходимые порты и поддерживайте проверенные процедуры резервного копирования и деплоя.
Docker предоставляет большую часть необходимых механизмов. Надежность в production зависит от того, насколько правильно они настроены.
Основы production-конфигурации
| Область | Требование для production |
|---|---|
| Образы | Воспроизводимые, минимальные и протестированные |
| Приложение | Stateless там, где это практично |
| Безопасность | Non-root, принцип наименьших привилегий, контролируемый доступ к Docker |
| Ресурсы | Ограничения CPU и памяти |
| Сеть | Открыты только необходимые порты |
| Состояние | Persistent volumes или внешние managed-сервисы |
| Надежность | Health checks и restart policies |
| Логи | Ротация или внешняя агрегация |
| Секреты | Хранятся вне слоев образа |
| Деплой | Повторяемый процесс build, deploy и rollback |
Создавайте production-образы правильно
Production-образ должен содержать то, что необходимо приложению для запуска, а не все инструменты, которые использовались при его сборке.
Docker рекомендует multi-stage builds, чтобы отделять компиляторы, development-зависимости и build-инструменты от финального runtime-образа. Более компактные runtime-образы обычно содержат меньше лишних зависимостей и уменьшают доступную поверхность атаки. Docker также рекомендует использовать доверенные минимальные base images и периодически пересобирать образы, чтобы в них попадали обновленные зависимости. (docs.docker.com)
Типичный подход выглядит так:
# syntax=docker/dockerfile:1
FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:24-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY package*.json ./
RUN npm ci --omit=dev && npm cache clean --force
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s \
--timeout=3s \
--start-period=10s \
--retries=3 \
CMD wget -qO- http://127.0.0.1:3000/health || exit 1
CMD ["node", "dist/server.js"]
Это иллюстративный пример для Node.js, а не универсальный Dockerfile. Важен сам подход: разделение build- и runtime-этапов, установка только production-зависимостей, запуск от non-root пользователя и наличие application-level health endpoint.
Docker рекомендует использовать USER, если сервису не требуются root-привилегии. (docs.docker.com)
Закрепление образов
Теги образов изменяемы. Например:
FROM alpine:3.21
после выпуска обновления издателем может указывать уже на другой образ.
Docker поддерживает закрепление образа по digest:
FROM alpine:3.21@sha256:...
Это повышает воспроизводимость, поскольку один и тот же digest идентифицирует одно и то же содержимое образа. Но здесь есть важный компромисс: жесткое закрепление по digest также означает, что обновления не будут поступать автоматически. Разумный production-процесс сочетает контролируемое закрепление с автоматической проверкой обновлений зависимостей, а не оставляет старые образы зафиксированными навсегда. (docs.docker.com)
INTERNAL LINK: How Docker Images and Layers Work
Не храните секреты в образе
Пароли, приватные токены и учетные данные нельзя встраивать в Docker image.
Docker отдельно предупреждает, что Dockerfile ARG и ENV не следует использовать для build-секретов, поскольку такие значения могут сохраниться в итоговом образе или его metadata. Secret mounts в BuildKit позволяют временно предоставить credentials во время сборки, не сохраняя их в финальном образе. (docs.docker.com)
Docker Compose также поддерживает runtime secrets. В Linux-контейнерах они могут монтироваться как файлы в /run/secrets/ и предоставляться только тем сервисам, которым действительно нужны. (docs.docker.com)
Основной принцип прост: конфигурация может меняться между окружениями, но application image не должен содержать credentials этих окружений.
Ограничивайте привилегии контейнеров
Контейнеры обеспечивают изоляцию, но это не повод игнорировать безопасность хоста.
Особого внимания требует доступ к Docker daemon. Документация Docker предупреждает, что пользователи, способные управлять обычным rootful Docker daemon, фактически могут получить возможности уровня root на хосте. Поэтому членство в группе docker следует считать привилегированным доступом. (docs.docker.com)
Для веб-приложений:
- запускайте приложение от non-root пользователя, когда это возможно;
- избегайте
privileged: true; - удаляйте capabilities, которые приложению не нужны;
- сохраняйте стандартную seccomp-защиту Docker, если нет конкретной и понятной причины ее менять;
- рассмотрите
no-new-privileges; - рассмотрите Rootless Docker, если его эксплуатационные ограничения приемлемы.
Rootless mode в Docker позволяет запускать и daemon, и контейнеры без root-привилегий, снижая потенциальный ущерб от некоторых уязвимостей daemon или runtime. (docs.docker.com)
Docker также рекомендует удалять ненужные Linux capabilities вместо того, чтобы предоставлять широкие привилегии. (docs.docker.com)
Ограничивайте CPU и память
По умолчанию контейнер не имеет ограничений ресурсов. Если лимиты не настроены, он может использовать столько CPU или памяти, сколько позволяет хост. Поэтому один некорректно работающий контейнер способен повлиять на остальные контейнеры на той же машине. (docs.docker.com)
Compose поддерживает такие ограничения:
services:
app:
image: registry.example.com/app:1.4.0
cpus: 1.0
mem_limit: 512m
Эти значения не следует копировать без анализа. Сначала измерьте обычную нагрузку, ожидаемый трафик, поведение приложения по памяти и условия отказа. PHP-FPM, JVM-приложение, Node.js-процесс и PostgreSQL могут требовать совершенно разных лимитов.
Цель ограничений — обеспечить изоляцию и предсказуемое поведение при сбоях, а не просто заставить каждый контейнер использовать меньше памяти.
Сохраняйте важные данные отдельно
Записываемая файловая система контейнера является временной. Когда контейнер удаляется, данные, находившиеся только в его writable layer, исчезают. (docs.docker.com)
Persistent-данные приложения следует хранить в Docker volumes, подходящих bind mounts, object storage или внешних database/storage-сервисах.
Docker описывает volumes как предпочтительный механизм хранения persistent-данных контейнеров во многих сценариях. Volumes существуют независимо от конкретного контейнера и могут резервироваться, восстанавливаться и переноситься. (docs.docker.com)
Это создает важное архитектурное разделение:
Container = заменяемый runtime приложения
Volume или внешний сервис = persistent state
Сам по себе volume не является резервной копией. Production-системам по-прежнему необходимы регулярные backups и, что еще важнее, протестированные процедуры восстановления.
INTERNAL LINK: Docker Volumes Explained
Health checks и восстановление
Процесс может продолжать работать, даже если само приложение уже недоступно.
Инструкция Docker HEALTHCHECK позволяет образу определить команду, которая проверяет, действительно ли приложение функционирует. После этого Docker отображает состояния starting, healthy и unhealthy. (docs.docker.com)
Для веб-сервиса endpoint /health может проверять, отвечает ли HTTP-приложение на запросы. Более сложные readiness checks могут дополнительно проверять зависимости, однако health endpoint не должен становиться настолько сложным, чтобы временный сбой внешней системы вызывал ненужные циклы перезапуска.
Compose также умеет ждать, пока зависимость со статусом service_healthy станет доступна, прежде чем запускать другой сервис. (docs.docker.com)
Restart policies решают связанную задачу:
restart: unless-stopped
Docker документирует политики always, unless-stopped и on-failure для автоматического перезапуска контейнеров после завершения процесса или перезапуска daemon. (docs.docker.com)
Restart policy помогает восстановиться после падения процесса. Она не заменяет мониторинг и не исправляет приложение, которое постоянно аварийно завершается.
Контролируйте сетевой доступ
Публикация Docker-портов требует осознанного подхода.
Например:
docker run -p 3000:3000 myapp
по умолчанию публикует порт на всех интерфейсах хоста. Документация Docker прямо указывает, что такие опубликованные порты могут стать доступны извне. (docs.docker.com)
Если обращаться к приложению должен только reverse proxy на том же сервере, привязка к loopback-интерфейсу уменьшает ненужную внешнюю доступность:
ports:
- "127.0.0.1:3000:3000"
Базы данных, Redis и внутренние очереди обычно не должны становиться публично доступными только потому, что они работают в Docker.
INTERNAL LINK: Reverse Proxy Architecture Explained
Не игнорируйте логи
Стандартный logging driver Docker json-file по умолчанию не выполняет ротацию логов. Поэтому приложение, которое генерирует много логов, со временем может занять значительный объем диска. Docker рекомендует рассмотреть local logging driver, чтобы снизить риск заполнения диска. (docs.docker.com)
В production следует осознанно выбрать один из вариантов:
- локальные логи с ротацией;
- logging driver, отправляющий логи во внешнюю систему;
- внешняя observability-платформа.
Независимо от выбранного подхода, логи должны храниться достаточно долго для расследования сбоев, но не настолько бесконтрольно, чтобы в итоге заполнить весь диск сервера.
Практическая базовая конфигурация Compose
Более защищенная конфигурация application service может начинаться примерно так:
services:
app:
image: registry.example.com/myapp:1.4.0
restart: unless-stopped
ports:
- "127.0.0.1:3000:3000"
cpus: 1.0
mem_limit: 512m
read_only: true
tmpfs:
- /tmp
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
logging:
driver: local
Это отправная точка, а не универсальный шаблон. read_only: true может нарушить работу приложений, которым нужны записываемые директории, а удаление всех capabilities может сломать ПО, которому действительно требуется одна из них. Поэтому production-hardening должен следовать принципу наименьших привилегий, а не принципу максимальных ограничений независимо от поведения приложения. Compose напрямую поддерживает такие настройки на уровне сервисов. (docs.docker.com)
Частые ошибки в production
Наиболее распространенные проблемы Docker в production редко связаны с самими контейнерами. Обычно причина — решения при деплое: ненужный запуск от root, использование изменяемого тега latest без контролируемого обновления, хранение данных базы внутри container layer, публичная публикация внутренних портов, отсутствие memory limits, размещение credentials в образах, бесконтрольный рост логов или попытка заменить мониторинг простым перезапуском контейнера.
Еще одна распространенная ошибка — монтирование директории с исходным кодом приложения с хоста в production. В production-рекомендациях по Compose Docker отдельно предлагает убрать development-oriented bind mounts с кодом, чтобы развернутый application code оставался внутри image. (docs.docker.com)
Когда Docker Compose достаточно
Docker Compose — разумный вариант для production, если одного хоста достаточно и команда может использовать его как границу инфраструктуры.
Конфигурация из reverse proxy, application containers, Redis и, возможно, базы данных может оставаться эксплуатационно простой.
Требования меняются, когда нужны автоматическое распределение контейнеров между машинами, node-level failover, масштабный service discovery, координированные rolling deployments или интенсивное горизонтальное масштабирование. В этот момент вопрос уже не в том, работает ли Docker в production, а в том, какая orchestration-платформа должна управлять контейнерами.
INTERNAL LINK: Docker Compose vs Container Orchestration
Рекомендация SeoNest
Рассматривайте production-развертывание Docker как систему, а не как отдельный Dockerfile.
Минимальная полезная production-модель выглядит так:
source code → CI build and test → immutable image → registry → controlled deployment → health verification → monitoring → rollback
Храните persistent state вне заменяемых application containers. Ограничивайте привилегии и открытые порты. Устанавливайте resource limits на основе измерений. Защищайте credentials независимо от образов. Автоматизируйте резервное копирование и тестируйте восстановление.
Docker упрощает упаковку приложения. Production engineering делает эту упаковку надежной.
FAQ
Безопасно ли использовать Docker Compose в production?
Может быть безопасно. Docker официально документирует Compose для production и single-host deployments. Подходит ли он конкретному приложению, зависит от требований к доступности, масштабированию и эксплуатации.
Нужно ли запускать контейнеры от root?
Только если приложению действительно нужны такие привилегии. Docker рекомендует использовать USER для сервисов, способных работать без root. (docs.docker.com)
Стоит ли использовать latest в production?
Изменяемый тег усложняет точное воспроизведение деплоя. Versioned tags или закрепление по digest дают больший контроль при условии, что обновления активно поддерживаются. (docs.docker.com)
Нужно ли делать backups Docker volumes?
Да. Volumes обеспечивают сохранность данных независимо от жизненного цикла контейнера, но сами по себе не обеспечивают disaster recovery.
Делает ли restart: always приложение высокодоступным?
Нет. Эта настройка может перезапустить упавший контейнер на том же Docker host. Она не защищает от отказа самого хоста и не диагностирует причину сбоя приложения.
Итог
Docker не становится production-ready только потому, что контейнер успешно запускается. Он становится частью production-ready системы, когда образы воспроизводимы, привилегии и ресурсы контролируются, состояние защищено, сбои можно обнаруживать, логи управляются, сетевой доступ настроен осознанно, а деплой можно восстановить или откатить.
Контейнер — заменяемая часть системы. Надежность обеспечивает production-архитектура вокруг него.
Sources
- Docker — Building best practices. Docker Build best practices
- Docker — Use Compose in production. Compose in production
- Docker — Docker Engine security. Docker Engine security
- Docker — Rootless mode. Docker Rootless mode
- Docker — Resource constraints. Docker resource constraints
- Docker — Configure logging drivers. Docker logging drivers
- Docker — Volumes. Docker volumes
- Docker — Build secrets. Docker build secrets
- Docker — Dockerfile reference. Dockerfile reference
- Docker — Port publishing and mapping. Docker port publishing


