HashiCorp Vault — Sidecar Pattern
The sidecar pattern deploys Vault Agent alongside your application container, handling authentication and secret delivery through shared volumes.
Why Sidecar?
| Approach | Pros | Cons |
|---|---|---|
| App-native (SDK) | Fine-grained control | Code changes, vendor lock-in |
| Init container | Simple, one-time fetch | No renewal or rotation |
| Sidecar (Agent) | Auto-renewal, no code changes | Extra container resource usage |
Architecture
┌─────────────────────────────────────────┐
│ Pod │
│ │
│ ┌──────────────┐ ┌───────────────┐ │
│ │ Vault Agent │ │ Application │ │
│ │ (sidecar) │ │ │ │
│ │ │ │ Reads: │ │
│ │ Renders: │──▶│ /vault/ │ │
│ │ secrets to │ │ secrets/ │ │
│ │ shared vol │ │ config.env │ │
│ └──────────────┘ └───────────────┘ │
│ │ │
│ Shared Volume │
│ (emptyDir or tmpfs) │
└─────────────────────────────────────────┘
Kubernetes Sidecar Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 2
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
serviceAccountName: myapp
# Init container: authenticate and render initial secrets
initContainers:
- name: vault-agent-init
image: hashicorp/vault:1.15
args:
- "agent"
- "-config=/etc/vault/vault-agent.hcl"
- "-exit-after-auth"
volumeMounts:
- name: vault-config
mountPath: /etc/vault
- name: vault-secrets
mountPath: /vault/secrets
containers:
# Sidecar: keep secrets refreshed
- name: vault-agent
image: hashicorp/vault:1.15
args:
- "agent"
- "-config=/etc/vault/vault-agent.hcl"
volumeMounts:
- name: vault-config
mountPath: /etc/vault
- name: vault-secrets
mountPath: /vault/secrets
resources:
requests:
memory: "64Mi"
cpu: "50m"
limits:
memory: "128Mi"
cpu: "100m"
# Application container
- name: myapp
image: myapp:latest
volumeMounts:
- name: vault-secrets
mountPath: /vault/secrets
readOnly: true
env:
- name: DB_PASSWORD_FILE
value: /vault/secrets/db-password
volumes:
- name: vault-config
configMap:
name: vault-agent-config
- name: vault-secrets
emptyDir:
medium: Memory # tmpfs — secrets never hit disk
Agent ConfigMap
apiVersion: v1
kind: ConfigMap
metadata:
name: vault-agent-config
data:
vault-agent.hcl: |
vault {
address = "http://vault.vault.svc.cluster.local:8200"
}
auto_auth {
method "kubernetes" {
mount_path = "auth/kubernetes"
config = {
role = "myapp"
}
}
sink "file" {
config = {
path = "/vault/secrets/.vault-token"
}
}
}
template {
contents = "{{ with secret \"secret/data/myapp/db\" }}{{ .Data.data.password }}{{ end }}"
destination = "/vault/secrets/db-password"
}
template {
contents = "{{ with secret \"secret/data/myapp/config\" }}DB_HOST={{ .Data.data.host }}\nDB_PORT={{ .Data.data.port }}\nDB_USER={{ .Data.data.username }}\nDB_PASS={{ .Data.data.password }}{{ end }}"
destination = "/vault/secrets/app.env"
}
exit_after_auth = false
Docker Compose Sidecar
version: "3.8"
services:
vault-agent:
image: hashicorp/vault:1.15
command: ["vault", "agent", "-config=/etc/vault/agent.hcl"]
volumes:
- ./vault-agent.hcl:/etc/vault/agent.hcl:ro
- ./role-id:/etc/vault/role-id:ro
- ./secret-id:/etc/vault/secret-id:ro
- shared-secrets:/vault/secrets
environment:
- VAULT_ADDR=http://vault:8200
myapp:
image: myapp:latest
depends_on:
- vault-agent
volumes:
- shared-secrets:/vault/secrets:ro
environment:
- DB_PASSWORD_FILE=/vault/secrets/db-password
volumes:
shared-secrets:
driver_opts:
type: tmpfs
device: tmpfs
Application Reading Secrets
# Python — read secret from file
import os
def read_secret(path):
"""Read a secret from a file rendered by Vault Agent."""
with open(path, 'r') as f:
return f.read().strip()
db_password = read_secret('/vault/secrets/db-password')
// Go — read secret from file
package main
import (
"os"
"strings"
)
func readSecret(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return strings.TrimSpace(string(data)), nil
}
Quick Reference
| Pattern | Use Case |
|---|---|
| Init container only | One-time secret fetch, no renewal |
| Sidecar only | Continuous renewal and rotation |
| Init + Sidecar | Initial secrets ready before app starts, then continuous |
Next: Agent Injector