Platform, deploy pipeline
Step-by-step of the GitHub Actions deploy workflow
The platform/.github/workflows/deploy.yml workflow runs on every push to main (or on manual gh workflow run deploy.yml). It takes the compose configs and pushes them to the VPS.
High-level
1. Check out platform repo (the operator's fork).
2. Verify prod config (run verify-prod-config.yml gates inline).
3. Render .env.prod from GitHub Secrets.
4. SCP files to VPS.
5. SSH in, run `podman compose -f compose.prod.yml --env-file .env.prod up -d`.
6. Poll healthchecks for all services.
7. Report success / failure.End-to-end: ~3 minutes.
Step-by-step
Step 1: Checkout
Standard actions/checkout@v4. The deploy uses whatever's at main head.
Step 2: Verify prod config
Runs the same gates as verify-prod-config.yml:
- No literal secrets in compose / config files.
sslmode=verify-fullin everyDATABASE_URL.- Image references have digest pinning (
@sha256:...). - Kratos
leak_sensitive_values: false. - Email verification hook configured.
If any gate fails, the deploy aborts. No partial deploy state.
Step 3: Render .env.prod
The compose file references ${VAR} env vars. The deploy step injects them via a file:
cat > .env.prod <<EOF
ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }}
SESSION_SIGNING_KEY=${{ secrets.SESSION_SIGNING_KEY }}
CIAM_RELOAD_API_KEY=${{ secrets.CIAM_RELOAD_API_KEY }}
# ... and so on
EOFThis .env.prod is never committed. It's regenerated each deploy from the source of truth (GitHub Secrets).
Step 4: SCP
scp -i $SSH_KEY \
prod/compose.prod.yml prod/Caddyfile prod/*.{yml,json,jsonnet,sql,sh} \
prod/{ciam,iam}-{kratos,hydra}/*.{yml,json} \
.env.prod \
$DEPLOY_USER@$DEPLOY_HOST:/opt/olympus/The VPS now has a fresh copy.
Step 5: SSH + compose
ssh $DEPLOY_USER@$DEPLOY_HOST <<'EOF'
cd /opt/olympus
podman compose -f compose.prod.yml --env-file .env.prod pull
podman compose -f compose.prod.yml --env-file .env.prod up -d
podman compose -f compose.prod.yml --env-file .env.prod ps
EOFThree commands: pull updated images, start/restart, list status.
Step 6: Poll healthchecks
# Wait up to 2 min for everything to be healthy
for i in {1..24}; do
ALL_OK=true
for svc in ciam-kratos iam-kratos ciam-hydra iam-hydra ciam-athena iam-athena ciam-hera iam-hera site; do
health=$(ssh ... "podman compose ps --format json | jq -r '.[] | select(.Service==\"$svc\") | .Health'")
if [ "$health" != "healthy" ]; then ALL_OK=false; break; fi
done
[ "$ALL_OK" = "true" ] && break
sleep 5
doneIf any service is unhealthy after 2 min, the workflow fails. The operator gets a notification.
Step 7: Result
Success = green workflow run, healthy services. Failure = red run with the step that failed identified.
What's NOT in the pipeline
- Database migrations beyond what containers run themselves. Each Ory binary runs its own migrations on start.
- Cache warming. Athena's settings cache is per-process; cold start is fine.
- DNS changes. The pipeline assumes DNS already points at the VPS.
- Cert provisioning. Caddy fetches certs on its first start.
Rollback
To roll back:
- Revert the
compose.prod.ymldigest references to the previous values. - Commit and push.
- Deploy workflow runs, pulls the older images.
No database rollback unless you also restore a backup, Kratos/Hydra migrations are usually forward-only.
Deploy from another operator
The pipeline runs from a fork of OlympusOSS/platform. Each operator has their own fork with their own configs and secrets. There's no "shared deploy from canonical platform."
When upstream OlympusOSS/platform updates (e.g. new ADR-driven changes), operators merge from upstream:
git remote add upstream https://github.com/OlympusOSS/platform
git fetch upstream
git merge upstream/main
# Resolve conflicts with your customizations
git pushCI runs; their deploy includes the merge.
Manual deploy without GitHub Actions
# From local
scp prod/*.yml $DEPLOY_HOST:/opt/olympus/
ssh $DEPLOY_HOST 'cd /opt/olympus && podman compose -f compose.prod.yml --env-file .env.prod up -d'You become responsible for matching the workflow's gate semantics manually. Easier to use the workflow.