Сети, тома и данные контейнера

Два вопроса, на которых спотыкаются кандидаты, знающие Docker только по docker run из README.

Вопрос: «Контейнер с PostgreSQL перезапустили — база пуста. Почему и как правильно?»

Что на самом деле проверяет интервьюер

Проверяют, усвоил ли кандидат принцип эфемерности контейнера. Контейнер обязан быть одноразовым: его можно убить и поднять заново в любой момент, и ничего ценного при этом потеряться не должно. Всё состояние выносится наружу — в volume, в базу, в объектное хранилище. Второй слой вопроса — знает ли кандидат разницу между volume и bind mount и почему в проде обычно предпочитают первое.

Развёрнутый ответ: где живут данные

Записи внутри контейнера попадают в writable-слой, который создаётся при docker run и удаляется при docker rm. Плюс к этому запись через overlay-драйвер медленнее прямой записи на диск: при первом изменении файл целиком копируется из нижнего слоя (copy-up), что особенно больно для СУБД.

Есть три способа вынести данные наружу:

ТипГде хранитсяКогда использовать
named volumeуправляется Docker (/var/lib/docker/volumes/...)прод: данные СУБД, загрузки пользователей
bind mountконкретный путь на хостеразработка: живой код, конфиги, сертификаты
tmpfsоперативная памятьсекреты и временные файлы, которые не должны попасть на диск
# named volume: Docker сам управляет местом хранения
docker volume create pgdata
docker run -d --name db \
  -e POSTGRES_PASSWORD=secret \
  -v pgdata:/var/lib/postgresql/data \
  postgres:16

# bind mount: конкретный путь хоста, удобно в разработке
docker run -d -v /home/dev/app:/app node:20

# read-only монтирование конфига
docker run -d -v /etc/myapp/config.yml:/app/config.yml:ro myapp:1.2

Почему в проде обычно named volume: он не зависит от структуры каталогов хоста, переживает пересоздание контейнера, умеет драйверы (NFS, облачные диски) и не приносит проблем с правами и SELinux-метками, которыми славится bind mount. Bind mount же незаменим в разработке, когда нужно, чтобы правка файла на ноутбуке мгновенно была видна внутри контейнера.

Отдельно стоит сказать вслух: volume не является резервной копией. docker volume rm или docker compose down -v сотрут данные так же надёжно, как rm -rf. Бэкап делается отдельно — pg_dump, снапшот тома, репликация.

Вопрос 2: как контейнеры находят друг друга

Типовая формулировка: «В compose два сервиса, app и db. Приложение подключается к localhost:5432 и получает connection refused. Что не так?»

Ответ: у каждого контейнера свой сетевой namespace, поэтому localhost внутри контейнера — это он сам, а не соседний контейнер и не хост. В пользовательской bridge-сети Docker поднимает встроенный DNS, который резолвит имя сервиса в IP контейнера. Правильный адрес — db:5432.

services:
  app:
    image: myapp:1.2
    environment:
      DATABASE_URL: postgres://user:pass@db:5432/app   # имя сервиса, не localhost
    depends_on:
      db:
        condition: service_healthy
    ports:
      - "8080:8080"

  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: pass
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      retries: 5

volumes:
  pgdata:

Типы сетей, которые нужно знать

  • bridge — по умолчанию. Виртуальный коммутатор на хосте, у каждого контейнера свой IP. В пользовательской bridge-сети работает DNS по именам; в старой сети default bridge — нет, там нужны были ссылки --link.
  • host — контейнер использует сетевой стек хоста напрямую: нет NAT и проброса портов, минимальная задержка, но и никакой сетевой изоляции, а порты конфликтуют с хостовыми.
  • none — сети нет вовсе, только loopback. Для задач, которым сеть не нужна.
  • overlay — сеть поверх нескольких хостов, основа Swarm; в Kubernetes ту же задачу решает CNI-плагин.

Про проброс портов тоже спрашивают: в записи -p 8080:80 первое число — порт хоста, второе — порт внутри контейнера. А EXPOSE в Dockerfile ничего не публикует: это лишь документация плюс подсказка для docker run -P.

docker network create app-net
docker run -d --name db --network app-net postgres:16
docker run -it --rm --network app-net alpine ping -c1 db   # резолвится по имени

docker network inspect app-net       # кто в сети и с какими IP
docker port app                      # какие порты реально опубликованы

Типичные ошибки кандидатов

  • Хранят данные СУБД в writable-слое контейнера и теряют их при первом же обновлении образа.
  • Обращаются к соседнему контейнеру по localhost, забывая про отдельный сетевой namespace.
  • Считают EXPOSE публикацией порта наружу.
  • Путают порядок в -p host:container и потом долго ищут, почему сервис недоступен.
  • Уверены, что volume это бэкап.
  • Ставят network_mode: host, чтобы «просто заработало», не понимая, что теряют изоляцию и переносимость.

Как ответить кратко (20–30 секунд)

«Всё, что контейнер пишет внутрь себя, лежит в writable-слое и умирает вместе с контейнером — поэтому данные PostgreSQL монтируют named volume в /var/lib/postgresql/data. Volume управляется Docker, переживает пересоздание контейнера и подходит для прода; bind mount привязан к пути на хосте и удобнее в разработке. При этом volume — не бэкап, его сносит docker compose down -v. Между собой контейнеры общаются не через localhost, а по имени сервиса: в пользовательской bridge-сети работает встроенный DNS Docker.»

Проверьте себя
1. В docker compose приложение обращается к базе по адресу localhost:5432 и получает connection refused. В чём причина?
AПорт 5432 не проброшен на хост через ports
BУ каждого контейнера свой сетевой namespace: localhost — это сам контейнер, обращаться нужно по имени сервиса (db:5432)
CPostgreSQL не поддерживает подключения из контейнеров
DНужно указать network_mode: host для обоих сервисов
2. Чем named volume предпочтительнее bind mount для данных СУБД в продакшене?
AVolume автоматически создаёт резервные копии
BVolume управляется Docker, не зависит от структуры каталогов хоста, переживает пересоздание контейнера и поддерживает драйверы хранилищ
CVolume работает быстрее, потому что хранится в оперативной памяти
DBind mount нельзя использовать с образами PostgreSQL
3. Что делает инструкция EXPOSE 8080 в Dockerfile?
AПубликует порт 8080 на хосте
BОткрывает порт в firewall хоста
CТолько документирует порт и служит подсказкой для docker run -P; сама по себе ничего не публикует
DПеренаправляет весь трафик контейнера на порт 8080