Pass secrets to your app
Choose environment variables or mounted files for application secrets.
Choose where a secret is stored with its provider declaration. Separately, choose how your app reads it: as an environment variable or a mounted file. You can change delivery without moving to a different secret store.
Environment delivery
Secret-backed environment bindings use the environment's selected delivery mode along with non-secret values:
environment-delivery:
default: direct
development-default: env-file
environments:
production:
environment-delivery: direct
directpasses values through the runtime process environment.env-filewrites private temporary environment files for the operation.
When a development environment is declared, it defaults to env-file and other
environments default to direct. Inspect the actual setting:
mm env delivery show
mm env review
mm env delivery set production direct
Environment variables are visible to the process and may be exposed by application diagnostics or privileged runtime inspection. Use mounted files when the application supports them and narrower exposure matters.
File delivery
Declare file consumers under the service. The map key is the file name under the runtime's secret directory, not a host path:
schema-version: 1
project:
id: 019c527b-6242-7b43-bfd4-bd60e27c1de3
name: secret-files
target: local
stacks:
app:
files: [compose.yaml]
modules:
api:
path: api
services:
api:
stack: app
secret-files:
database-password:
secret: database.password
uid: "0"
gid: "0"
mode: 292 # decimal form of 0444
secrets:
database.password:
storage:
provider: age
location: local/database-password
environments:
local:
bindings: {}
The deployment plan shows secret names, versions, permissions, and consuming services, without printing values. If a secret changes after review, make a new plan.
Runtime behavior differs:
| Runtime | File delivery | Important constraint |
|---|---|---|
| Podman Compose | Versioned native Podman secret | Native store; UID/GID/mode supported |
| Docker Swarm | Versioned immutable Swarm secret | Available on every node; UID/GID/mode supported |
| Docker Compose | Private operation-scoped source file | No native store; requires UID 0, GID 0, mode 0444 |
Podman and Swarm receive material on standard input. Rotation creates a new owned revision, updates workloads, and retains the previous revision for rollback. Inspection and cleanup use labels and metadata and do not read secret content.
Docker Compose writes a 0600 source in a private operation directory and
removes the path after container creation; the mounted inode remains available
to that container. Compose cannot enforce requested file ownership or mode, so
other values fail before material is written. Rotation also fails early when a
rollback-safe update cannot be guaranteed.
Check metadata without revealing values:
mm secret status
mm env review
mm deploy plan --output json
These views omit credentials and secret values. Host and container-engine administrators can still access secrets used by workloads.
Build secrets
For sensitive build inputs, use modules.<name>.build.secrets to map a BuildKit
secret ID to a declared mm secret. Do not use public build arguments for tokens.
mm passes build secrets through private files after plan review. See the
build configuration reference.
A build secret can change the image, and its value can differ between
environments, so mm never reuses or promotes a build that mounts one. When a
secret only authenticates fetching inputs, such as a Composer or npm token for
private packages, list it under credential-only-secrets:
modules:
web:
build:
secrets: { composer_auth: composer.auth }
credential-only-secrets: [composer_auth]
The secret is still mounted, but it is left out of the build fingerprint, so the same inputs reuse the same image whoever builds them and however often the token rotates. Only list a secret here when the image does not depend on its value.