Olympus Docs
CookbookSecrets & encryption

Pull secrets from Vault at runtime

HashiCorp Vault integration for Olympus

For high-security deployments, secrets in .env files are insufficient. Use HashiCorp Vault (or similar) for runtime secret retrieval.

Why

  • Secrets never on disk at rest.
  • Centralized rotation.
  • Audit log of secret access.
  • Per-service / per-instance access tokens.

Architecture

Olympus services ── short-lived Vault token ──► Vault


                                        Secrets: DB password, encryption key, etc.

Each service authenticates to Vault, gets secrets, uses them.

Setup

# Run Vault (dev mode for testing only)
podman run -d -p 8200:8200 --name vault \
  -e VAULT_DEV_ROOT_TOKEN_ID=root-token \
  hashicorp/vault

For prod, follow Vault's deployment guide.

Store secrets

export VAULT_ADDR=https://vault.your-domain.com:8200
export VAULT_TOKEN=root-token

vault kv put secret/olympus/postgres password=mysecret
vault kv put secret/olympus/kratos cookie_secret=abc123
vault kv put secret/olympus/encryption_key value=base64bytes

App reads at startup

// entrypoint.ts
import vault from "node-vault";

const v = vault({ endpoint: process.env.VAULT_ADDR, token: process.env.VAULT_TOKEN });

const pgPassword = (await v.read("secret/data/olympus/postgres")).data.data.password;
const cookieSecret = (await v.read("secret/data/olympus/kratos")).data.data.cookie_secret;

process.env.POSTGRES_PASSWORD = pgPassword;
process.env.KRATOS_SECRETS_COOKIE = cookieSecret;

// Spawn actual app
import { spawn } from "child_process";
spawn("node", ["server.js"], { env: process.env, stdio: "inherit" });

Bootstrap pulls secrets, then runs app with env populated.

Or: Vault Agent

Vault Agent fetches secrets to disk:

# vault-agent.hcl
exit_after_auth = false
pid_file = "./pidfile"

auto_auth {
  method "kubernetes" {
    config = {
      role = "olympus"
    }
  }
}

template {
  source      = "./templates/postgres.tmpl"
  destination = "/secrets/postgres.env"
}
# postgres.tmpl
POSTGRES_PASSWORD={{ with secret "secret/olympus/postgres" }}{{ .Data.data.password }}{{ end }}

Agent maintains the file. App sources it.

Per-service auth

Each service has its own auth method:

vault auth enable approle

vault write auth/approle/role/kratos \
  policies=kratos-read \
  token_ttl=1h \
  token_max_ttl=24h

Kratos gets a role_id + secret_id. Trades for a token. Token expires; refresh periodically.

Service code:

const roleId = process.env.VAULT_ROLE_ID;
const secretId = process.env.VAULT_SECRET_ID;
const auth = await v.approleLogin({ role_id: roleId, secret_id: secretId });
v.token = auth.auth.client_token;

Policies

Per-role policy:

# kratos-read policy
path "secret/data/olympus/kratos/*" {
  capabilities = ["read"]
}

path "secret/data/olympus/encryption_key" {
  capabilities = ["read"]
}

Kratos can read its secrets. Can't write. Can't read other services' secrets.

Principle of least privilege.

Rotation

When rotating a secret:

vault kv put secret/olympus/postgres password=NEWVALUE

App: how does it know?

Option A: poll

setInterval(async () => {
  const current = (await v.read("secret/data/olympus/postgres")).data.data.password;
  if (current !== POSTGRES_PASSWORD) {
    POSTGRES_PASSWORD = current;
    // Reconnect DB
  }
}, 60_000);

Every minute, check. Fast detection.

Option B: webhook

Vault sends webhook on update → app reloads.

Option C: restart

After rotation, restart services. Simple. Brief downtime.

For most: option C is fine.

Dynamic secrets

Vault can generate one-time credentials (DB passwords valid 24h):

vault read database/creds/olympus-reader

Returns fresh DB user + password. After TTL, revoked.

Reduces blast radius further. No long-lived DB passwords.

Disaster recovery

What if Vault is down?

  • Services already running: continue with cached secrets.
  • New service starting: can't read secrets → fail.

Mitigation:

  • Use Vault HA (3+ instances).
  • Cache secrets to local file (TTL'd).
  • Fall back to env vars during outage.

Sealing

Vault is "sealed" at startup. Requires unsealing via key shares (Shamir's).

For HA: auto-unseal via cloud KMS:

seal "awskms" {
  region = "us-east-1"
  kms_key_id = "..."
}

Vault unseals automatically using AWS KMS key. Operationally simpler than manual key shares.

Audit

Vault logs all access:

2026-05-13T15:00:00Z [INFO] vault: response: client_id=olympus-kratos path=secret/data/olympus/postgres

Trail of "who read what when." For compliance.

Other secret managers

Same patterns work for:

  • AWS Secrets Manager.
  • GCP Secret Manager.
  • Azure Key Vault.
  • Doppler.

Library calls differ; concepts identical.

When to use Vault

  • Multiple services need shared secrets.
  • Compliance demands key audit trail.
  • Frequent rotation.
  • Per-service / per-pod credentials.

Smaller deployments: .env file is fine.

Migration

From .env to Vault:

  1. Stand up Vault.
  2. Move secrets to Vault.
  3. Update services to read from Vault at startup.
  4. Remove from .env (encrypted backup of .env, keep nowhere accessible).
  5. Verify everything works.
  6. Decommission old practice.

Don't rush. Test.

On this page