Секреты и конфигурация: как не светить пароли

Рейтинг: 68.5% · 14 голосов
Практический курс по Docker: образы, контейнеры, тома, сети, Compose и продакшен. Уроки по главам с обсуждением.
Ответить
Аватара пользователя
Marina_DevOps
Сообщения: 54
Зарегистрирован: 11 май 2026, 05:31

Секреты и конфигурация: как не светить пароли

Сообщение Marina_DevOps »

Оглавление курса (46)
  1. Что такое Docker и какие задачи он решает
  2. Установка Docker и запуск первого контейнера
  3. Образы: слои, теги и реестр Docker Hub
  4. Пишем свой Dockerfile
  5. Тома и хранение данных: volumes и bind mounts
  6. Сети в Docker: связываем контейнеры между собой
  7. Переменные окружения и конфигурация контейнеров
  8. Docker Compose: поднимаем многоконтейнерное приложение
  9. Оптимизация образов: multi-stage сборка, размер и кэш слоёв
  10. Логи, отладка и мониторинг контейнеров
  11. Базовая безопасность контейнеров
  12. Подготовка к продакшену: что важно учесть
  13. Dockerfile глубже: ENTRYPOINT и CMD, HEALTHCHECK, .dockerignore, запуск не от root
  14. Реестры образов: приватные registry, push и pull, теги и digest, imagePullSecrets
  15. BuildKit и buildx: multi-arch сборки, секреты сборки, экспорт кэша
  16. Docker в CI/CD: автосборка, сканирование образов (Trivy, Docker Scout), публикация
  17. Итоговый проект и куда расти: от Dockerfile до прода, обзор оркестрации (Kubernetes, Podman, OCI)
  18. Архитектура Docker: dockerd, containerd, runc и OCI
  19. Как работает изоляция: namespaces и cgroups
  20. Образы изнутри: слои, OverlayFS и storage driver
  21. Жизненный цикл контейнера и политики перезапуска
  22. Отладка контейнеров: exec, attach, nsenter, debug
  23. Сети Docker глубже: bridge, overlay, macvlan и iptables
  24. Docker Compose глубже: profiles, healthcheck-зависимости, override
  25. Данные и тома глубже: драйверы, бэкап, права
  26. Секреты и конфигурация: как не светить пароли (вы здесь)
  27. Ограничение ресурсов: CPU, память, OOM и cgroups
  28. Здоровье и автозапуск: HEALTHCHECK, init и сигналы
  29. Логирование контейнеров: драйверы, ротация, сбор
  30. Мониторинг контейнеров: stats, cAdvisor, Prometheus
  31. Безопасность глубже: rootless, user namespaces, seccomp, capabilities
  32. Supply chain: сканирование, SBOM и подпись образов
  33. Глубокая оптимизация образов: distroless, scratch, кэш
  34. Multi-arch и сборка под ARM: buildx и эмуляция
  35. Реестры в продакшене: Harbor и облачные registry
  36. Docker без Docker: Podman, containerd, nerdctl, Buildah
  37. От Compose к оркестрации: когда контейнеров становится много
  38. Docker Swarm: встроенная оркестрация
  39. Сборка образов в CI без демона: kaniko, BuildKit, Buildah
  40. Траблшутинг Docker: типичные ошибки и как чинить
  41. Производительность и эксплуатация Docker-хоста
  42. Docker Desktop, WSL2 и альтернативы на Windows и Mac
  43. Контейнеризация приложений: бэкенд, фронтенд, БД
  44. Docker и Kubernetes: containerd, миграция, kompose
  45. Лучшие практики и антипаттерны Docker
  46. Сквозной проект: production-ready стек и путь дальше
Боль: один docker inspect - и пароль от продакшен-базы у всех

Давай честно. Пароль от базы, токен платежного шлюза, приватный ключ для доступа в приватный реестр - все это где-то лежит. Вопрос не в том, есть ли у тебя секреты, а в том, насколько широко они размазаны. Самый частый сценарий утечки выглядит так: разработчик кладет пароль в ENV в Dockerfile, потому что так быстрее, образ уезжает в реестр, а через полгода стажер делает

Код: Выделить всё

