Secrets Management for Rust Services in Production
Keep credentials out of images, repositories and logs. Compare environment variables, mounted files, systemd credentials and a secret manager, and plan for rotation.

Most credential leaks are not clever attacks. They are a .env committed in a hurry, a token in a Dockerfile ENV line, or a Debug print in a log aggregator. The defenses are boring and cheap if you decide them before the first deploy. This post compares four delivery methods and then lists the Rust-side habits that keep a secret from leaking after it arrives. It is the secrets step of shipping Rust to production, following configuration from the environment.
Rust 1.90.
Where secrets must not be
| Place | Why not |
|---|---|
| Source repository | history is permanent and widely cloned |
Dockerfile ENV or ARG | stored in image layers and visible in docker history |
Compile-time env! or include_str! | baked into the binary, readable with strings |
| Command line arguments | visible in ps to other users |
| Logs and error messages | copied into aggregators with long retention |
Four ways to deliver them
- Environment from the orchestrator. Kubernetes
Secretas env, a systemdEnvironmentFileowned by root, Fly or Heroku config. Simple, widely supported. Visible in/proc/PID/environto the same user and root. - Mounted files. Docker secrets appear at
/run/secrets/name; Kubernetes can mount aSecretas a file; systemd'sLoadCredential=provides$CREDENTIALS_DIRECTORY/name. The application reads the file at startup. Permissions scope who can read it. - Secret manager API. AWS Secrets Manager, GCP Secret Manager or Vault, fetched at startup with the instance's identity. Central audit and rotation, and an extra dependency and failure mode at boot.
- Encrypted in the repository. SOPS or
ageencrypted files committed, decrypted at deploy. Works for small teams without a manager; the decryption key is the secret that remains.
Pick the simplest one your platform already offers. Do not build your own.
Reading a file secret in Rust
use std::{fs, io};
fn read_secret(name: &str) -> io::Result<String> {
let dir = std::env::var("CREDENTIALS_DIRECTORY").unwrap_or_else(|_| "/run/secrets".into());
Ok(fs::read_to_string(format!("{dir}/{name}"))?.trim().to_string())
}Trim the trailing newline that editors add. Treat a missing file as a startup error, as in the validation in the configuration post.
Keep it from leaking after it arrives
- Wrap values in a type with a redacting
Debug, or use thesecrecycrate, which also zeroes memory on drop. See handling secrets with zeroize and secrecy. - Do not log request headers wholesale;
AuthorizationandCookiecarry credentials. - Do not include a secret in an error message, including connection strings with passwords.
- Do not pass secrets as command line arguments to child processes.
Rotation
A secret that cannot be rotated without downtime will not be rotated. Support two active values at once where the protocol allows it: accept tokens signed with the old and new keys during a window, or re-read the file on SIGHUP. For database passwords, create the new user, switch, then delete the old one. Put rotation on a calendar and test the procedure; an untested rotation fails during an incident.
Catching mistakes early
Run a scanner such as gitleaks or trufflehog in CI and as a pre-commit hook. Set .gitignore for key files (git ignore for Rust projects). A leaked cloud key is abused within minutes by automated scanners, so assume exposure and rotate.
Limits
No delivery method protects against an attacker who has code execution as the service user; they can read what the service reads. The defenses reduce the chance of accidental disclosure and limit the blast radius. The OWASP Secrets Management Cheat Sheet is the primary reference.


