Olympus Docs
InternalsPlatform

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-full in every DATABASE_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
EOF

This .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
EOF

Three 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
done

If 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:

  1. Revert the compose.prod.yml digest references to the previous values.
  2. Commit and push.
  3. 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 push

CI 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.

On this page