docker inspect
на запущенном контейнере и видит этот пароль открытым текстом. Дальше - как повезет.

Тема docker секреты на собеседованиях и в реальной эксплуатации это лакмус: по тому, как человек хранит docker пароль, сразу видно, понимает он модель угроз или просто катит код. В этом уроке разберем, почему ENV и слои образа - это публичная доска объявлений, как работают build secret docker и runtime-секреты под капотом, и как выстроить принцип "секреты снаружи, контейнер только читает". Глубоко, с разбором вывода команд и граблями, на которые наступают даже сеньоры.

Ключевая мысль, которую надо усвоить раз и навсегда: образ - это артефакт, который ты публикуешь и кешируешь, а секрет - это то, что нельзя публиковать и кешировать. Совмещать их физически нельзя. Все решения ниже - про то, как развести эти две сущности во времени и в пространстве.

Изображение

Почему ENV и слои образа не место для секретов в контейнере

Начнем с механики, потому что без нее советы звучат как догма. Образ Docker - это набор слоев (read-only), описанных манифестом, плюс конфиг. Каждая инструкция Dockerfile, которая меняет файловую систему или метаданные, порождает слой или запись в конфиге. И вот ключевое: слои неизменяемы и кешируются, а конфиг доступен любому, кто скачал образ.

Разберем три канала утечки, которые надо понимать пальцами.

1. ENV живет в конфиге образа и виден всем. Если ты пишешь в Dockerfile

Код: Выделить всё

ENV DB_PASSWORD=hunter2
, это значение попадает в конфиг и наследуется во все дочерние образы и все запущенные контейнеры. Проверяется так:

Код: Выделить всё

docker inspect myapp:latest --format '{{json .Config.Env}}'
Вывод:

Код: Выделить всё

["PATH=/usr/local/bin:/usr/bin:/bin","DB_PASSWORD=hunter2","LANG=C.UTF-8"]
Никакого взлома. Просто . Любой, у кого есть образ из реестра, видит это поле. Передача пароля через

Код: Выделить всё

--build-arg
тоже не спасает: ARG не пишется в ENV, но остается в истории сборки.

2. История слоев хранит команды и ARG. Смотрим:

Код: Выделить всё

docker history --no-trunc myapp:latest
Если ты делал

Код: Выделить всё

ARG TOKEN
и потом

Код: Выделить всё

RUN curl -H "Authorization: $TOKEN" ...
, то в выводе ты увидишь развернутую команду RUN с подставленным токеном. Даже если ты потом удалил файл с секретом в следующем слое - предыдущий слой никуда не делся, он лежит в образе целиком. Файловая система слоев работает как стопка прозрачных пленок: верхняя пленка "затирает" файл белым, но нижняя, где он есть, осталась. Распаковал слой - достал секрет.

3. Логи и CI. Секрет в ENV рано или поздно попадет в дамп окружения. Любой

Код: Выделить всё

printenv
, краш с трейсбеком, который печатает config, или CI-шаг с - и пароль в логах сборки, которые хранятся месяцами и часто доступны шире, чем сам реестр.

Вывод жесткий: ни ENV, ни ARG, ни COPY секретного файла в образ - не годятся. Образ должен быть стерильным и безопасным для публикации даже в публичный Docker Hub. Все, что секретно, подается отдельно - либо на момент сборки через BuildKit, либо в рантайме через монтирование.

Build secret docker: как BuildKit подает секрет в сборку и не оставляет следа

Иногда секрет нужен именно во время сборки: скачать приватный пакет, авторизоваться в приватном npm/composer-реестре, клонировать приватный git. Для этого есть build secret docker - механизм BuildKit, который монтирует секрет в файловую систему сборки только на время одной инструкции RUN и не пишет его ни в слой, ни в историю.

BuildKit - это движок сборки по умолчанию в современном Docker и в

Код: Выделить всё

docker buildx
. Синтаксис в Dockerfile:

Код: Выделить всё

# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=secret,id=npmrc,target=/root/.npmrc \
    npm ci --omit=dev
COPY . .
Запуск сборки с подачей секрета из файла:

Код: Выделить всё

