ZIM-НИЙ SAAALEЗимние скидки: до −50% на старт и −20% на продление
до 31.01.2026 Подробнее
Выберите продукт

Docker secrets и Compose: безопасные переменные и ротация

Env-переменные удобны, но для паролей и ключей это риск. Покажу, как перевести чувствительные данные на Docker secrets в Compose: подключение, права, паттерн *_FILE, ротация без простоя и контроль утечек — всё на примерах и чек-листах.
Docker secrets и Compose: безопасные переменные и ротация

Если вы храните пароли БД, токены API и ключи в обычных env-переменных, вероятность утечки выше, чем кажется. В продакшене утечки чаще происходят не от «хакеров в плаще», а из‑за логов, дампов и невнимательного девелопера. У Docker и Compose есть штатные механизмы для работы с секретами, а значит — можно минимизировать поверхность атаки и упростить ротацию. Разбираем по шагам, без теории ради теории.

Почему env-переменные с секретами — плохая идея

Env-переменные исторически удобны: положил DB_PASSWORD в .env или environment: и поехали. Но для секретов это компромисс с безопасностью. Ключевые проблемы:

  • Просматриваемость процессом. Переменные окружения видны процессу и часто любой с достаточными правами может их получить через /proc/<pid>/environ или инструменты диагностики.
  • Логи и дампы. Секреты легко утекут в логи приложения, CI/CD, отладочные дампы, при панике/stacktrace.
  • Docker inspect. Значения из environment попадут в метаданные контейнера и могут быть показаны командам диагностики.
  • Файлы .env. Обычно лежат рядом с compose.yml, и их часто коммитят в репозиторий или пересылают в мессенджеры.
  • Кэш сборки. Путают ARG и ENV, затем секреты случайно попадают в слои образа при сборке.

Гораздо безопаснее подавать чувствительные данные в контейнер файлами, причём с жёсткими правами и минимальным временем «жизни» в памяти процесса. Здесь на сцену выходят secrets.

Docker secrets: Compose против Swarm

Есть два близких, но разных случая:

  • Compose (standalone). Современный docker compose поддерживает секцию secrets:. Compose монтирует секреты в контейнер как файлы (по умолчанию в /run/secrets/<name>) с правами чтения только для владельца. Источник секрета — локальный файл на хосте. Шифрования «на диске Docker» нет, поэтому защищаем сам хост и каталог с секретами.
  • Swarm. Секреты хранятся в менеджерах кластера и передаются по TLS только узлам, где они нужны. Важно: секрет нельзя обновить «на месте» — создаём новый и переобновляем сервис. Есть rolling update из коробки.

В статье фокус на Compose, как самом распространённом сценарии на одиночных серверах и маленьких кластерах. На одиночном сервере (например, на VDS) это быстрый и практичный минимум безопасности.

Secrets как файлы в /run/secrets в Docker Compose

Базовый пример: PostgreSQL и приложение на Compose

Задача: поднять PostgreSQL и приложение так, чтобы пароль не «светился» в environment, а лежал в /run/secrets. Официальные образы многих СУБД и приложений поддерживают «файловые» переменные формата NAME_FILE — это удобно.

Готовим секреты на хосте

Секрет — это просто файл с содержимым. Важно, чтобы права не были слишком широкими, и чтобы редактор/оболочка не добавили перевод строки.

# создаём каталог для секретов проекта
mkdir -m 0700 -p ./secrets

# пароль без завершающего \n
printf "%s" "S3cr3t_Pg_Pass" > ./secrets/db_password
chmod 0400 ./secrets/db_password

Права 0400 на файл и 0700 на каталог — здравый минимум, чтобы случайно его не прочитали другие пользователи на хосте.

Compose-файл с secrets

Ниже пример compose.yml. PostgreSQL прочитает пароль из файла через POSTGRES_PASSWORD_FILE. Приложению передаём путь к файлу пароля БД, чтобы оно само открывало файл при старте.

version: "3.9"

secrets:
  db_password:
    file: ./secrets/db_password

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - source: db_password
        target: db_password
        mode: 0400
    volumes:
      - dbdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      timeout: 5s
      retries: 5

  app:
    image: ghcr.io/example/app:latest
    environment:
      DB_HOST: db
      DB_USER: app
      DB_NAME: app
      DB_PASSWORD_FILE: /run/secrets/db_password
    depends_on:
      db:
        condition: service_healthy
    secrets:
      - source: db_password
        target: db_password
        mode: 0400
    # пример: запускаем 2 реплики для обновлений без простоя
    deploy:
      replicas: 2

volumes:
  dbdata:

