Переменные окружения, volume, сети
Переменные окружения, volume, сети
Если у тебя конфиги захардкожены - Docker тебя накажет. Один раз. Потом ты исправишь.
env
DATABASE_URL=postgres://...
JWT_SECRET=super-secret
volumes
Volume нужен, чтобы данные жили дольше контейнера.
volumes:
- dbdata:/var/lib/postgresql/data
networks (обычно по умолчанию)
Compose создаёт сеть сам. Но можно явно, если нужно разделить.
Named volumes vs bind mounts
services:
db:
volumes:
- dbdata:/var/lib/postgresql/data # named volume (Docker управляет)
- ./init.sql:/docker-entrypoint-initdb.d/init.sql # bind mount (твой файл)
volumes:
dbdata: {}
- Named volume - данные живут внутри Docker, переживают
docker compose down - Bind mount - монтируешь файл/каталог с хоста. Удобно для конфигов и dev-режима
.env vs env_file vs Docker secrets
Три способа передать значения в контейнер - у каждого своя ниша:
Подход Где живёт Когда выбирать
──────────── ───────────────── ──────────────────────────────────
.env (compose) рядом с compose.yaml dev/local; compose сам подхватит
env_file указанный путь staging/prod; разные файлы под
окружения (.env.staging, .env.prod)
Docker secrets docker swarm/k8s secret прод-секреты (пароли БД, JWT_KEY)
монтируются как файл - не светятся в `docker inspect`
services:
backend:
env_file: .env.staging # переменные из файла
environment:
- LOG_LEVEL=debug # переопределяем точечно
secrets:
- db_password # монтируется в /run/secrets/db_password
secrets:
db_password:
file: ./secrets/db_pass.txt
Volumes: ловушки с правами на файлы
# Контейнер пишет от root (UID=0):
docker run -v $(pwd)/data:/app/data myapp
ls -la data/
# -rw-r--r-- 1 root root file.log ← на хосте теперь файл от root
Это классическая боль bind mount: процесс в контейнере запускается от root, и файлы на хосте получают владельца root. Локально мешает (sudo rm), в CI ломает следующие шаги.
# Решение: запускать процесс от обычного пользователя
RUN addgroup -g 1000 app && adduser -D -u 1000 -G app app
USER app
Либо в compose: user: "1000:1000". Совпадение UID хоста и контейнера = одинаковые права.
Bind mount поверх named volume = потеря данных
services:
db:
volumes:
- dbdata:/var/lib/postgresql/data
- ./fresh-init:/var/lib/postgresql/data # ← перетрёт named volume!
Второй mount по тому же пути «накроет» первый. Данные в dbdata останутся, но контейнер их не увидит. Симптом: «БД стартует пустой, хотя volume есть». Проверка - docker compose exec db ls /var/lib/postgresql/data.
Пользовательские сети
По умолчанию compose создаёт одну сеть. Но можно разделить:
services:
frontend:
networks:
- frontend-net
backend:
networks:
- frontend-net
- backend-net
db:
networks:
- backend-net
networks:
frontend-net: {}
backend-net: {}
Здесь frontend видит backend, но не видит db напрямую. Это изоляция.
Network modes: bridge / host / none / custom
Режим Изоляция Когда использовать
───── ───────── ──────────────────────────────────────────────
bridge полная дефолт; почти всегда правильный выбор
host нет высокая производительность сети (proxy, VPN,
мониторинг); порты контейнера = порты хоста
none полная контейнер без сети (изолированная обработка
данных, security sandboxing)
custom управляемая свой DNS, фиксированные подсети, multi-host
через overlay (swarm)
services:
proxy:
network_mode: host # nginx видит сетевой интерфейс хоста напрямую
sandbox:
network_mode: none # никакой сети - даже DNS нет
DNS в Docker network
Docker встроил собственный DNS на адресе 127.0.0.11. В кастомной сети сервисы резолвят друг друга по имени сервиса из compose:
// Из контейнера backend в той же сети:
db, _ := sql.Open("postgres", "host=db port=5432 user=postgres ...")
// ^^ имя сервиса из docker-compose.yml
В PHP то же самое - host берётся из имени сервиса compose. Через PDO:
<?php
declare(strict_types=1);
// Подключение к Postgres внутри той же docker network
$pdo = new PDO(
'pgsql:host=db;port=5432;dbname=app',
// ^^ имя сервиса из docker-compose.yml
'postgres',
$_SERVER['POSTGRES_PASSWORD'] ?? '',
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
],
);
В Symfony связь конфигурируется через переменную окружения DATABASE_URL - Doctrine DBAL/ORM сам распарсит host из URL:
<?php
declare(strict_types=1);
use Doctrine\DBAL\DriverManager;
// DATABASE_URL=postgresql://postgres:password@db:5432/app?serverVersion=16
$conn = DriverManager::getConnection([
'url' => $_SERVER['DATABASE_URL'],
]);
# Проверка из контейнера:
docker compose exec backend nslookup db
# Server: 127.0.0.11
# Name: db
# Address: 172.18.0.3
Это работает только внутри той же docker network. Если frontend-net и backend-net разные - frontend не зарезолвит db.
Best practices
.dockerignoreобязателен - даже если думаешь, что не нужен. Без него.git,node_modules,__pycache__улетят в образ.- Секреты в Dockerfile через
ARGоставляют след - даже multi-stage не спасает:docker historyпокажет аргумент. Для build-time секретов ---secret(BuildKit). .envвсегда в.gitignore+ храни рядом.env.exampleс пустыми значениями как документацию.- Volume для БД делается один раз и навсегда - переименование (
dbdata→pgdata) создаёт новый пустой volume. Симптом тот же: «БД пустая после рестарта». depends_on≠ «БД готова» - он ждёт только запуск контейнера, не его сервиса. Для надёжности -healthcheck+depends_on: condition: service_healthy(тема следующего урока).
Мини-задание
- Сделай
.env.exampleс перечнем переменных (POSTGRES_PASSWORD, JWT_SECRET) - Подключи env в compose через
env_file: .env - Добавь named volume для данных PostgreSQL
- Проверь, что данные сохраняются после
docker compose downиdocker compose up - Проверь, что данные НЕ сохраняются после
docker compose down -v
Итог
.env- для секретов.env_fileподключает их в compose. Никогда не коммить.env.- Named volumes хранят данные между перезапусками. Bind mounts - для разработки.
- Сети изолируют сервисы: frontend не должен иметь прямой доступ к БД.
Типичная ошибка
Хранить секреты в docker-compose.yml и коммитить его. Или забыть named volume для БД - и потерять все данные при docker compose down.
Мини-практика (10 минут)
- Создай
.envсPOSTGRES_PASSWORD=secret123. 2. Вdocker-compose.ymlподключи черезenv_file: .env. 3. Подними postgres, создай таблицу черезdocker compose exec db psql -U postgres -c "CREATE TABLE test (id serial);". 4. Выполниdocker compose downиdocker compose up -d. 5. Проверь, что таблица на месте:docker compose exec db psql -U postgres -c "\dt". 6. Выполниdocker compose down -vи повтори - таблицы нет.