✦ For everyone, free.

Practical knowledge for real and everyday life

Home

14.2.2.3 Mounted Secret Files

A focused guide to Mounted Secret Files, connecting core concepts with practical Docker and container operations.

Mounted secret files are the pattern of delivering a credential into a container as a file on a mounted volume rather than as an environment variable, giving the application access to the value through ordinary file reads while keeping it out of docker inspect output, process environment dumps, and child-process inheritance.

Why a file instead of an environment variable

An environment variable set on a container is visible to every process running inside it, is inherited automatically by any child process that process spawns, and is readable through docker inspect by anyone with inspect access to the container. A mounted file narrows all three of these exposure paths: only the specific process that opens the file reads its contents, no inheritance happens automatically, and the value never appears in docker inspect output at all.

docker inspect my-api --format '{{json .Config.Env}}'
docker exec my-api cat /run/secrets/db_password

The first command reveals every environment variable set on the container, including any secret mistakenly passed that way; the second requires deliberate, authenticated access to the container itself and only reveals the one file requested.

Docker Swarm's native secret mounts

Swarm's built-in secrets mechanism mounts each secret as an individual file under /run/secrets/<secret-name>, backed by an in-memory filesystem (tmpfs) rather than the container's writable layer, so the value never persists to disk inside the container:

echo "supersecretpassword" | docker secret create db_password -
services:
  api:
    image: my-api:latest
    secrets:
      - db_password
docker exec my-api ls -la /run/secrets/
docker exec my-api cat /run/secrets/db_password

By default the mounted file is owned by root with restrictive permissions, and the secret name becomes the filename unless an explicit target name is specified in the service definition.

Custom mount paths and filenames

A secret can be mounted at a path other than the default, which is useful when an application or library expects a credential file at a specific, conventional location:

secrets:
  db_password:
    external: true

services:
  api:
    secrets:
      - source: db_password
        target: /etc/myapp/secrets/db_password
        mode: 0400

The mode setting controls the file's permission bits once mounted, and restricting it to read-only for the owning user (0400) is generally the correct default unless a specific process running as a different user genuinely needs access.

Bind-mounting a secret outside of Swarm

When Swarm's native mechanism is not in use, the same pattern can be approximated with an ordinary read-only bind mount from a host path:

docker run -v /etc/secrets/db_password:/run/secrets/db_password:ro my-api

This approach loses the in-memory, encrypted-at-rest guarantees Swarm's secret store provides, since the value sits as a plaintext file on the host's actual disk, so host filesystem permissions become the entire protection boundary:

chmod 600 /etc/secrets/db_password
chown root:root /etc/secrets/db_password

Reading mounted secrets in application code

The application reads a mounted secret exactly as it would read any other file, with no special API required:

const fs = require('fs');
const dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();
with open('/run/secrets/db_password') as f:
    db_password = f.read().strip()

Trimming trailing whitespace or newline characters is worth doing explicitly, since many tools that create secret files (echo, certain secret managers' CLI output) append a trailing newline that would otherwise become part of the credential value if compared or used literally.

Handling secret rotation with mounted files

A mounted file approach can support rotation without a container restart if the underlying mount is refreshed in place and the application either re-reads the file periodically or reacts to a filesystem change notification:

const fs = require('fs');
let dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();

fs.watch('/run/secrets/db_password', () => {
  dbPassword = fs.readFileSync('/run/secrets/db_password', 'utf8').trim();
});

Swarm's own native secrets do not support in-place rotation of a single secret object; rotation is handled by creating a new secret and updating the service to reference it, after which the orchestrator performs a rolling replacement of the affected containers.

Avoiding accidental persistence

A mounted secret file should never be copied into a location that persists beyond the container's lifecycle, such as a named volume meant for application data, or logged as part of debugging output:

docker logs my-api | grep -i password

Running a check like this periodically against application logs is a reasonable way to confirm that debug or error logging has not inadvertently captured the contents of a mounted secret file.

Common mistakes

  • Copying a mounted secret's contents into an environment variable inside the application's own startup script, recreating the exposure the file-based approach was meant to avoid.
  • Mounting a secret file with overly permissive file modes, allowing any process inside the container to read a value that only one process needs.
  • Forgetting to strip trailing newline characters from a secret file's contents, causing authentication failures that are difficult to diagnose because the value looks correct when printed.
  • Using a plain bind mount from host disk for a highly sensitive secret when an in-memory, Swarm-native secret mount is available and would avoid leaving the value as plaintext on persistent storage.
  • Logging the full contents of a configuration directory during debugging without excluding the secrets path, inadvertently writing credential values into application logs.

Mounted secret files give an application straightforward, file-based access to credentials while keeping them out of the inspection and inheritance paths that make environment variables a weaker choice, and the specific mechanism, whether Swarm-native, bind-mounted, or sidecar-rendered, should be chosen based on how sensitive the value is and how frequently it needs to rotate.