Modern Kubernetes workloads often rely on databases for stateful operations, but many deployments still use static database passwords stored in environment variables, configuration files, or Kubernetes Secrets. Static credentials introduce major security risks: they can be leaked, reused indefinitely, and are difficult to rotate consistently across distributed workloads.
A more secure approach is to replace static credentials with dynamic, ephemeral database credentials generated on demand. By using HashiCorp Vault with sidecar injection, Kubernetes workloads can automatically receive short-lived credentials without developers manually managing secrets.
This article explains how to replace static passwords with dynamic credentials using Vault and sidecar injection, including architecture design, configuration steps, and coding examples.
Why Static Passwords Are Dangerous
Static credentials create several security problems:
1. Long-lived exposure
If a password leaks, attackers can use it indefinitely until it is manually rotated.
2. Manual rotation
Rotating database credentials across microservices is slow and error-prone.
3. Secret sprawl
Passwords often end up in:
-
- Git repositories
- CI/CD pipelines
- Environment variables
- Configuration files
4. Lack of auditing
It is difficult to track which service used a credential.
Dynamic credentials solve these problems by generating temporary access tokens tied to a workload identity.
What Are Dynamic Ephemeral Credentials
Dynamic credentials are automatically generated usernames and passwords with:
-
- Short Time-To-Live (TTL)
- Automatic expiration
- Automatic revocation
- Least-privilege permissions
Instead of storing:
DB_USER=appuser
DB_PASSWORD=SuperSecret123
Vault generates credentials like:
username: v-token-readonly-abc123
password: KJhsd7s9s8sd98s
ttl: 1 hour
After expiration, the credentials are automatically revoked.
Architecture Overview
The solution involves four main components:
-
- Kubernetes Pod
- Vault Server
- Vault Agent Sidecar
- Database
Flow:
-
- Pod starts
- Vault sidecar authenticates using Kubernetes ServiceAccount
- Vault generates dynamic credentials
- Sidecar writes credentials to a shared volume
- Application reads credentials
- Credentials rotate automatically
Example architecture:
Application Container
|
Shared Volume (/vault/secrets)
|
Vault Agent Sidecar
|
Vault Server
|
Database
Installing Vault on Kubernetes
Vault can be installed using Helm:
helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault hashicorp/vault \
--set "server.dev.enabled=true"
For production:
helm install vault hashicorp/vault \
--set "injector.enabled=true"
This enables the Vault Agent Injector which automatically injects sidecars.
Enabling Kubernetes Authentication
Vault must trust Kubernetes.
Enable auth method:
vault auth enable kubernetes
Configure Kubernetes auth:
vault write auth/kubernetes/config \
token_reviewer_jwt="$SA_JWT" \
kubernetes_host="$KUBE_HOST" \
kubernetes_ca_cert="$KUBE_CA_CERT"
This allows Vault to authenticate Pods.
Configuring the Database Engine
Enable the database secrets engine:
vault secrets enable database
Example configuration for PostgreSQL:
vault write database/config/postgres \
plugin_name=postgresql-database-plugin \
allowed_roles="app-role" \
connection_url="postgresql://{{username}}:{{password}}@postgres:5432/appdb?sslmode=disable" \
username="vaultadmin" \
password="vaultadminpassword"
Vault uses the admin account to generate users dynamically.
Creating Dynamic Credential Roles
Define a role that generates credentials:
vault write database/roles/app-role \
db_name=postgres \
creation_statements="
CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';
GRANT SELECT,INSERT,UPDATE ON ALL TABLES IN SCHEMA public TO \"{{name}}\";
" \
default_ttl="1h" \
max_ttl="24h"
This role:
-
- Creates database users
- Sets expiration
- Limits privileges
Creating Vault Policies
Create policy:
path "database/creds/app-role" {
capabilities = ["read"]
}
Apply policy:
vault policy write app-policy app-policy.hcl
Creating Kubernetes Roles
Bind Kubernetes ServiceAccount to Vault role:
vault write auth/kubernetes/role/app-role \
bound_service_account_names=app-sa \
bound_service_account_namespaces=default \
policies=app-policy \
ttl=1h
Now Pods using app-sa can request credentials.
Configuring Sidecar Injection
Vault Agent Injector uses Pod annotations.
Example Pod YAML:
apiVersion: v1
kind: Pod
metadata:
name: app-pod
annotations:
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/role: "app-role"
vault.hashicorp.com/agent-inject-secret-db: "database/creds/app-role"
spec:
serviceAccountName: app-sa
containers:
- name: app
image: myapp:latest
Vault automatically injects a sidecar.
Rendering Credentials to Files
Add template annotation:
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/app-role" -}}
username={{ .Data.username }}
password={{ .Data.password }}
{{- end }}
Vault writes file:
/vault/secrets/db
Example contents:
username=v-token-app123
password=SDs8sdf78sdf
Application Code Example (Python)
Example application reading credentials dynamically:
import psycopg2
import time
def read_credentials():
with open('/vault/secrets/db') as f:
lines = f.readlines()
creds = {}
for line in lines:
key, value = line.strip().split('=')
creds[key] = value
return creds
while True:
creds = read_credentials()
conn = psycopg2.connect(
host="postgres",
database="appdb",
user=creds['username'],
password=creds['password']
)
cur = conn.cursor()
cur.execute("SELECT NOW()")
print(cur.fetchone())
conn.close()
time.sleep(60)
The application automatically picks up rotated credentials.
Automatic Credential Rotation
Vault Agent renews leases automatically.
When credentials expire:
-
- Vault generates new credentials
- Sidecar updates file
- Application reloads credentials
No manual intervention required.
Example TTL settings:
default_ttl = "1h"
max_ttl = "24h"
Short TTL improves security.
Advanced Template Configuration
More advanced template:
vault.hashicorp.com/agent-inject-template-db: |
{{- with secret "database/creds/app-role" -}}
{
"username": "{{ .Data.username }}",
"password": "{{ .Data.password }}",
"ttl": "{{ .LeaseDuration }}"
}
{{- end }}
Generated file:
{
"username": "v-app-123",
"password": "sdkfjsd8fs",
"ttl": "3600"
}
JSON format simplifies parsing.
Handling Credential Reloading
Applications should reload credentials safely.
Example (Node.js):
const fs = require('fs');
const { Client } = require('pg');
function getCredentials() {
const content = fs.readFileSync('/vault/secrets/db', 'utf8');
const lines = content.split('\n');
let creds = {};
lines.forEach(line => {
if(line){
let parts = line.split('=');
creds[parts[0]] = parts[1];
}
});
return creds;
}
async function queryDB() {
const creds = getCredentials();
const client = new Client({
host: 'postgres',
database: 'appdb',
user: creds.username,
password: creds.password
});
await client.connect();
const res = await client.query('SELECT NOW()');
console.log(res.rows);
await client.end();
}
setInterval(queryDB, 60000);
This approach ensures continuous operation.
Security Benefits
Dynamic credentials improve security significantly.
1. Zero hardcoded secrets
No credentials stored in:
-
- Git
- Images
- ConfigMaps
2. Automatic rotation
Passwords rotate without downtime.
3. Fine-grained access
Each Pod gets unique credentials.
4. Automatic revocation
When Pod terminates:
-
- Lease revoked
- User deleted
5. Audit logging
Vault logs:
-
- Credential generation
- Usage
- Revocation
Production Best Practices
Use short TTL values
Recommended:
15m – 1h
Short lifetimes reduce risk.
Use least privilege roles
Avoid:
GRANT ALL PRIVILEGES
Prefer:
GRANT SELECT,INSERT
Enable TLS everywhere
Secure:
-
- Vault communication
- Database connections
- Kubernetes API
Separate roles per service
Example:
orders-service-role
billing-service-role
analytics-service-role
This limits blast radius.
Use Readiness Probes
Ensure application waits for credentials:
readinessProbe:
exec:
command:
- cat
- /vault/secrets/db
Common Pitfalls
1. Credential caching
Applications sometimes cache credentials forever.
Always reload periodically.
2. Connection pools
Pools may hold expired credentials.
Solutions:
-
- Pool refresh
- TTL-aware pools
3. Vault rate limits
Large clusters may overload Vault.
Solutions:
-
- Horizontal scaling
- Performance standby nodes
- Caching agent
4. Missing renewals
If the sidecar dies:
-
- Credentials expire
- App fails
Use:
restartPolicy: Always
Migration Strategy from Static Secrets
Step-by-step approach:
Step 1
Keep static secrets but introduce Vault.
Step 2
Test dynamic credentials in staging.
Step 3
Switch production gradually.
Step 4
Remove Kubernetes Secrets.
Comparison: Static vs Dynamic Credentials
| Feature | Static | Dynamic |
|---|---|---|
| Rotation | Manual | Automatic |
| TTL | Unlimited | Configurable |
| Revocation | Manual | Automatic |
| Security | Medium | High |
| Auditability | Low | High |
Dynamic credentials clearly provide superior security.
Conclusion
Replacing static database passwords with dynamic, ephemeral credentials using Vault and sidecar injection represents one of the most impactful security upgrades for Kubernetes environments. Static credentials are inherently fragile because they create long-lived trust relationships that attackers can exploit. Once exposed, static passwords often remain valid for weeks or months, dramatically increasing the risk of unauthorized database access.
Dynamic credentials fundamentally change this model by introducing short-lived, automatically generated identities tied directly to Kubernetes workloads. Each Pod receives its own unique database user with precisely scoped permissions and a limited lifetime. Even if credentials are compromised, they quickly expire and become useless, significantly reducing the attack window.
The integration of Vault Agent sidecars makes this system practical and scalable. Instead of requiring developers to implement complex authentication flows, sidecar injection provides a transparent mechanism for secret delivery. Applications simply read credentials from files, while Vault handles authentication, rotation, and revocation behind the scenes. This separation of concerns allows development teams to focus on business logic while security policies are centrally enforced.
Operationally, this architecture improves reliability as well as security. Automated credential rotation eliminates the risks associated with manual password updates and reduces the chance of outages caused by expired secrets. Fine-grained Vault policies ensure that each microservice receives only the access it requires, implementing a strong least-privilege model across the cluster.
From a compliance perspective, Vault introduces comprehensive audit logging and traceability. Security teams gain visibility into when credentials are issued, which workloads use them, and when they expire. This level of accountability is extremely difficult to achieve with traditional Kubernetes Secrets or environment-variable-based credentials.
Sidecar-based secret injection also aligns naturally with Kubernetes design principles. The ephemeral nature of Pods matches the short-lived nature of dynamic credentials, creating a consistent and secure lifecycle for both compute and identity resources.
In modern cloud-native environments, database credentials should no longer be treated as static configuration. Instead, they should be dynamic infrastructure components generated on demand and destroyed automatically. Vault and sidecar injection provide a mature and production-proven approach for achieving this goal.
Organizations adopting this pattern gain:
-
- Stronger zero-trust security
- Reduced secret exposure
- Automated credential lifecycle management
- Improved auditability
- Simplified operations
- Better compliance posture
As Kubernetes deployments continue to scale, the use of dynamic ephemeral credentials will become the standard approach for securing databases and other sensitive services. Implementing Vault with sidecar injection today positions organizations for a future where no static secrets exist inside containerized workloads, dramatically reducing one of the most common and dangerous attack vectors in cloud-native infrastructure.