Сети, тома и данные контейнера
Два вопроса, на которых спотыкаются кандидаты, знающие 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.»