- Проверено по исходникам закреплённых версий: outline, wakapi, gitea, gramps и authelia умеют брать секреты из файла, tududi и wanderer — нет. - Отмечено, что `secrets-env-to-file` переросла в эпик: разбивать по приложениям отдельным заходом.
8.3 KiB
Секреты приложений через *_FILE, а не inline environment:
Приоритет: средний
miniflux сделан правильно — роль secrets кладёт каждую переменную vault в
отдельный файл, каталог монтируется :ro, а compose ссылается на *_FILE
(files/miniflux/docker-compose.template.yml). Остальные держат секреты прямыми
значениями в environment:: их читает любой член группы docker, любой процесс
с доступом к /proc/<pid>/environ, и они попадают в docker inspect.
tuwunel из списка убран: в его compose секретов нет (сверено 2026-07-25). memos и calibre тоже чистые.
Матрица поддержки (проверено по исходникам, 2026-07-25)
Проверялись закреплённые в compose версии, не main.
| Приложение | Секрет | Файл | Механизм |
|---|---|---|---|
outline 1.9.2 |
SECRET_KEY, UTILS_SECRET, OIDC, SMTP |
да | generic: environment.ts оборачивает process.env в Proxy, пустая переменная читается из <NAME>_FILE. С v1.7.0 |
| пароль postgres | да, с оговоркой | DATABASE_URL_FILE выносит URL целиком; чтобы вынести только пароль — раздельные DATABASE_HOST/PORT/NAME/USER + DATABASE_PASSWORD_FILE (взаимоисключимы с DATABASE_URL) |
|
| пароль сайдкара postgres | да | штатный POSTGRES_PASSWORD_FILE официального образа |
|
wakapi 2.17.5 |
salt, SMTP, OIDC | да | два пути: generic _FILE в loadSecretFiles() (с 2.17.2, в README список устарел) либо config.yml в /app/config.yml |
gitea 1.27.0 |
SMTP | да, частично | GITEA__mailer__PASSWD__FILE, работает для любого ключа app.ini, с 1.20 |
gramps 26.7.0 |
GRAMPSWEB_SECRET_KEY |
да | entrypoint читает /app/secret/secret, если переменная не задана. Путь уже смонтирован в нашем compose |
| SMTP | нет | обходной путь есть, но недокументированный: config.cfg исполняется Flask как Python, значит EMAIL_HOST_PASSWORD = open(...).read() сработает |
|
authelia 4.39.20 |
jwt-секрет, session, ключ хранилища, hmac OIDC, SMTP | да | AUTHELIA_..._FILE |
jwks-ключ, client_secret × 4 |
нет через _FILE |
IsSecretKey() отвергает ключи с []. Покрывается template-фильтром: X_AUTHELIA_CONFIG_FILTERS=template + {{ secret "путь" }} (с 4.38) |
|
tududi 1.2.4 |
все четыре | нет | всё через process.env; ни _FILE, ни конфига, ни CLI |
wanderer 0.18.3 |
мастер-ключ meilisearch | нет | только CLI/env/инлайн в config.toml; апстрим отклонил запрос в 2023 |
| ключ шифрования pocketbase | нет | собственная переменная wanderer, не штатный --encryptionEnv pocketbase; ENTRYPOINT в exec-форме, подстановку не сделать |
Что из этого следует
Самое дешёвое — gramps: каталог /app/secret уже смонтирован, достаточно
записать туда значение и убрать переменную из environment:. Оба сервиса
(gramps_app и gramps_celery) читают тот же файл через общий entrypoint.
Authelia выгоднее переводить не на _FILE, а целиком на template-фильтр:
_FILE покрывает пять полей из семи, фильтр — все разом, и смешивать их в одном
поле нельзя (errFmtSecretAlreadyDefined). Отдельная находка: четыре
client_secret штатно хранятся хэшем $pbkdf2-sha512$... (у нас уже есть
inv authelia-gen-secret-and-hash), плейнтекст с 4.38 вызывает warning
валидатора. То есть эти поля можно вообще перестать считать секретами.
Gitea даёт частичный выигрыш: секрет уходит из environment:, docker inspect и окружения процесса, но environment-to-ini пишет его открытым
текстом в app.ini на диске — открытая проблема апстрима (#35316).
tududi и wanderer не умеют ничего. Для них остаётся либо принять env, либо
перенести переменные в файл на диске: у tududi backend/app.js зовёт dotenv
при рабочей директории /app/backend, поэтому смонтированный туда .env уберёт
секреты хотя бы из docker inspect — в отличие от env_file:, где они попадают
в Config.Env. У wanderer доступен только env_file:, то есть косметика.
Попутная находка в закомментированном OIDC-блоке wakapi (ловушка на будущее, не
живой баг): имена WAKAPI_OIDC_PROVIDER_* устарели, в 2.17.5 разбирается
WAKAPI_OIDC_PROVIDERS_(\d+)_(...); кроме того renameEnvVars() режет строку по
первому =, так что секрет с base64-паддингом через env поедет битым. Оба
довода — за config.yml.
Docker secrets — разобрано, покрытия не расширяет
secrets: в compose вне swarm — это bind-mount файла в /run/secrets/<name>,
то есть тот же способ доставки, что и *_FILE. Приложение, умеющее только
переменную окружения, из /run/secrets не прочитает — ограничение в приложении,
а не в доставке. На таблицу выше не влияет.
Что даёт: секрет объявляется на сервис, а не монтируется каталогом. У miniflux
сейчас {{ secrets_dir }}:/secrets:ro висит на обоих сервисах, и postgres видит
все шесть файлов, включая секреты OIDC, хотя нужен ему один пароль. Улучшение
поверх образца, а не замена ему. uid/gid/mode вне swarm игнорируются —
права остаются с хост-файла, их ставит роль secrets.
Границы задачи
Она про то, где секрет лежит в рантайме (env против файла). Про то, чтобы он
не печатался в терминал при деплое, — no-secrets-in-playbook-output,
и она идёт первой: no_log дёшев, закрывает все приложения разом и не зависит от
этой матрицы. Для tududi и wanderer он вообще единственный ответ.
Матрица показала, что задача переросла в эпик: каждая строка — отдельное приложение со своим механизмом, и каждое мерджится независимо и само по себе даёт пользу. Договорились разбить по приложениям отдельным заходом (2026-07-25); до этого задачу в работу не брать целиком.
Из sec-ревью (fable, 2026-07-13). Связка с service-users-docker-group.
Связано: files/outline, files/wakapi, files/tududi, files/wanderer, files/gramps, files/gitea, files/authelia, files/miniflux (образец), roles/secrets.