Ключевые моменты:

  • secrets объявляются на верхнем уровне и подключаются в каждую службу через service.secrets.
  • target задаёт имя файла в контейнере, обычно в /run/secrets. Compose монтирует файл только на чтение.
  • Опция mode — это права внутри контейнера (например, 0400).
  • Для БД используем POSTGRES_PASSWORD_FILE. Для своего приложения — считываем файл в рантайме.

Проверить, как секрет появился в контейнере, можно так:

docker compose up -d

docker compose exec app ls -l /run/secrets

Подходит связка healthcheck + поэтапный рестарт. Подробно про проверку готовности и политику перезапуска — в разборе healthcheck и restart-policy.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Как читать секрет из файла в приложении

Идеально, когда приложение умеет само читать секреты из файлов. Если нет — используйте тонкий стартовый скрипт, который читает файл и записывает его в приватный конфиг приложения или запускает процесс с нужным аргументом, не печатая значение в логи.

#!/bin/sh
set -eu

# читаем файл пароля и сохраняем в локальный конфиг
DB_PASS="$(cat "/run/secrets/db_password")"
# пример: генерируем конфиг приложения
cat > /app/config.local <<EOF
[db]
user=app
password=${DB_PASS}
host=db
name=app
EOF
chmod 0600 /app/config.local

# запускаем приложение ровно как обычно
exec /app/bin/server --config /app/config.local

Здесь пароль побывает в памяти процесса оболочки, но не попадёт в переменные окружения и не «засветится» в ps. Следите, чтобы скрипт не делал echo секретов в логи.

Ротация секретов в Compose

В Compose нет «горячей» замены секрета внутри уже работающего контейнера. Если поменять содержимое исходного файла на хосте, старый контейнер продолжит видеть прежний инстанс файла. Надёжный путь — создать новый секрет, обновить сервис и пересоздать контейнеры. Чтобы не уронить трафик — готовим стратегию.

Паттерны ротации без простоя

  1. Две реплики + последовательное обновление. Разверните сервис в 2+ экземплярах через deploy.replicas или масштабирование. Обновляйте по одному контейнеру, проверяя здоровье. Подойдёт для stateless-части.
  2. Blue/Green. Поднимите новый сервис с новым секретом (например, app_v2), прогрейте, переключите трафик в прокси и затем снимите старый.
  3. Двойная учётка БД. Создайте нового пользователя/пароль в БД с теми же правами. Обновите приложение на новый секрет. После стабилизации отключите старого пользователя.

Покажу процесс на последовательном обновлении с двумя репликами.

Шаги ротации

  1. Создайте новый файл секрета. Назовём его версионированно.
printf "%s" "N3w_S3cr3t" > ./secrets/db_password_v2
chmod 0400 ./secrets/db_password_v2
  1. Добавьте секрет в compose. И временно подключите его вторым, если приложение может выбрать путь. Чаще проще заменить старый монтируемый секрет на новый.
secrets:
  db_password:
    file: ./secrets/db_password
  db_password_v2:
    file: ./secrets/db_password_v2

services:
  app:
    # ...
    secrets:
      - source: db_password_v2
        target: db_password
        mode: 0400
  1. Обновите сервис с минимальным простоем. Если у вас 2 реплики, перезапуск произойдёт поочерёдно.
docker compose up -d --no-deps --scale app=2 app

Флаг --no-deps исключит лишние перезапуски зависимостей. После обновления проверьте метрики и логи. Когда убедились, что всё стабильно, удалите старый секрет из файла Compose и приберите файл с диска.

  1. Удалите старый секрет из проекта. Никогда не храните «пенсионеров» бесконечно.
shred -u ./secrets/db_password

Обратите внимание: точная пошаговая ротация зависит от приложения. Если оно кеширует соединение к БД, поможет мягкая перезагрузка процесса (SIGHUP) или поэтапная пересборка реплик.

Схема ротации секретов: Blue/Green и поочередный перезапуск

Ротация в Docker Swarm (кратко)

В Swarm секреты иммутабельны: вы не обновите значение, только создадите новый. Алгоритм тот же: создаём db_password_v2, привязываем к сервису, удаляем старый. Приятный бонус — rolling update из коробки.

# создаём новый секрет в Swarm
printf "%s" "N3w_S3cr3t" | docker secret create db_password_v2 -

# обновляем сервис на новый секрет (примерно)
docker service update --secret-rm db_password --secret-add source=db_password_v2,target=db_password,mode=0400 --update-parallelism 1 --update-delay 10s app

# после успешного обновления удаляем старый секрет
docker secret rm db_password

Контроль доступа, права и аудит

