Урок 04

Volumes и сети

Почему volumes

Проблема: данные внутри контейнера пропадают при удалении.

docker run -d --name pg -e POSTGRES_PASSWORD=secret postgres:16
# Создал БД, наполнил данными
docker rm -f pg
# ВСЁ ПРОПАЛО

Решение: volumes — хранилища вне контейнера, переживают удаление.

Три типа хранилищ

1. Anonymous volume (анонимный)

Создаётся автоматически. Docker генерирует имя.

docker run -v /var/lib/postgresql/data postgres

Плюс: просто. Минус: непонятное имя, сложно искать.

2. Named volume (именованный)

Явно указываешь имя. Docker хранит в /var/lib/docker/volumes/.

docker run -v pg_data:/var/lib/postgresql/data postgres

Плюс: понятно, переиспользуется. Для: БД, кеши.

3. Bind mount (привязка к хосту)

Монтируешь папку с хоста.

docker run -v /home/user/code:/var/www/html nginx

Плюс: видно с хоста, удобно для разработки. Минус: проблема с правами.

Для: кода во время разработки.

Управление volumes

docker volume ls
docker volume create pg_data
docker volume inspect pg_data
docker volume rm pg_data
docker volume prune              # удалить неиспользуемые

Где лежат:

ls /var/lib/docker/volumes/

Практика: PostgreSQL с volume

1. Запусти с volume:

docker run -d --name pg \
    -e POSTGRES_PASSWORD=secret \
    -e POSTGRES_DB=skills_db \
    -p 5432:5432 \
    -v pg_data:/var/lib/postgresql/data \
    postgres:16-alpine

2. Создай данные:

docker exec -it pg psql -U postgres -d skills_db
CREATE TABLE test (id serial, name text);
INSERT INTO test (name) VALUES ('hello');
SELECT * FROM test;
\q

3. Удали контейнер:

docker rm -f pg

4. Запусти заново с тем же volume:

docker run -d --name pg \
    -e POSTGRES_PASSWORD=secret \
    -e POSTGRES_DB=skills_db \
    -p 5432:5432 \
    -v pg_data:/var/lib/postgresql/data \
    postgres:16-alpine

5. Проверь данные:

docker exec -it pg psql -U postgres -d skills_db -c "SELECT * FROM test;"
# Данные на месте!

Volume спасает данные.

Практика: bind mount

Создай HTML:

mkdir -p /tmp/site
echo '<h1>Hello from bind mount</h1>' > /tmp/site/index.html

Запусти Nginx:

docker run -d --name web \
    -p 8080:80 \
    -v /tmp/site:/usr/share/nginx/html \
    nginx

Проверь:

curl http://localhost:8080
# <h1>Hello from bind mount</h1>

Измени файл на хосте:

echo '<h1>Updated!</h1>' > /tmp/site/index.html
curl http://localhost:8080
# <h1>Updated!</h1>

Изменения сразу видны.

Read-only mount

Монтировать только для чтения:

docker run -v /tmp/site:/usr/share/nginx/html:ro nginx

ro — контейнер не может писать.

Для: конфигов, сертификатов.

Проблема с правами

Симптом: файлы, созданные контейнером, принадлежат root на хосте.

Проверка:

docker exec -it my-app touch /var/www/test.txt
ls -la /var/www/test.txt
# -rw-r--r-- 1 root root

Решение: указать UID/GID пользователя.

docker run -u $(id -u):$(id -g) -v $PWD:/var/www my-app

Или создавать пользователя в Dockerfile с нужным UID.

Сети

Зачем

Проблема: два контейнера не знают друг друга по имени.

Решение: создать сеть и подключить контейнеры.

Виды сетей

1. bridge (по умолчанию) — для контейнеров на одном хосте.

2. host — контейнер использует сеть хоста напрямую.

3. none — без сети.

4. overlay — для кластера (Swarm, K8s).

5. macvlan — как отдельное устройство в сети.

Управление сетями

docker network ls
docker network create my-network
docker network inspect my-network
docker network rm my-network
docker network prune

Практика: два контейнера

1. Создай сеть:

docker network create skills-net

2. Запусти PostgreSQL:

docker run -d --name pg \
    --network skills-net \
    -e POSTGRES_PASSWORD=secret \
    -e POSTGRES_DB=skills_db \
    postgres:16-alpine

3. Запусти приложение:

docker run -d --name app \
    --network skills-net \
    -e DB_HOST=pg \
    -e DB_DATABASE=skills_db \
    -e DB_USERNAME=postgres \
    -e DB_PASSWORD=secret \
    my-laravel-app

4. Проверь связь:

docker exec -it app ping pg
# Работает

docker exec -it app nc -zv pg 5432
# Connection to pg 5432 port [tcp/postgresql] succeeded!

Docker DNS резолвит имена контейнеров в IP.

5. Работает в приложении:

В Laravel .env:

DB_HOST=pg
DB_DATABASE=skills_db
DB_USERNAME=postgres
DB_PASSWORD=secret

Порт пробрасывать наружу не нужно — контейнеры в одной сети видят друг друга.

Ports — что публиковать

Только то, что нужно извне:

  • Веб-сервер — да, -p 80:80, -p 443:443
  • Приложение (Laravel/Node) — да, -p 8000:8000 (для отладки)
  • БД — нет, если приложение в той же сети
  • Redis — нет, если приложение в той же сети

Правило: наружу — только порт Nginx (или LB).

Проброс хоста

Если контейнер слушает только 127.0.0.1 внутри — проброс не сработает извне.

Причина: контейнер слушает loopback, не 0.0.0.0.

Пример — приложение:

# Плохо — слушает только 127.0.0.1 внутри
my-app --host=127.0.0.1

# Хорошо — слушает все интерфейсы
my-app --host=0.0.0.0

Для Next.js:

npx next start -H 0.0.0.0 -p 3000

Просмотр сети контейнера

docker inspect my-app | grep IPAddress
docker exec -it my-app cat /etc/hosts
docker network inspect skills-net

Итог

Volumes:

  • Named — для данных БД, кеша
  • Bind mount — для кода при разработке
  • Read-only — для конфигов

Сети:

  • Контейнеры в одной сети видят друг друга по имени
  • Наружу публикуй только нужные порты
  • Приложения должны слушать 0.0.0.0, не 127.0.0.1

Дальше — Dockerfile основы.