Docker часто продают как магическое средство от всех инфраструктурных проблем. На деле
это просто удобный механизм упаковки и запуска приложений с предсказуемыми зависимостями.
Он хорош там, где нужна воспроизводимость, быстрый старт окружения и управляемый деплой.
Но чтобы контейнеры действительно помогали, полезно понимать базовые сущности и ограничения.
1. Ментальная модель: image, container, volume, network
Самая частая путаница — между образом и контейнером. Образ (image) — это
шаблон файловой системы и метаданных. Контейнер — уже запущенный экземпляр этого шаблона.
Том (volume) нужен для сохранения данных вне жизненного цикла контейнера.
Сеть — это то, как контейнеры и внешний мир общаются между собой.
docker image ls
docker container ls
docker volume ls
docker network ls
Если в голове эта модель уложена, Docker перестаёт казаться магией. Вы начинаете видеть
конкретные слои: что собирается, что запускается, где лежат данные и как трафик проходит.
2. Dockerfile: лучше простой, чем «умный»
Хороший Dockerfile стремится быть воспроизводимым и понятным. Ему не нужно делать всё
на свете. Его задача — собрать нужную среду выполнения.
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
ENV PYTHONUNBUFFERED=1
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
Здесь видно несколько полезных практик: сначала копируются зависимости, чтобы кешировать
слой установки, потом код приложения. Не стоит тащить в образ весь домашний каталог,
локальные секреты или мусор из репозитория — для этого существует .dockerignore.
3. Контейнер не должен хранить важные данные внутри себя
Это одна из ключевых идей. Если база данных пишет всё в файловую систему контейнера без
volume, данные пропадут при пересоздании контейнера. Поэтому stateful-компоненты почти
всегда требуют отдельного тома или внешнего managed-хранилища.
docker run -d --name postgres -e POSTGRES_PASSWORD=secret -v pg_data:/var/lib/postgresql/data postgres:16
Контейнер можно удалить и поднять заново, а volume останется. Именно так и достигается
нормальная эксплуатационная устойчивость.
4. Сети Docker: почему контейнеры «видят» друг друга
По умолчанию Docker создаёт bridge-сеть. Если контейнеры находятся в одной пользовательской
сети, они могут обращаться друг к другу по имени сервиса.
docker network create app_net
docker run -d --name db --network app_net postgres:16
docker run -d --name api --network app_net my-api:latest
Внутри этой сети приложение может обращаться к базе по хосту db. Это удобнее,
чем шить IP-адреса в конфиги. Для продакшена важно помнить: публикация порта наружу через
-p — это не то же самое, что внутренняя доступность контейнеров между собой.
5. Compose: когда нужен набор сервисов
Для локальной разработки и небольших окружений Docker Compose закрывает типовой сценарий:
приложение + база + очередь + reverse proxy.
services:
app:
build: .
restart: unless-stopped
env_file:
- .env
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- pg_data:/var/lib/postgresql/data
volumes:
pg_data:
Главное — помнить, что depends_on не означает «сервис полностью готов».
Контейнер базы может стартовать, но ещё не принимать подключения. Для этого нужны
healthchecks, retry-логика на стороне приложения или явные wait-стратегии.
6. Ограничения ресурсов и наблюдаемость
Контейнер без ограничений может неожиданно съесть ресурсы хоста. Если рядом живут другие
сервисы, это закончится деградацией всей машины.
docker run -d --name worker --cpus="1.5" --memory="768m" my-worker:latest
Даже если ограничения не жёсткие, полезно хотя бы понимать потребление:
docker stats
docker inspect my-container
docker logs -f my-container
Логи лучше выводить в stdout/stderr и собирать их централизованно. Когда приложение пишет
в файл внутри контейнера, логирование становится менее прозрачным и хуже интегрируется
в общую систему наблюдаемости.
7. Безопасность: минимум, который нельзя игнорировать
- Не запускайте контейнеры под root без необходимости.
- Не храните секреты в образе и в Git.
- Используйте минимальные базовые образы.
- Очищайте старые и неиспользуемые образы.
- Следите за обновлениями и CVE в базовых слоях.
USER 1001:1001
Одна строка USER в Dockerfile уже делает поведение приложения безопаснее.
Это не панацея, но правильное направление по умолчанию.
8. Типовые ошибки в реальной эксплуатации
- Контейнеры используются как VM и накапливают ручные изменения внутри себя.
- Volumes не настроены, данные теряются после пересоздания.
- Порты опубликованы наружу без необходимости.
- Секреты попадают в образ или в docker-compose.yml.
- Логи не ротируются и съедают диск на хосте.
Все эти ошибки решаются не магией, а дисциплиной: инфраструктура должна быть описана,
сборка — повторяема, запуск — прозрачен, а данные — вынесены туда, где они переживут контейнер.
9. Небольшой production-чек-лист
# что запущено
docker ps
# какие volumes есть
docker volume ls
# какие сети созданы
docker network ls
# сколько места съели образы и контейнеры
docker system df
# осторожная уборка неиспользуемого
docker system prune
С prune всегда аккуратно: сначала поймите, что именно считается неиспользуемым
в вашей среде, иначе можно удалить то, что вы планировали быстро откатить.
Docker не отменяет эксплуатацию. Он просто делает её более стандартизированной, если использовать его осознанно.
Итог
Docker особенно полезен там, где нужен повторяемый запуск приложений и единый формат
доставки. Но ценность контейнеризации раскрывается только в связке с хорошими практиками:
отдельные volumes для данных, понятные сети, контроль ресурсов, безопасные образы,
наблюдаемость и документированный деплой.