Несколько практических правил, которые экономят часы расследований:

  • Права на хосте. Каталогу с секретами — 0700, файлам — 0400. Владелец — пользователь, от имени которого запускаете docker compose.
  • Права в контейнере. Через service.secrets[].mode задайте 0400. Проверяйте: ls -l /run/secrets.
  • Не логируйте секреты. Отключите отладочные дампы, фильтруйте параметры в middleware логгера, используйте маскировку значений.
  • .gitignore и шифрование. Никогда не коммитьте «чистые» секреты. Если храните в репозитории — применяйте шифрование на уровне файлов и хранение ключей отдельно. В CI/CD держите секреты в безопасных хранилищах.
  • Бэкапы. Секреты попадают в бэкап томов и каталогов. Шифруйте бэкапы и контролируйте доступы к ним.
  • Учёт и сроки жизни. Ведите инвентаризацию: владелец, место применения, срок действия, дата последней ротации, контакт для уточнений.

Дополнительно продумайте сетевую сегментацию и фильтрацию трафика контейнеров — см. практику по настройке firewall для Docker (iptables/nftables).

От env к secrets: пошаговая миграция

  1. Найдите все места, где секреты в environment. docker compose config поможет увидеть финальную конфигурацию.
  2. Проверьте поддержку *_FILE. Многие официальные образы и популярные приложения уже умеют читать секреты из файлов.
  3. Вынесите значения в файлы в каталоге ./secrets. Задайте жёсткие права и владельца.
  4. Опишите secrets: в Compose. Подключите их в соответствующие сервисы. Проверьте права в контейнере.
  5. Сделайте деплой без простоя. Как минимум — 2 реплики сервиса, затем обновление по одной. Либо временный Blue/Green.
  6. Уберите старые env-переменные. После ввода секрета удалите «наследие» из .env, конфигов и CI.

Частые вопросы

Где физически хранятся secrets в Compose?

В Compose-режиме источником служит ваш файл на хосте. Docker не шифрует его «сам по себе». Поэтому защищённость каталога, права и бэкапы критичны. В контейнере секрет виден как файл только на чтение, обычно под /run/secrets.

Можно ли обновить секрет без рестарта контейнера?

Как правило, нет. Даже если вы перезапишете исходный файл на хосте, контейнер продолжит видеть старое содержимое до его пересоздания. Некоторые приложения умеют перечитывать файл по сигналу (SIGHUP), если вы меняете не секрет в /run/secrets, а «ссылку» в своём конфиге, но это уже вне механизма Docker.

Чем отличаются secrets и configs?

Технически оба монтируются как файлы. Семантически secrets — чувствительные данные, которым всегда выставляют строгие права, не логируют и часто не попадают в образы. configs — публичные конфиги, которые безопасно хранить в репозитории.

Чеклист безопасности

  • Секреты только файлами через secrets, а не в environment.
  • Проверяйте права: каталог 0700, файлы 0400, внутри контейнера 0400.
  • Используйте *_FILE, если образ это поддерживает.
  • Минимум 2 реплики для безостановочных обновлений или Blue/Green.
  • Версионируйте секреты: _v2, _2025-10, фиксируйте дату ротации.
  • Не храните секреты в чистом виде в Git и CI-логах, шифруйте бэкапы.
  • Чистите старые секреты после успешной ротации — и в Compose, и на диске.

Итоги

Переменные окружения удобны, но для секретов рискованны. Перевод чувствительных значений на Docker secrets с монтированием файлов в контейнер, строгими правами и продуманной ротацией резко снижает вероятность утечки. В Compose это просто: источник — локальные файлы, потребитель — приложение через *_FILE или собственный конфиг. Для безостановочного обновления держите минимум две реплики, обновляйте по одной и сразу убирайте старые секреты. Чем раньше вы вычистите секреты из environment, тем спокойнее будут ночи дежурного админа.

Поделиться статьей

Вам будет интересно

IPv6 на сервере: SLAAC vs static, privacy extensions и firewall без сюрпризов OpenAI Статья написана AI (GPT 5)

IPv6 на сервере: SLAAC vs static, privacy extensions и firewall без сюрпризов

Разбираем, как сервер получает IPv6 через SLAAC и почему для продакшена чаще нужен статический адрес. Объясняем privacy extensions ...
MX migration без простоя: dual delivery, TTL, приоритеты и план отката OpenAI Статья написана AI (GPT 5)

MX migration без простоя: dual delivery, TTL, приоритеты и план отката

Перенос почты — это не просто смена MX. В статье — практичный план MX migration без простоя: как заранее снизить DNS TTL, выставит ...
systemd-journald и syslog: хранение, ротация и форвардинг логов в Linux OpenAI Статья написана AI (GPT 5)

systemd-journald и syslog: хранение, ротация и форвардинг логов в Linux

Разбираем, как в Linux устроены логи с systemd-journald и syslog: где хранится journal, как включить Storage=persistent, ограничит ...