docker buildx build --secret id=npmrc,src=$HOME/.npmrc -t myapp:latest .
Что происходит под капотом. Флаг

Код: Выделить всё

--secret
говорит BuildKit взять файл с клиента и сделать его доступным как tmpfs-файл внутри контейнера сборки по пути

Код: Выделить всё

/run/secrets/<id>
(или по , если указан). Доступен он только на время выполнения этой конкретной RUN-инструкции. Как только RUN завершился, монтирование снимается. В слой не пишется ничего: tmpfs живет в памяти и в результирующую файловую систему не попадает. В

Код: Выделить всё

docker history
ты увидишь строку RUN с записью про mount, но без содержимого секрета.

Можно подать секрет и из переменной окружения, не создавая файл:

Код: Выделить всё

export GH_TOKEN=ghp_xxx
docker buildx build --secret id=gh,env=GH_TOKEN -t myapp .
А внутри Dockerfile читать его:

Код: Выделить всё

RUN --mount=type=secret,id=gh \
    git clone https://oauth2:$(cat /run/secrets/gh)@github.com/org/private.git
Грабля номер один. Секрет не остается в слое только если ты сам его туда не записал. Если внутри RUN ты сделаешь

Код: Выделить всё

cat /run/secrets/gh > /app/token.txt
- все, ты собственноручно положил его в слой, BuildKit тут бессилен. Правило: прочитал, использовал в рамках одной команды, не сохранил.

Грабля номер два - забыть строку

Код: Выделить всё

# syntax=docker/dockerfile:1
в самом верху. Без актуального синтаксиса парсер не поймет

Код: Выделить всё

--mount=type=secret
. И проверь, что BuildKit включен (в новых Docker он по умолчанию; если нет -

Код: Выделить всё

DOCKER_BUILDKIT=1
).

После сборки обязательно проверь себя - это часть гигиены supply chain:

Код: Выделить всё

docker history --no-trunc myapp:latest | grep -i secret
docker inspect myapp:latest --format '{{json .Config.Env}}'
Если в выводе нет ни самого значения, ни намека - сборка чистая.

Секреты в контейнере в рантайме: файл, Docker secrets и внешние хранилища

Сборка стерильна, но приложению в рантайме все равно нужен пароль к базе. Как подать секреты в контейнере, не вшивая их в образ? Принцип один: секрет приходит снаружи, контейнер его только читает, желательно из файла, а не из ENV. Файл лучше ENV, потому что ENV наследуется дочерними процессами и легко утекает в дампы, а файл с правами 0400 читает только нужный процесс.

Вариант для одиночного хоста и Compose - монтирование файла-секрета. Compose v2 умеет работать с секретами как с отдельной сущностью. Файл

Код: Выделить всё

compose.yaml
:

Код: Выделить всё

services:
  app:
    image: myapp:latest
    secrets:
      - db_password
    environment:
      DB_PASSWORD_FILE: /run/secrets/db_password

secrets:
  db_password:
    file: ./secrets/db_password.txt
Здесь секрет монтируется внутрь контейнера как файл

Код: Выделить всё

/run/secrets/db_password
. Обрати внимание: в environment мы кладем не сам пароль, а путь к файлу (паттерн _FILE). Многие официальные образы - postgres, mysql, redis - из коробки читают

Код: Выделить всё

POSTGRES_PASSWORD_FILE
и подобные, это стандарт де-факто. Сам файл

Код: Выделить всё

./secrets/db_password.txt
лежит вне образа и обязан быть в

Код: Выделить всё

.gitignore
.

Вариант для кластера - Docker secrets в Swarm. Нативные docker secrets (именно как механизм) живут в Swarm: они хранятся в зашифрованном Raft-логе менеджеров, передаются на ноды по mTLS и монтируются в сервис как tmpfs-файл в

Код: Выделить всё

/run/secrets/<name>
. На диск рабочей ноды в открытом виде не пишутся.

Код: Выделить всё

printf 'S3cr3tP@ss' | docker secret create db_password -
docker service create --name app --secret db_password myapp:latest
Внутри контейнера сервис читает

Код: Выделить всё

