Secrets in runtime configuration files

Some applications need to retain configuration files such as config.yaml, application.yml or settings.json. Even then, do not commit literal secrets to the repository. Generate or mount the real configuration file from a secure source at runtime.

When to use this guide

  • Environment variables alone cannot readily represent the configuration.
  • The application requires a configuration file at a specific path.
  • You need both config.yaml.example and a real config.yaml for operation.
  • Kubernetes, Vault Agent, a CSI driver or secure file delivery through the deployment system is available.

Recommended pattern

  • Keep only config.yaml.example in the repository.
  • Do not commit the real config.yaml to Git.
  • Generate or mount the real file at runtime through a Kubernetes Secret volume, CSI/Vault Agent or the deployment system’s secure file delivery.
  • Read configuration from the designated path and fail closed if the file is missing or a required secret is empty.

When to choose this pattern

  • The application must read a configuration file at a specific path.
  • Environment variables cannot easily represent the hierarchical configuration.
  • The deployment platform supports secret file mounts or secure file delivery as a standard feature.

Patterns to avoid

  • Committing production config.yaml to the repository
  • Storing a config.yaml containing secrets in a ConfigMap
  • Baking config.yaml into a Docker image
  • Using COPY in a Dockerfile to include production config.yaml
  • Documenting manual file delivery as the standard operating method

Recommended structure

text
config.yaml.example         # Sample configuration only; can be committed to Git
config.yaml                 # Real production configuration; do not commit to Git
.gitignore                  # Includes config.yaml
/etc/app/config.yaml        # Example path for a real file mounted or generated at runtime

Examples

Before

yaml
database:
  host: prod-db.internal
  user: app_user
  password: super-secret-password

jwt:
  secret: hardcoded-jwt-secret

Treat the secrets as exposed if a file like this is included in the repository.

After

Repository file: config.yaml.example

yaml
database:
  host: prod-db.internal
  user: app_user
  password: "<runtime-provided>"

jwt:
  secret: "<runtime-provided>"

Example runtime file path

text
/etc/app/config.yaml

A Kubernetes Secret volume, Vault Agent/CSI or the deployment system’s secure file delivery generates or mounts this file at runtime.

File permissions

  • Use least-privilege permissions so only the application’s operating account can read the configuration file.
  • Do not make the file world-readable.
  • Restrict its owner and group according to your operating standards.

These commands apply to an ordinary writable file. For read-only mounts such as Kubernetes Secret volumes, configure permissions and ownership through the volume settings.

bash
chmod 600 /etc/app/config.yaml
chown appuser:appgroup /etc/app/config.yaml

Kubernetes example

  • Store the file contents in a Secret or an external secret integration resource.
  • Mount it into the Pod at /etc/app/config.yaml using a Secret volume.
  • Do not replace the Secret with a ConfigMap.

Developer responsibilities

  • Remove secret values from real configuration files in the repository.
  • Keep reading the runtime file path, but fail immediately if the file or a required value is missing.
  • Do not print the whole configuration object in logs, panic output or debug dumps.
  • Document whether secret rotation requires the application to reload or restart.

Infrastructure and platform responsibilities

  • Supply the real file through a Kubernetes Secret volume, CSI/Vault Agent or secure file delivery.
  • Establish deployment standards that avoid ConfigMaps, Docker image embedding and Dockerfile COPY for secret-bearing files.
  • Restrict file ownership, group and permissions to the minimum required.
  • Document file updates and restart/reload steps during secret rotation.

Verification

  • Confirm that production configuration files are absent from the repository.
  • Check that permissions are restricted on the container or host.
  • Verify that the application fails when the file is missing.
  • Check that configuration values do not leak through logs, actuator endpoints or exception messages.

Common mistakes

  • Committing production config.yaml to Git
  • Storing secret-bearing configuration files in ConfigMaps
  • Baking the configuration file into a Docker image
  • Deploying the file with broad, world-readable permissions
  • Rotating a secret without defining how to reload or restart the application

Remediation steps for the owner

Revoke or disable exposed credentials and replace them first. Do not wait for the file cleanup or deployment changes below.

  1. Remove secret values from the real config.yaml committed to the repository.
  2. Keep only config.yaml.example in the repository and add the real config.yaml to .gitignore.
  3. Supply the real config.yaml at runtime through a Kubernetes Secret volume, CSI/Vault Agent or secure file delivery.
  4. Keep the application reading the designated path, but make it exit immediately if the file or a required secret is missing.
  5. Restrict file permissions and remove world-readable access.
  6. Mask secrets in debug logs, panic output and configuration dumps.
  7. Document the reload or restart procedure after file updates during secret rotation.
  8. Revoke and replace any old value exposed in the repository.

Example instructions for the owner

  • “Even if the service needs the config.yaml format, do not commit the production file to Git. Mount it securely at runtime.”
  • “Keep only config.yaml.example in the repository and supply the real config.yaml through a Kubernetes Secret volume or the deployment system’s secure file delivery.”
  • “Make the application fail immediately if the configuration file is missing or a required secret is empty.”
  • “Do not put a config.yaml containing secrets in a Docker image or ConfigMap.”

Operational checklist

  • Is the real configuration file covered by .gitignore?
  • Are the roles of config.yaml.example and the real config.yaml clearly separated?
  • Are file permissions restricted to the minimum required?
  • Does the application avoid printing the whole configuration object in logs, panic output and debug dumps?
  • Is a reload or restart procedure defined for secret rotation?
  • If manual delivery is exceptionally required, is it treated only as a fallback rather than the recommended default?

Related documentation