Policy files in git (config-as-code)
Treat Olympus config like code
Olympus has many config files: kratos.yml, hydra.yml, Caddyfile, identity schemas, policies (Cedar / OPA), email templates. Treat them as code in a git repo.
Repo layout
olympus-config/
├── kratos.yml
├── hydra.yml
├── identity-schemas/
│ ├── default.schema.json
│ └── customer-x.schema.json
├── courier-templates/
│ └── ...
├── policies/
│ ├── cedar/
│ │ └── orders.cedar
│ └── opa/
│ └── billing.rego
├── caddy/
│ └── Caddyfile
└── .github/workflows/
└── deploy.ymlVersion-controlled. Reviewable. Reproducible.
Validation
CI checks each commit:
# .github/workflows/validate.yml
name: Validate config
on: [push]
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate Kratos config
run: docker run -v $PWD:/config oryd/kratos:v1.4.0 kratos validate config /config/kratos.yml
- name: Validate Hydra config
run: docker run -v $PWD:/config oryd/hydra:v2.2.0 hydra validate /config/hydra.yml
- name: Validate identity schemas
run: |
for schema in identity-schemas/*.json; do
ajv validate -s "$schema" -d test-data/sample.json
done
- name: Lint Cedar policies
run: |
for p in policies/cedar/*.cedar; do
cedar lint --policies "$p"
done
- name: Caddy syntax check
run: docker run -v $PWD:/config caddy:2 caddy validate --config /config/caddy/CaddyfileCatch errors before they hit prod.
Deploy
On merge to main:
- name: Deploy config
run: |
rsync -av config/ deploy@$HOST:/opt/olympus/config/
ssh deploy@$HOST 'cd /opt/olympus && podman-compose restart'Or with finer control:
- name: Deploy and reload
run: |
rsync -av config/kratos.yml deploy@$HOST:/opt/olympus/config/
ssh deploy@$HOST 'podman-compose restart ciam-kratos'Only restarts service whose config changed.
Diff-based deploys
For multiple files, only deploy changed:
- name: Detect changes
run: |
changed_files=$(git diff --name-only HEAD~1)
if echo "$changed_files" | grep -q "kratos.yml"; then
KRATOS_CHANGED=1
fi
# ... per file
- if: env.KRATOS_CHANGED == '1'
run: ssh deploy "cd /opt/olympus && podman-compose restart ciam-kratos"Faster, less disruptive.
Secret separation
Don't commit secrets. Use templating:
# kratos.yml.template
secrets:
cookie:
- ${KRATOS_SECRETS_COOKIE}
cipher:
- ${KRATOS_SECRETS_CIPHER}At deploy time:
envsubst < kratos.yml.template > kratos.ymlOr use Helm-like tools (kustomize, jsonnet, etc.).
For secret storage:
- Vault.
- AWS Secrets Manager.
- Doppler.
# Pull secrets at deploy
doppler run -- envsubst < kratos.yml.template > kratos.ymlEnvironment branches
Different config per env:
git branches:
main → prod
staging → staging
dev → devMerging: dev → staging → main (promotion).
Or files:
config/
├── base/
├── dev/
├── staging/
└── prod/Build script picks based on --env.
Review workflow
Pull request for every change:
1. Engineer makes config change.
2. PR opened.
3. CI validates (Kratos / Hydra accepts the config).
4. Reviewer approves.
5. Merge → deploy.Bad config blocked at CI. Don't deploy then debug.
Per-tenant config
For multi-tenant white-label, config per tenant:
config/
├── tenants/
│ ├── acme/
│ │ ├── branding.json
│ │ └── identity-schema.json
│ └── bigcorp/
│ └── ...Deploy specific tenant:
./scripts/deploy-tenant.sh acmeUpdates only Acme's pocket. Doesn't disturb others.
Versioning configs
Tag releases:
git tag config-v1.4.0 -m "Production config v1.4.0"
git push --tagsRollback to tag:
git checkout config-v1.3.5
./scripts/deploy.shInstant revert to known-good.
Drift detection
Periodically check that deployed config matches git:
# Cron daily
diff <(ssh prod cat /opt/olympus/config/kratos.yml) ./config/kratos.ymlIf different: someone made a manual change. Investigate.
Or use Atlantis / similar for "are we in sync?"
Multiple environments tested
Each env runs CI:
deploy_dev:
needs: validate
steps: [...]
deploy_staging:
needs: deploy_dev
steps: [...]
deploy_prod:
needs: deploy_staging
environment: prod-approval # manual gate
steps: [...]Promotion through environments. Each stable for hours/days before next.
Audit
Git history is the audit:
git log --follow kratos.yml
# Each change traceable.Pair with deploy logs:
INSERT INTO config_deploys (env, commit_sha, files_changed, deployed_at, deployed_by)
VALUES ('prod', 'abc123', ['kratos.yml'], NOW(), 'github-actions');Trace "when was MFA enforced for admins?" → audit log + git.
Failures
If deploy fails partway:
- name: Deploy
run: ssh prod "..."
- if: failure()
run: |
ssh prod "cd /opt/olympus && git checkout HEAD~1 && podman-compose restart"
slack-notify "Deploy failed, reverted"Auto-revert on failure. Investigate, fix, redeploy.
Documentation in repo
Each non-trivial change → comment in commit or separate doc:
# decisions/2026-05-13-enable-passkey.md
Decision: enable WebAuthn / passkeys for all users.
Why:
- Phishing-resistant.
- Reduces password fatigue.
Trade-offs:
- Some users won't enroll (free choice).
- Older browsers don't support.
Mitigation: keep password as fallback.
Approver: Bobby
Date: 2026-05-13Decision log alongside code.