/run/secrets/db_password
. Это самый недооцененный механизм: команды часто тащат тяжелый Vault туда, где хватило бы Swarm-секретов. Но учти ограничение: Swarm-секреты иммутабельны. Чтобы сменить значение, надо создать новый секрет и обновить сервис (

Код: Выделить всё

docker service update --secret-rm old --secret-add new
) - ротация не бесплатная.

Вариант для зрелого продакшена - внешнее хранилище. HashiCorp Vault, Yandex Lockbox, AWS Secrets Manager - это управляемые хранилища с аудитом, версионированием и автоматической ротацией. Подключают их обычно двумя путями:
  • entrypoint-подход: скрипт-обертка при старте контейнера ходит в Vault/Lockbox по короткоживущему токену, кладет секреты во временный файл или в окружение текущего процесса и запускает приложение через . Секрет не вшит, живет только в памяти процесса.
  • sidecar-подход: рядом с приложением едет агент (например Vault Agent), он аутентифицируется, забирает секреты и кладет их в общий tmpfs-том. Приложение читает файл, агент сам обновляет его при ротации. Приложение про Vault вообще не знает - чистое разделение ответственности.
Для RU-реалий Yandex Lockbox - это аналог Vault в Yandex Cloud: версии секрета, IAM-доступ, чтение через CLI

Код: Выделить всё

yc lockbox payload get
или SDK прямо из entrypoint. Подход тот же: контейнер на старте подтягивает значение и держит его в памяти.

.env, .gitignore и сканирование репозитория на секреты через gitleaks

Самая массовая утечка - не хитрый docker inspect, а банальный закоммиченный . Файл удобен для локальной разработки: Compose автоматически подхватывает из него переменные. Но он обязан быть в

Код: Выделить всё

.gitignore
, а в репозиторий кладется только

Код: Выделить всё

.env.example
с пустыми значениями-заглушками.

Код: Выделить всё

# .gitignore
.env
.env.local
secrets/
*.pem
*.key
Проблема в том, что

Код: Выделить всё

.gitignore
не защищает задним числом: если файл уже закоммичен, добавление в ignore ничего не лечит - секрет уже в истории git навсегда, в каждом клоне. Поэтому нужен автоматический сканер. Стандарт индустрии - gitleaks: он ищет секреты по регуляркам и по энтропии (высокая случайность строки = вероятный токен), проходит по всей истории коммитов, а не только по текущим файлам.

Запуск через образ, без локальной установки:

Код: Выделить всё

docker run --rm -v "$(pwd):/repo" zricethezav/gitleaks:latest \
    detect --source=/repo --verbose
Кусок вывода при находке:

Код: Выделить всё

Finding:     AWS_SECRET=AKIA...
Secret:      AKIAIOSFODNN7EXAMPLE
RuleID:      aws-access-token
File:        config/prod.yml
Commit:      4f2a1c9
Author:      dev2
Поле - это адрес, где утек секрет в истории. Если он не пустой, значит секрет уже в git и его недостаточно удалить - нужно немедленно отозвать и перевыпустить (rotate), потому что считай, что он уже скомпрометирован. Чистка истории (

Код: Выделить всё

git filter-repo
) - вторична, главное - ротация.

Правильное место для gitleaks - два рубежа: pre-commit hook (ловит до коммита, на машине разработчика) и шаг в CI на каждый PR (ловит то, что просочилось мимо хука). Это прямая часть supply chain security: ты защищаешь не только образ, но и исходники, из которых он собирается.

Типичные грабли и антипаттерны
  • ARG для секрета.

    Код: Выделить всё

    ARG TOKEN
    кажется безопасным, но он виден в

    Код: Выделить всё

    docker history
    . Только

    Код: Выделить всё

    --mount=type=secret
    .
  • COPY .env в образ. Классика. Файл с паролями уезжает в реестр внутри слоя. Добавь в

    Код: Выделить всё

    .dockerignore
    , не только в

    Код: Выделить всё

    .gitignore
    .
  • echo секрета в логи.

    Код: Выделить всё

    RUN echo "token: $TOKEN"
    для отладки - и токен в логах CI навсегда. Никогда не печатай секреты.
  • Запись секрета в слой внутри RUN. BuildKit-секрет, сохраненный в файл командой - это уже не секрет. Используй в рамках одной команды.
  • Один пароль на все среды. dev, stage, prod должны иметь разные секреты. Утечка из dev не должна открывать prod.
  • Секрет в ENV вместо файла. ENV наследуется дочерними процессами и течет в дампы. Где можешь - паттерн _FILE.
  • Длинноживущие токены. Статический пароль, который никогда не меняется, - вопрос времени. Внешние хранилища дают короткоживущие и ротируемые секреты.
