A neutral media client + server for a library you own and are entitled to stream. Bring your own files; Zaentrum catalogs, processes, and streams them to clean clients on the web, your phone/tablet, and your TV.
docker run -d --privileged --restart unless-stopped -p 80:80 --name zaentrum \
ghcr.io/zaentrum/appliance:latest
open http://zaentrum.localhostThat single container runs the whole platform: a full Kubernetes (k3s) in-process, the
operator, and everything the operator brings up — the portal, the web app, the catalog and
its consoles, streaming, and bundled Keycloak, Postgres, Valkey and Kafka. One
image, one port, nothing else to install; the first boot pulls the platform's images from
ghcr.io and takes a few minutes.
- Port 80, and the name
zaentrum.localhost. The platform's Ingress answers that host only, and its sign-in is bound tohttp://zaentrum.localhost— no other name, no other port. Modern browsers resolve*.localhostto127.0.0.1, so this needs no/etc/hostsedit. - linux/amd64 only. No arm64 image is published yet.
- This machine only. Phones, TVs and other computers need https — see phones and TVs.
- Keep the container. The library and the database live in its volume, and replacing the container starts an empty platform — see persistence.
The appliance in detail: deploy/allinone/README.md.
There is no setup wizard: a fresh install comes up configured, with the bundled Keycloak
(realm zaentrum) and an empty library. Three steps make it yours
(self-hosting → first run):
-
Sign in as
adminwith the first admin password. The operator generates it once per install and keeps it in the Secretzaentrum-keycloak-admin; Keycloak then has you choose a password of your own:docker exec zaentrum kubectl -n zaentrum get secret zaentrum-keycloak-admin \ -o jsonpath='{.data.realm-admin-password}' | base64 -d; echo
On a cluster, run the same
kubectlwithoutdocker exec zaentrum. -
Add a TMDB key under Catalog Management → settings (
/katalog-manage/) before the first scan: titles, posters and plots come from TMDB, and the images carry no key. -
Copy your files into the library, then trigger a scan in Catalog Management. The library is the
media/folder of the platform'smediavolume — on the appliance, a directory inside the container (how to fill it).
Sign-in needs https everywhere but on the machine itself. Keycloak marks its login cookies
Secure, which a browser keeps over plain http for localhost names only — over
http://<lan-ip> Keycloak answers the login form with "Cookie not found" — and the Android
phone and TV apps refuse plain http altogether and trust public certificate authorities only.
The appliance serves plain http at zaentrum.localhost, so it is for the machine it runs on.
For other devices, give the appliance a name and a certificate
(how), or run the operator
on a cluster under a real hostname with TLS
(self-hosting,
troubleshooting).
Zaentrum runs its own bundled Keycloak (realm zaentrum), or — with
identity.mode: external on a cluster — validates the tokens of your own OIDC provider and
renders no Keycloak. Those are the two modes the CRD accepts; federating your provider through
the bundled Keycloak (broker,
ADR-0007) is
designed, not built.
Users are managed in Keycloak, whose admin console is not on the public host: the Ingress
sends only /auth/realms and /auth/resources to Keycloak. Reach it through a port-forward on
local port 8080 — the console's links point there — signed in as the bootstrap admin (keys
username and password of the Secret zaentrum-keycloak-admin), then switch to the realm
zaentrum:
kubectl -n zaentrum port-forward svc/keycloak 8080:80
open http://localhost:8080/auth/admin/Or publish it on the public host with spec.identity.exposeAdminConsole: true. The details:
operator/README.md; the appliance's variant:
deploy/allinone/README.md.
The same platform runs on any Kubernetes cluster through the operator, which reconciles the
whole stack from a single Zaentrum resource. Install it once, as cluster-admin, from an
install manifest — the CRDs, the cluster RBAC and the controller — then apply a Zaentrum:
# the newest release, its controller on operator:vX.Y.Z — for the stable channel (the default)
kubectl apply -f https://github.com/zaentrum/zaentrum-operator/releases/latest/download/operator-install.yaml
# …or the newest build of main, pinned to one immutable operator:sha-<commit> — for edge:
# kubectl apply -f https://raw.githubusercontent.com/zaentrum/zaentrum-operator/main/deploy/operator-install.yaml
kubectl create namespace zaentrum
kubectl apply -f zaentrum.yaml # your Zaentrum resource
kubectl -n zaentrum get zaentrum # PHASE Ready once it is upThe operator renders the platform from the chart it embeds, so pair a release's operator with the release's images (Releases).
On OpenShift or any OLM cluster, the OLM bundle is the alternative; without
the operator, helm install the chart it renders,
operator/platform/chart.
spec.hostname is the name the platform answers at: the operator derives the OIDC issuer,
Keycloak's KC_HOSTNAME and the Ingress host from it. Serve that name over https — with
spec.tls, a certificate Secret or a cert-manager issuer
(the platform's certificate), or TLS
terminated in front of the Ingress and identity.issuerScheme: https — and
network.issuerHostAliasIP so in-cluster token validation reaches the https issuer. A minimal
resource and every field:
self-hosting,
operator & CR reference.
After every update the platform checks itself — outside-in through its public
URL, with a real sign-in — and the operator reports the verdict in
status.verification (kubectl get zaentrum, zae platform status). What runs,
when, the test account and how to ask for a run:
operator/README.md.
deploy/base, deploy/compose, deploy/k3s and deploy/overlays are older profiles kept in
the tree; they do not bring up a working platform and are not supported
(why).
One Ingress — or, with routing.provisionRoutes, OpenShift Routes with the same paths — fronts
everything on the host spec.hostname names:
| Path | Backend | What it is |
|---|---|---|
/, /portal |
zaentrum-portal |
the portal — the launchpad that opens the apps and, for an admin, the consoles |
/api/portal |
portal-api |
the portal's API |
/chino, /auth/callback |
chino-web |
the video app — a static SPA |
/api |
chino-api |
the product BFF |
/katalog |
katalog-manager-ui |
the Catalog console (admin) |
/katalog-manage |
katalog-manage-ui |
Catalog Management — scan and settings (admin) |
/api/manage |
katalog-manager-api |
the neutral management / write API |
/auth/realms, /auth/resources |
keycloak |
sign-in (bundled identity); all of /auth with exposeAdminConsole |
flowchart LR
user(["Browser"]) --> ing["Ingress (spec.hostname)"]
ing -- "/ and /portal" --> portal["zaentrum-portal"]
ing -- "/api/portal" --> papi["portal-api"]
ing -- "/chino" --> web["chino-web (SPA)"]
ing -- "/api" --> api["chino-api (BFF)"]
ing -- "/katalog, /katalog-manage" --> consoles["catalog consoles"]
ing -- "/api/manage" --> mgr["katalog-manager-api"]
ing -- "/auth/realms, /auth/resources" --> kc["Keycloak"]
api --> katalog["katalog-api (read)"]
api --> stream["chino-stream (HLS)"]
stream --> katalog
Zaentrum is the platform; the clients are skins over one shared core.
| Component | What it is | State |
|---|---|---|
| chino (web · mobile · androidtv) | Video client — the reference product | Real |
portal (zaentrum-portal + portal-api) |
The launchpad: the apps, the catalog consoles, the operator console | Real |
katalog-manager-ui (/katalog, /katalog-manage) |
The Catalog and Catalog Management consoles | Real |
| chino-api / chino-stream | Product BFF + HLS/CMAF origin | Real |
| katalog-manager-api | Neutral management / write API | Real |
| katalog-api | Neutral catalog read API | Real |
| processing (analyzer, packager, transcoder, katalog-ingest) | The media pipeline — features.pipeline, on a GPU or the CPU (spec.pipeline); the appliance runs it on the CPU |
Real |
| musig / tv | Music / live clients | Planned |
A release is one tag, vX.Y.Z, cut in every repository of the platform at once. Each publishes
its images as :vX.Y.Z and :X.Y (the newest patch); this one also publishes the operator, the
release's install manifest (operator-install.yaml on the
GitHub release), the appliance
ghcr.io/zaentrum/appliance:vX.Y.Z, and the OLM bundle and catalog at the same tag. latest
stays the newest build of main; a release never moves it.
An install follows one of two channels, which the front door's
releases.json points:
stable(the default) names the newest release. A new install starts on it. In manual mode (the default) an install then stays on the version it runs and reports the next release instatus.availableUpdate; in auto mode it moves on its own. A push tomainmoves nothing: the operator pins each image to the digest of the tag the channel names.edgeislatest: every push tomain, rolled component by component as the operator re-resolves eachghcr.io/zaentrum/*image to its current digest.
spec.version: vX.Y.Z pins an install to a release and opts it out of both. What a release
publishes, and how one is cut: the front door's
releases guide.
Deployment & operations documentation lives in the front-door repo:
github.com/zaentrum/zaentrum → docs/.
It routes by audience and covers every path (prerequisites, self-hosting, the
operator + Zaentrum CR reference, a worked GitOps deploy, day-2 updates, and
troubleshooting). This repo holds the operator, the chart, and the install bundles
the docs describe.
This is the platform meta-repo: the operator, the chart it renders, its install bundles and
the appliance image. The application/service sources live in their own repos at
github.com/zaentrum/<svc> and publish flat ghcr.io/zaentrum/<svc> images; the chart here
just references those images.
operator/ the controller-manager: reconciles a Zaentrum into the platform → ghcr.io/zaentrum/operator
operator/platform/chart/ the platform's Helm chart, embedded in the operator (or helm install it)
operator/bundle/ the OLM bundle (OperatorHub, OpenShift)
deploy/operator-install.yaml the pinned cluster install: CRDs, RBAC, the controller
deploy/allinone/ the appliance: k3s + the operator install + a Zaentrum → ghcr.io/zaentrum/appliance
deploy/{base,compose,k3s,overlays}/ older profiles, not supported
apps/admin/, platform/keycloak/ images CI still builds (admin, keycloak); only those profiles use them
(deployment docs live in the front-door repo: github.com/zaentrum/zaentrum/docs)
Service images the chart pulls, each built in a github.com/zaentrum service repo:
zaentrum-portal · portal-api · chino-web · chino-api · chino-stream · katalog-api ·
katalog-manager · katalog-manager-ui · zae, and with the pipeline analyzer · packager ·
transcoder · katalog-ingest.
Zaentrum is content-neutral. It catalogs and streams a library you already own; it never fetches content, and how files arrive on disk is out of scope. There are no built-in downloaders, no indexer integrations, and no automation that reaches out for media — by design and forever. See the scope section of the front door's architecture.
MPL-2.0 — file-level copyleft that protects the platform while staying distributable on mobile app stores (which matters for the iOS client). Rationale in the license section of the front door's architecture.