Sidecar Pattern
The sidecar pattern injects secrets into your app container as files on a shared volume. The app reads /secrets/DATABASE_URL like any config file. No SDK, no API calls, no code changes.
Why Use This Instead of Environment Variables?
| Approach | Pros | Cons |
|---|---|---|
Env vars (arcan kv run) | Simple, works everywhere | Visible in /proc, can't rotate without restart, leaked in crash dumps |
| ESO (K8s Secrets) | Native K8s, works with existing deployments | K8s Secrets are base64 (not encrypted), stored in etcd |
| Sidecar (files) | Rotatable without restart, never in etcd, 0400 permissions, audit-compliant | Requires volume mount, app must read from file |
Use sidecar when: your security team requires secrets as files (not env vars), you need rotation without restart, or your app already reads config from disk.
Real-World Examples
Example 1: Node.js app reading database credentials from files
Your Node.js app reads the database URL from a file instead of process.env:
// app.js — reads secret from file, watches for rotation
const fs = require('fs');
function getSecret(name) {
return fs.readFileSync(`/secrets/${name}`, 'utf8').trim();
}
// On startup
const dbUrl = getSecret('DATABASE_URL');
const pool = new Pool({ connectionString: dbUrl });
// Optional: watch for rotation (sidecar mode updates the file)
fs.watch('/secrets/DATABASE_URL', () => {
console.log('Secret rotated, reconnecting...');
const newUrl = getSecret('DATABASE_URL');
pool.end();
pool = new Pool({ connectionString: newUrl });
});
Generate the Pod spec:
arcan generate sidecar --realm=myapp --env=prod --mode=sidecar --interval=300 --image=myapp:latest
This produces a Pod with:
- Init container — fetches secrets before your app starts (so
DATABASE_URLexists at boot) - Sidecar container — checks every 5 minutes for rotated secrets, updates the file
- Your app container — reads from
/secrets/, watches for changes
Example 2: PgBouncer with auto-rotating credentials
PgBouncer reads a userlist.txt file for database credentials. When Arcan rotates the PostgreSQL password, the sidecar updates the file and PgBouncer picks it up on the next connection:
arcan generate sidecar --realm=database --env=prod --mode=sidecar --interval=60 --image=pgbouncer/pgbouncer:latest
In the generated Pod spec, PgBouncer mounts /secrets/ and reads:
# /secrets/PGBOUNCER_AUTH — written by Arcan sidecar
"arcan_user" "rotated_password_here"
PgBouncer config (pgbouncer.ini):
[pgbouncer]
auth_type = plain
auth_file = /secrets/PGBOUNCER_AUTH
Every 60 seconds, the sidecar checks if Arcan has rotated the credential. If the value changed, it writes the new file. PgBouncer re-reads on the next client connection. Zero downtime, zero restart.
Example 3: Nginx with TLS certificates from Arcan PKI
Arcan's PKI engine generates short-lived TLS certificates. The sidecar refreshes them before expiry:
arcan generate sidecar --realm=certs --env=prod --mode=sidecar --interval=3600 --image=nginx:latest
Nginx config:
server {
listen 443 ssl;
ssl_certificate /secrets/TLS_CERT;
ssl_certificate_key /secrets/TLS_KEY;
}
The sidecar writes new cert/key files every hour. Reload nginx with:
nginx -s reload
Or configure nginx to watch the files (OpenResty/lua-resty-auto-ssl pattern).
Example 4: Batch job with one-time injection
A data pipeline job needs AWS credentials to process S3 files. The init container injects them, the job runs, the pod terminates. Credentials are never stored on disk (tmpfs volume):
arcan generate sidecar --realm=pipeline --env=prod --mode=init --image=my-etl-job:latest
The job script reads:
#!/bin/sh
export AWS_ACCESS_KEY_ID=$(cat /secrets/AWS_ACCESS_KEY_ID)
export AWS_SECRET_ACCESS_KEY=$(cat /secrets/AWS_SECRET_ACCESS_KEY)
python3 process_data.py
When the pod terminates, the tmpfs volume (and the credentials) are gone.
How It Works
┌──────────────────────────────────────────────────┐
│ Kubernetes Pod │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────┐ │
│ │ Init: │ │ Sidecar: │ │ Your App │ │
│ │ arcan fetch │ │ arcan loop │ │ │ │
│ │ → write │ │ → check │ │ reads │ │
│ │ /secrets/ │ │ → update │ │ /secrets/│ │
│ └──────┬──────┘ └──────┬──────┘ └────┬─────┘ │
│ │ │ │ │
│ └────────────────┴───────────────┘ │
│ │ │
│ ┌───────┴────────┐ │
│ │ Shared Volume │ │
│ │ (tmpfs/Memory) │ │
│ │ /secrets/ │ │
│ │ DATABASE_URL │ │
│ │ API_KEY │ │
│ │ TLS_CERT │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────┘
- Init container runs first — fetches all secrets from Arcan, writes each as a file to
/secrets/ - Your app starts — reads files from
/secrets/(they're guaranteed to exist) - Sidecar (if enabled) — loops every N seconds, checks for changes, updates files in-place
- Shared volume is
emptyDir: { medium: Memory }— tmpfs, secrets never touch node disk
Modes
| Mode | Use Case | Command |
|---|---|---|
init | One-shot jobs, apps that don't need rotation | arcan generate sidecar --mode=init |
sidecar | Long-running services, credential rotation | arcan generate sidecar --mode=sidecar --interval=300 |
Command Reference
arcan generate sidecar [flags]
| Flag | Default | Description |
|---|---|---|
--realm, -r | (required) | Realm slug |
--env, -e | prod | Environment |
--namespace, -n | default | Kubernetes namespace |
--mode | init | Injection mode: init or sidecar |
--image | your-app:latest | Application container image |
--interval | 60 | Refresh interval in seconds (sidecar mode) |
--server-url | (auto-detected) | Arcan server URL |
--token-secret | arcan-credentials | K8s Secret holding the API token |
Security
- Secret files use
0400permissions (read-only by owner) - Shared volume backed by tmpfs (memory) — never written to node disk
- API token should have read-only scopes (
arcan token create --scopes read) - Use NetworkPolicy to restrict which Pods can reach the Arcan server
- In sidecar mode, only changed values are overwritten (diff check before write)