Мини-лаба: подай build secret и runtime secret руками
  • Создай файл

    Код: Выделить всё

    ./secret.txt
    со строкой

    Код: Выделить всё

    super-secret-value
    .
  • Напиши Dockerfile с

    Код: Выделить всё

    # syntax=docker/dockerfile:1
    и инструкцией

    Код: Выделить всё

    RUN --mount=type=secret,id=mysecret cat /run/secrets/mysecret
    , выводящей секрет в лог сборки (для лабы, не для прода).
  • Собери:

    Код: Выделить всё

    docker buildx build --secret id=mysecret,src=./secret.txt -t lab .
    . Убедись, что секрет напечатался в выводе RUN.
  • Проверь, что в образе его нет:

    Код: Выделить всё

    docker history --no-trunc lab
    и

    Код: Выделить всё

    docker inspect lab --format '{{json .Config.Env}}'
    . Содержимого секрета быть не должно.
  • Подними Compose-сервис с секретом-файлом и -переменной, зайди внутрь (

    Код: Выделить всё

    docker compose exec app sh
    ) и прочитай

    Код: Выделить всё

    /run/secrets/db_password
    .
  • Прогони gitleaks по любому своему репозиторию и посмотри на формат находок.
Контрольные вопросы
  • Почему

    Код: Выделить всё

    docker history
    делает

    Код: Выделить всё

    ARG TOKEN
    небезопасным даже если ты удалил файл с токеном в следующем слое?
  • Чем монтирование секрета файлом в

    Код: Выделить всё

    /run/secrets/
    лучше передачи через ENV в рантайме?
  • Что физически происходит с build secret после завершения RUN-инструкции и почему он не попадает в слой?
  • Ты нашел секрет в истории git через gitleaks. Достаточно ли удалить файл и почистить историю?
Итог

Образ публикуется и кешируется, секрет - нет, поэтому они не должны жить в одном месте. На сборке секрет подается через

Код: Выделить всё

--mount=type=secret
BuildKit и не оставляет следа. В рантайме - монтируется файлом (Docker secrets в Swarm, secrets в Compose, sidecar/entrypoint к Vault или Yandex Lockbox), а контейнер только читает. Файл - в

Код: Выделить всё

.gitignore
и

Код: Выделить всё

.dockerignore
, репозиторий сканируется gitleaks на двух рубежах. Принцип, который останется с тобой: секреты снаружи, контейнер только читает, утекший секрет - сразу ротируй.
👍5 ❤️1 🔥1 😄 🤔
Аватара пользователя
toddti
Сообщения: 1
Зарегистрирован: 22 май 2026, 12:36

Re: Секреты и конфигурация: как не светить пароли

Сообщение toddti »

Блин, у нас полгода в Dockerfile висел ENV с паролем от прода. Сделал docker inspect - реально лежит открытым текстом. Сегодня же выпиливаю и ротирую токены, спасибо что показал на пальцах.
👍 ❤️1 🔥1 😄 🤔2
Аватара пользователя
heike
Сообщения: 1
Зарегистрирован: 24 май 2026, 05:25

Re: Секреты и конфигурация: как не светить пароли

Сообщение heike »

А если у меня просто Compose на одном сервере без Swarm, секрет файлом через secrets в compose.yaml норм или лучше сразу Vault поднимать? Кажется Vault тут оверкилл, но хочется по уму.
👍2 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
Данные и тома глубже: драйверы, бэкап, права
Следующая глава →
Ограничение ресурсов: CPU, память, OOM и cgroups

Все главы курса «Docker: контейнеризация от основ до продакшена»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Установка и первый конфиг nginxnginx в Docker

Вернуться в «Docker с нуля»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость