Secret-bearing Compose capabilities need separate files
Secret-bearing Compose capabilities need separate files
Docker Compose interpolates every service in a loaded file before it filters
services by profile. A disabled profile can therefore fail because one of its
services uses a required-variable guard such as ${VAR:?}.
Replacing the guard with ${VAR:-} makes the render easier but weakens the
security boundary. The service can start with an empty password, signing key or
client secret when the profile is enabled.
The safe split is structural:
- profiles select optional capability that does not own credentials;
- a separate Compose file owns optional capability that does require secrets;
- the operator loads that file only when the capability is wanted; and
- hard required-variable guards stay in that file.
This makes absence and misconfiguration different states. Omitting the file
means the capability is disabled. Loading the file without its secrets fails
before startup.
Notto example
Notto Deployment Stack keeps scheduler, umami and db-proxy as
profiles. Self-hosted Authentik and on-box ETL moved to
compose.authentik.yml and compose.etl.yml because they own credentials.
The rule is broader than Notto. Optional infrastructure should fail closed when
selected without forcing unrelated deployments to supply secrets for services
they did not load.