Documentation menu

How Atrium works

A picture-first tour of Atrium's architecture — the launcher, the directory, the runtime, and how identity flows through the whole thing.

Atrium has three jobs: it's the front door to your products (the launcher), the directory of who can use what (the admin surface), and the automation layer between Zitadel and your downstream tools (the Actions runtime + SCIM connector). This page walks through how those pieces work together so you can explain it to a colleague over coffee.

The 30-second view

Atrium is the hub. Zitadel owns identity. The runtime is a sidecar that handles webhooks so Atrium itself can be down for maintenance without breaking your sign-in flow. Downstreams get provisioned either by SCIM (when they speak the standard) or by Actions scripts (when they don't).

Three principles

1. Zitadel is the source of truth for identity. Atrium never invents users. Every account, every email, every SSO link lives in Zitadel — Atrium reads from it. The only mutations Atrium makes are explicit ones: an admin approves an access request, a group reconciles, an admin flips an app's "public" flag.

2. The directory is the source of truth for access. Atrium maps your apps to Zitadel projects, your teams to Atrium groups, and your access policies to which group grants which app. The reconciler keeps Zitadel grants in sync with Atrium's policy.

3. Automation runs in a separate process. The Actions runtime is its own service so a maintenance window or a long script don't ripple into the admin UI or the launcher.

Identity flow on first sign-in

The AtriumUser row is small on purpose — just the bits Atrium needs locally (request history, dismissed onboarding tips, audit trail). Anything that's actually about the user — name, email, department, group memberships in Entra — stays in Zitadel and gets read on demand.

Group-driven access

Atrium's policy model has three primitives:

Groups bundle two things at once: who (members) and what they can do (apps × role-keys). When an admin adds a user to a group, the user inherits every app + role-key that group grants.

The reconciler then converts that Atrium-side picture into Zitadel-side user_grant rows so the access actually takes effect. If you remove a user from the group, the reconciler removes the grants on the next sweep.

The reconciler

Three triggers fire reconciliation: a group operation (member added/removed, grant mapping changed), the scheduled cron, and the manual "Detect drift" button on /admin/groups. The reconciler is fully idempotent — it can fire ten times in a row with no side-effect once state has converged.

Webhook-driven automation

When something happens in Zitadel (a user is created, a grant is added, a session is opened), Zitadel can fire a webhook. Atrium routes those webhooks to the Actions runtime — a separate Fastify service that runs admin-authored JavaScript in a sandbox.

Five things to know:

  • Signed payloads — Zitadel signs every webhook with HMAC-SHA256. The runtime rejects unsigned or wrong-signature requests at the door.
  • Per-script worker — every invocation gets its own worker thread. A misbehaving script can't poison the next call.
  • 5-second hard deadline — the worker is killed if a script runs too long. Sign-ins keep moving.
  • Curated ctx — the script can call ctx.fetch, ctx.zitadel.*, ctx.atrium.groups.*, read ctx.secrets.NAME. No process, no require, no global fetch.
  • Push-on-save — when an admin promotes a script to ACTIVE in Atrium, Atrium pushes the source + the secret values to the runtime over an internal channel. The runtime caches both in memory.

That separation — Atrium owns the script catalog in Postgres, the runtime owns the execution — means Atrium maintenance never breaks Zitadel webhook delivery.

Where SCIM fits

SCIM is one kind of automation: provisioning users into downstream services that speak the standard protocol. For everything else (Dokploy, internal tools), an Action does the same job with full flexibility. See SCIM provisioning for the deep dive on the SCIM side.

Where to go from here