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.exampleand a realconfig.yamlfor operation. - Kubernetes, Vault Agent, a CSI driver or secure file delivery through the deployment system is available.
Recommended pattern
- Keep only
config.yaml.examplein the repository. - Do not commit the real
config.yamlto 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.yamlto the repository - Storing a
config.yamlcontaining secrets in a ConfigMap - Baking
config.yamlinto a Docker image - Using
COPYin aDockerfileto include productionconfig.yaml - Documenting manual file delivery as the standard operating method
Recommended structure
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
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
database:
host: prod-db.internal
user: app_user
password: "<runtime-provided>"
jwt:
secret: "<runtime-provided>"
Example runtime file path
/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.
chmod 600 /etc/app/config.yaml
chown appuser:appgroup /etc/app/config.yaml
Kubernetes example
- Store the file contents in a
Secretor an external secret integration resource. - Mount it into the Pod at
/etc/app/config.yamlusing 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 COPYfor 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.yamlto 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.
- Remove secret values from the real
config.yamlcommitted to the repository. - Keep only
config.yaml.examplein the repository and add the realconfig.yamlto.gitignore. - Supply the real
config.yamlat runtime through a Kubernetes Secret volume, CSI/Vault Agent or secure file delivery. - Keep the application reading the designated path, but make it exit immediately if the file or a required secret is missing.
- Restrict file permissions and remove world-readable access.
- Mask secrets in debug logs, panic output and configuration dumps.
- Document the reload or restart procedure after file updates during secret rotation.
- Revoke and replace any old value exposed in the repository.
Example instructions for the owner
- “Even if the service needs the
config.yamlformat, do not commit the production file to Git. Mount it securely at runtime.” - “Keep only
config.yaml.examplein the repository and supply the realconfig.yamlthrough 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.yamlcontaining secrets in a Docker image or ConfigMap.”
Operational checklist
- Is the real configuration file covered by
.gitignore? - Are the roles of
config.yaml.exampleand the realconfig.yamlclearly 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?