Design Patterns of Component Interaction in openEduSuite
A platform like openEduSuite is not a single product but an ecosystem of services: groupware, file storage, LMS, video conferencing, identity, email. How these parts talk to each other decides the platform's usefulness. This post describes the recurring design patterns of component interaction; every claim is backed by evidence in the repository.
The patterns are not openEduSuite-specific; they answer a concrete question: how do you make many independent β sometimes externally maintained β open-source services cooperate in a way that works like one product? Four principles carry the answers: one source of truth for identity, one for configuration, DNS as the contract instead of IP addresses, and explicit interfaces between namespaces.
1. The SSO chain as an integration pattern
The foundation of all component interaction is a single source of identity. In openEduSuite that role belongs to Keycloak, acting as the central identity broker: it speaks SAML 2.0 toward the federation and OpenID Connect (OIDC) toward the platform's own services. This dual role is the architectural key β it lets the platform speak to the German higher-education identity federation DFN-AAI (and through it eduGAIN) on the outside, while every internal service sees one modern, uniform interface. The full architecture is documented in content/de/architecture/identity-authentication.md.
What matters is the direction of the chain: the user authenticates exactly once at their home IdP, Keycloak issues an OIDC token, and each service validates only that token. No service stores a second password; none of them knows the federation directly.
Two protocol paths have emerged for onboarding a service:
- Native OIDC for services with built-in OIDC support (Nextcloud, OpenProject, JupyterHub via
GenericOAuthenticator, Open WebUI), pointing directly at the Keycloak endpoints β documented in the collab-services code underhelmfile/and in the articlecontent/de/blog/collab-services-scientific-tools.md. - The oauth2-proxy sidecar for services without native OIDC capability. A reverse-proxy sidecar is injected into the same pod, intercepts HTTP traffic on port 4180, redirects unauthenticated users to Keycloak, and proxies authenticated requests to the local application container. This pattern serves RStudio, ttyd, Slidev, and the Collab Dashboard.
The sidecar approach is the pragmatic path to bring a legacy or minimal application into the SSO ecosystem without rewriting it: authentication moves to the edge of the pod instead of into the application. Details of the chain service β oauth2-proxy β Keycloak β SAML IdP are in the "Single Sign-On architecture" section of the collab-services article.
For operational handling of this chain's secrets, Section 4's GitOps principle applies: OIDC client secrets are treated like every other critical credential and never belong in Git in plaintext (see content/de/blog/sops-secret-management-argocd-cmp.md, priority P1).
2. The reverse proxy as a fractal pattern
A second conspicuous pattern is the reverse proxy as a fractal: at every level of the system you find the same mechanism β a proxy or ingress that routes traffic by Host header to the next service β only at a different scale.
At the outermost level, an ingress controller terminates TLS and routes all hostnames through a single entry IP. The traffic flow is documented in content/de/architecture/networking-traffic-flow.md: the browser resolves a hostname such as cloud.example.edu, the ingress inspects the Host header and forwards to the matching Kubernetes service. Exactly one IP address serves the whole platform β classic virtual hosting.
Inside the deployed clusters this job is often handled by an HAProxy-based ingress in front of a MetalLB VIP (documented in content/de/blog/self-hosted-campus-k3s.md), while Traefik serves specific HTTP/HTTPS scenarios in parallel. The point is not the concrete choice but the pattern: many domains, one entry point.
At the inner level the same pattern repeats as sidecar or alias: services like Slidev or the Collab Dashboard are served by a small nginx container, with the oauth2-proxy sidecar sitting as yet another mini-proxy directly in front (see the custom charts in content/de/blog/collab-services-scientific-tools.md). Some teams additionally use per-service nginx-style alias redirects that map a short canonical hostname to a longer one β practically the fractal turned inward. Where such redirects (say via HTTP 301) exist, they follow the same core principle: define a single canonical host and point everything else at it consistently, instead of maintaining per-service URL schemes.
The recurring value: the reverse proxy is the one place where the hostname β service mapping is managed centrally, rather than scattered across a hundred application configs.
3. Service discovery via DNS instead of hard-coded IPs
Kubernetes services get a stable, cluster-internal FQDN convention (<service>.<namespace>.svc.cluster.local) that serves as the contract between components: one component never references another via a floating IP, only via this DNS name. The namespace is part of the name, which makes it explicit where a component lives.
The documented pattern avoids hard-coded addresses in code. The boundary shows up dramatically when DNS breaks: the community-of-practice notes record a DNS CNAME chain that triggered a SERVFAIL in CoreDNS and had to be bridged temporarily with hostAliases β i.e., not with the ephemeral IP, but with an equivalent inside the pod. The lesson is twofold: cluster-internal services reference each other via the DNS FQDN; and when DNS is sick, hostAliases are a diagnostic and stopgap tool, never a permanent solution, because they hard-code information that is otherwise managed centrally.
For services that must be reachable through the ingress, the contract is typically a wildcard DNS registration to the ingress IP (see networking-traffic-flow.md), so a new service needs only an ingress and a DNS record β no further network re-wiring.
4. GitOps: configuration as code as the single source of truth
The fourth pattern is less a protocol than a discipline: Git is the single source of truth for cluster state. ArgoCD reconciles declaratively against the repository; a change is a commit, drift is visible in the ArgoCD UI. The assessment is elaborated in content/de/blog/gitops-argocd-app-of-apps-applicationset.md, with practical application in content/de/blog/self-hosted-campus-k3s.md ("the cluster is a build artifact").
Two configuration types behave differently:
- Secrets never belong in Git in plaintext. Critical keys such as OIDC client secrets or LDAP bind credentials are committed encrypted with SOPS and age as
.enc.yaml, and decrypted only at deploy time via an ArgoCD config-management-plugin sidecar. This architecture is fully described incontent/de/blog/sops-secret-management-argocd-cmp.mdand separates encryption from decryption in both time and place. - ConfigMaps are the truth for non-secret configuration. The pattern: update the ConfigMap β
applyβ trigger a pod rollout. The catch is learned in practice: not every application reloads a changed ConfigMap at runtime. Some (web servers and groupware sidecars among them) keep serving the old state until the pod restarts. Checksum annotations or an explicit rollout therefore belong in the workflow β otherwise the running configuration "lies" against the truth stored in Git. This is exactly the lesson from the self-hosting article: environment variables likeNEXTCLOUD_TRUSTED_DOMAINSconfigure only the installer, not the running application β immutable install parameters must not be confused with living, host-dependent configuration.
A realistic corrective: ArgoCD "lies" wherever parts of the system are managed outside Git (ad-hoc kubectl changes or nix-managed state). Drift handling is thus not merely cosmetic but a security concern β an acknowledged weak spot.
5. Mail and directory interaction
The mail integration shows how openEduSuite connects several components behind the same SSO into one service. SOGo serves as the groupware frontend for calendar, contacts, and email; delivery itself is handled by classic components (Dovecot/Postfix in the core platform, see content/de/blog/service-landscape.md). For larger federated scenarios, Stalwart Mail is additionally documented as a Rust-based IMAP/SMTP/JMAP server (content/de/blog/federated-student-email-architecture.md).
What matters for interaction is the directory behind it: user management does not live in each mail service but in a central directory (LDAP/OpenLDAP) behind Keycloak. A service does not authenticate users itself; it relies on the mapping decreed by the identity broker β the LDAP bind credentials count among the critical secrets from Section 4 (priority P1 in the SOPS documentation). This keeps the mail domain attached to the same user directory as every other service account, without mail components inventing their own identities.
6. Authorization and isolation as an interface contract
Where several services coexist in one cluster, isolation is a contract, not a feeling. openEduSuite uses two mechanisms for this, described in content/de/architecture/security.md and networking-traffic-flow.md:
- Namespace isolation: services run in their own Kubernetes namespaces (in the deployment examples:
openedu,llm,home,monitoring,backup,argocd). Ahomenamespace for portals is separated fromllmfor AI workloads, so a broken component stays contained β documented incontent/de/blog/self-hosted-campus-k3s.md. - Default-deny network policies: all pod-to-pod traffic is denied by default and must be explicitly allowed. A NetworkPolicy thereby becomes the binding interface contract between two components: who may talk to whom is declared in Git and versioned, instead of being implied by network topology.
This default-deny model is the logical complement to the SSO chain from Section 1: identity governs who a user is; network policies govern what a pod may reach β even if the identity layer has already been bypassed.
7. Limits: what the patterns do not solve
The documented operations experience also shows where the patterns hit their limits:
- ArgoCD "lies" under parallel configuration management. Where deployment parts originate outside Git (ad-hoc
kubectl, nix-managed state), the running configuration no longer matches the Git truth. GitOps is only as honest as the discipline of running everything through Git. - Domain renames are high-risk. A domain migration touches every component. The documented lesson from the self-hosting article: env variables like
NEXTCLOUD_TRUSTED_DOMAINSapply only at install time; later changes need the application's own configuration path (occ config:system:set). Orphaned remnants of old domains (id.umr-style) accumulate quickly during renames. The clean answer is visibility: manifests and runbooks belong next to the code they affect. - Visibility through verification tests instead of trust. Because the chain from Section 1 is so central, it is not taken for granted but tested as a contract. The documented spec-contract-test pyramid (
content/de/blog/spec-contract-test-pyramid.md) organizes exactly this: specifications (what is needed), contracts (how services communicate), and automated tests (that it works). An SSO matrix across all services, verified end-to-end, protects against a seemingly harmless Keycloak change silently locking a group of services out of login. Robust guards β such as "no portal service tile may show a dead link" β are part of this contract thinking.
Conclusion
openEduSuite's interaction patterns reduce to one sentence: one source of truth per vertical concern. One for identity (Keycloak), one for configuration (Git), one contract for reachability (DNS), one contract for permission (NetworkPolicies). The reverse proxy repeats this principle as a fractal at every level, and every exception to the rule is documented so the next operations intervention holds no surprises.
Anyone adopting this list for their own deployment will find evidence for every pattern in the cited repository files β and in the documented limits the corrective that makes an architecture pattern durable.