Skip to main content

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?

ApproachProsCons
Env vars (arcan kv run)Simple, works everywhereVisible in /proc, can't rotate without restart, leaked in crash dumps
ESO (K8s Secrets)Native K8s, works with existing deploymentsK8s Secrets are base64 (not encrypted), stored in etcd
Sidecar (files)Rotatable without restart, never in etcd, 0400 permissions, audit-compliantRequires 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_URL exists 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 │ │
│ └────────────────┘ │
└──────────────────────────────────────────────────┘
  1. Init container runs first — fetches all secrets from Arcan, writes each as a file to /secrets/
  2. Your app starts — reads files from /secrets/ (they're guaranteed to exist)
  3. Sidecar (if enabled) — loops every N seconds, checks for changes, updates files in-place
  4. Shared volume is emptyDir: { medium: Memory } — tmpfs, secrets never touch node disk

Modes

ModeUse CaseCommand
initOne-shot jobs, apps that don't need rotationarcan generate sidecar --mode=init
sidecarLong-running services, credential rotationarcan generate sidecar --mode=sidecar --interval=300

Command Reference

arcan generate sidecar [flags]
FlagDefaultDescription
--realm, -r(required)Realm slug
--env, -eprodEnvironment
--namespace, -ndefaultKubernetes namespace
--modeinitInjection mode: init or sidecar
--imageyour-app:latestApplication container image
--interval60Refresh interval in seconds (sidecar mode)
--server-url(auto-detected)Arcan server URL
--token-secretarcan-credentialsK8s Secret holding the API token

Security

  • Secret files use 0400 permissions (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)