Jitsi Meet on Kubernetes without Docker Compose, with Kapitan
Jitsi Meet, video meetings in the browser, as its four services web, prosody, jicofo and the video bridge jvb, rendered by the kadet-hub kubernetes generator 1.14.0 from docker-compose.yml of docker-jitsi-meet stable-11248-3.
- Latest version:
1.0.0, published - Source: Docker Compose file
jitsi/docker-jitsi-meet@fca3e3a578a1bdd527c20b9d86a31cfc362d5426(docker-compose.yml),sha256:e50de1a563bfeb005e7afd9b2defbeb298f21c8ea76ba0080724398191366a6e - Proof level:
deploys, deployed on kind and smoke tested by the kadet-hub/recipes CI renderspassed in the release run of with check 0.19.0, kind v0.33.0- Output capabilities: none
- Inventory backends:
omegaconf - Generators: kubernetes 1.14.0
sha256:787129e1eaed6b9bc868fe4c4aec58d40de6a89fc3cc08424a4cb5822b855080 - Image security: Not scanned: the image scan of 2026-11-07 did not cover version 1.0.0.
What it deploys
recipes.jitsi-meet(init.yml)recipes.jitsi-meet.web(web.yml):jitsi-meetrecipes.jitsi-meet.prosody(prosody.yml):jitsi-meet-prosodyrecipes.jitsi-meet.jicofo(jicofo.yml):jitsi-meet-jicoforecipes.jitsi-meet.jvb(jvb.yml):jitsi-meet-jvb
Required parameters
applications.jitsi-meet.jicofo_auth_password is JICOFO_AUTH_PASSWORD,
with which jicofo logs in to prosody as focus@auth.meet.jitsi.
applications.jitsi-meet.jvb_auth_password is JVB_AUTH_PASSWORD, with
which jvb logs in as jvb@auth.meet.jitsi. The compose setup has
gen-passwords.sh write random values, such as openssl rand -hex 16
prints, and any such string works. applications.jitsi-meet.moderator_password
is the password of moderator@meet.jitsi, the first user who may open a
meeting. Set all three as refs with the backend of your choice. Without one
of them the compile stops with Interpolation key '<key>' not found.
Include
A target includes the recipe and its options as classes and can override anything the recipe sets:
classes:
- recipes.jitsi-meet
parameters:
namespace: jitsi-meet
kapitan:
vars:
target: jitsi-meet
applications:
jitsi-meet:
jicofo_auth_password: <ref>
jvb_auth_password: <ref>
moderator_password: <ref>
Fetching it
The quickstart goes through these steps on a kind cluster with the uptime-kuma recipe, from installing Kapitan to the running app.
A separate fetch inventory pins the recipe by version and digest. This is fetch/targets/recipes.yml for version 1.0.0:
parameters:
kapitan:
dependencies:
- type: oci
source: ghcr.io/kadet-hub/recipes/jitsi-meet:1.0.0@sha256:8c268b78db723657e9310c4c5fee873917b1a5c4d6729b0a74f44e707104f8f7
output_path: inventory/classes/recipes/jitsi-meet
The first command fetches the recipe into inventory/classes/recipes/jitsi-meet, the second the generators and compiles:
kapitan compile --inventory-path fetch --fetch --output-path .
kapitan compile --fetch
The first command exits with status 1 because the fetch target compiles nothing. Kapitan's --fetch checks neither the digest nor the attestation. This verifies the attestation of the recipe:
gh attestation verify \
oci://ghcr.io/kadet-hub/recipes/jitsi-meet@sha256:8c268b78db723657e9310c4c5fee873917b1a5c4d6729b0a74f44e707104f8f7 \
--owner kadet-hub \
--cert-identity https://github.com/kadet-hub/registry/.github/workflows/release.yml@refs/heads/main
The attested index must also list each digest under its recipe with the pinned version, and not as yanked. Verifying by hand shows how to read it.
Settings
<c> stands for each of the components jitsi-meet, jitsi-meet-prosody,
jitsi-meet-jicofo and jitsi-meet-jvb.
| Setting | Inventory |
|---|---|
| Jitsi release, stable-11248-3 | components.<c>.image, tag and digest, the same release for all four |
time zone, UTC from env.example |
components.<c>.env.TZ |
| public URL, unset | components.jitsi-meet.env.PUBLIC_URL |
| media address, unset | components.jitsi-meet-jvb.env.JVB_ADVERTISE_IPS |
user name of the first moderator, moderator |
components.jitsi-meet-prosody.env.MODERATOR_USER |
cluster domain, cluster.local |
applications.jitsi-meet.cluster_domain |
other variables of env.example, such as ENABLE_LOBBY |
components.<c>.env.<variable> of each component whose compose service lists it |
| exposure | components.jitsi-meet.service.type, components.jitsi-meet-jvb.service.type |
size of the claims storage, 1Gi |
components.jitsi-meet.volume_claims.storage.spec.resources.requests.storage, the same below jitsi-meet-prosody |
| resources | components.<c>.resources |
Values of env are strings, so quote "1". web resolves prosody by the
Service’s full name, jitsi-meet-prosody.<namespace>.svc.<cluster domain>,
so set applications.jitsi-meet.cluster_domain on a cluster whose domain
is not cluster.local.
The images render their configuration at each start from the environment.
The directories /config of each image and /prosody-plugins-custom of
prosody take files that extend it, such as custom-config.js for web. A
target mounts a ConfigMap there through config_maps of the component.
Mapping
The service web becomes the component jitsi-meet, prosody
jitsi-meet-prosody, jicofo jitsi-meet-jicofo and jvb
jitsi-meet-jvb. The compose file sets no variable of its own beyond
SENTRY_DSN. Its services pass variables from .env, which the
documentation copies from env.example at the same commit, SHA-256
b38560d14b7f25be441f8ae116f13fcb626d8a3bd248c86f03ef25060d2330d9
(DEC-306). A variable that env.example leaves unset is not rendered.
| Compose | Recipe |
|---|---|
service web |
StatefulSet jitsi-meet, Service jitsi-meet |
service prosody |
StatefulSet jitsi-meet-prosody, Service jitsi-meet-prosody |
service jicofo |
Deployment jitsi-meet-jicofo, Service jitsi-meet-jicofo |
service jvb |
Deployment jitsi-meet-jvb, Service jitsi-meet-jvb |
images ${JITSI_IMAGE_REPO:-ghcr.io/jitsi}/<service>:stable-11248-3 |
ghcr.io/jitsi/<service>:stable-11248-3 with the digest of its index |
ports ${HTTP_PORT}:8000 and ${HTTPS_PORT}:8443 of web, 8000 and 8443 in env.example |
Service ports http 8000 and https 8443 |
expose 5222, 5269, 5347 and 5280 of prosody |
Service ports c2s, s2s, component and http |
port 127.0.0.1:8888:8888 of jicofo |
Service port http 8888 |
ports 10000:10000/udp and 127.0.0.1:8080:8080 of jvb |
Service ports media 10000/UDP and http 8080 |
TZ and the passwords in env.example |
TZ: UTC in each env, JICOFO_AUTH_PASSWORD and JVB_AUTH_PASSWORD from the Secret jitsi-meet |
JIGASI_XMPP_PASSWORD, JIGASI_TRANSCRIBER_PASSWORD, JIBRI_RECORDER_PASSWORD, JIBRI_XMPP_PASSWORD |
not rendered. They serve jigasi and jibri, which docker-compose.yml does not run (DEC-306). |
SENTRY_DSN="${JICOFO_SENTRY_DSN:-0}" and its jvb counterpart |
SENTRY_DSN: '"0"', the quoted value compose passes |
prosody’s network alias ${XMPP_SERVER:-xmpp.meet.jitsi} |
XMPP_SERVER: jitsi-meet-prosody for jicofo and jvb, and web’s XMPP_BOSH_URL_BASE with the Service’s full name (DEC-308) |
bind mounts ${CONFIG}/storage/web and ${CONFIG}/storage/prosody |
claim templates storage, ReadWriteOnce, 1Gi, at /storage and /var/lib/prosody, with fsGroup: 1000 (DEC-307) |
the other bind mounts below ${CONFIG} |
not rendered. The containers only read them, and the images have the directory empty or read it only for a feature that is off (DEC-307). |
read_only: true |
readOnlyRootFilesystem: true (DEC-309) |
tmpfs at /run and /tmp |
emptyDir volumes of medium: Memory with the compose sizes as sizeLimit (DEC-309) |
labels service: jitsi-<service> |
pod annotations |
restart, depends_on, the network meet.jitsi |
not rendered |
| not in the compose file | ENABLE_AUTH and ENABLE_GUESTS (DEC-310), the postStart hook and MODERATOR_* of prosody (DEC-311) |
Not every Service port answers with these settings, as with the compose
file. jicofo serves its REST API on 8888 at its loopback address unless
JICOFO_ENABLE_REST is set. prosody listens on 5269 only with
PROSODY_ENABLE_S2S and on 5347 only for an external component that its
configuration names.
Proof
check jitsi-meet --level deploys with tests/target.yml, on kind for
each Kubernetes minor CI tests, with check 0.16.0. A compose file has no
Kubernetes objects to compare with, so the recipe is proven by deploying it
(RCP-38). tests/target.yml sets low-entropy test passwords.
tests/smoke starts a pod of prosody’s image, which carries bash and curl.
From there, through the Services, it
- gets the start page of web on 8000 and on 8443,
/about/healthof jvb on 8080, and XMPP stream features from prosody on 5222 - requires that
meet.jitsioffers no anonymous login, answers in-band registration withservice-unavailableand refuses a wrong password, over BOSH through web - waits until jicofo’s REST API, on its loopback address, reports a bridge, because jicofo grants a meeting without one and then offers no session
- logs in as the moderator and as an anonymous guest, and requires that jicofo refuses the guest a new meeting
- has the moderator open a meeting and join its room, and the guest join it, each answering jicofo’s disco#info query as a Jitsi client does
- requires jicofo’s Jingle offer to the moderator, which names a UDP candidate on port 10000 at jvb’s pod IP, so jicofo allocated the meeting on the bridge
- sends a message from the guest to the room and one from the moderator, and requires that each reaches the other
- stores private XML data as the moderator, deletes prosody’s pod, logs in again once a new pod runs, gets a meeting from jicofo and reads the data back.
Not tested: audio and video, whether a client reaches UDP port 10000, ICE through STUN, TURN, the web app in a browser, and the ports that answer only with other settings.
Versions
1.0.0, :sha256:8c268b78db723657e9310c4c5fee873917b1a5c4d6729b0a74f44e707104f8f7
The recipe's files are in kadet-hub/recipes. All recipes