H5P service in production
H5P content is served by its own Node service (api/h5p, GPL, reached over HTTP only; see
Licensing). It is multi-tenant: the request host selects the tenant’s
database, bucket and Passport public key. This page covers running it in production; the service
itself is described in api/h5p/README.md.
Production mounts
Section titled “Production mounts”The development stack mounts the whole api/ folder into the service, which also exposes the
application code, every secret of every env file and the Passport private keys. In production the
service reads a config directory that holds, per tenant, only the database, bucket and URL
settings it needs and the Passport public key. The API writes it (H5PServiceConfigExporter):
<dir>/.env platform subset<dir>/.env.<host> tenant subset<dir>/keys/oauth-public.key and <dir>/keys/<host_with_underscores>/oauth-public.key-
Set the directory on the API, in the platform environment (the production example does this with a dedicated volume):
LARAVEL_H5P_SERVICE_CONFIG_DIR=/var/www/html/storage/h5p-serviceThat becomes
H5P_SERVICE_CONFIG_DIRin the platform.env. Empty means “not exported” (development). -
Start the API, then export once:
Terminal window docker compose exec api php artisan ulams:h5p:export-configThe export also runs at start (
ulams:tenant:sync-env) and whenever a tenant is created, synced or deleted.ulams:upgraderuns it as a step too, so after every release the directory matches the tenants table. Run it by hand after creating tenants outside those commands or after restoring a backup. -
Mount the directory read-only into the service with the production override (
api/h5p/compose.h5p.prod.yml; the exampledocker-compose.ymlalready contains the same settings):Terminal window docker compose -f docker-compose.yml -f h5p/compose.h5p.prod.yml up -d h5pIt sets
ENV_DIR=/config,KEYS_DIR=/config/keysandJWT_PUBLIC_KEY_PATH=/config/keys/oauth-public.key, mounts${H5P_SERVICE_CONFIG_HOST_DIR:-./storage/h5p-service}at/config:ro, makes the container’s root file system read-only and gives it atmpfsfor/tmp. The override uses!override(Docker Compose 2.24 or newer).
The service re-reads the files every TENANT_RELOAD_CHECK_MS, so a new tenant works without a
restart. The files are written atomically and, when the command runs as root, handed to the
owner and group of the Laravel storage directory; the service reads them through the same group
(group_add: ["82"] in the example).
Connections
Section titled “Connections”A tenant with no H5P request for 30 minutes (TENANT_IDLE_EVICT_MS) releases its database pool
and S3 client; its next request reconnects with a short delay and no visible error. Set it to 0
to keep every tenant connected.
Libraries
Section titled “Libraries”Installing, updating or deleting H5P libraries changes them for all tenants, so the service
accepts it only on the platform host (the platform admin, see
H5P libraries). The h5p_libraries volume holds them; back it up with the
other volumes (Backups).
Learner sessions
Section titled “Learner sessions”Access tokens live about 5 minutes. The admin and the React front refresh them in the background and hand every new token to the H5P frame, which keeps saving the learner’s state without reloading the content.
The state proxy of the reference front
Section titled “The state proxy of the reference front”The reference front (web) keeps the learner’s token in an httpOnly session cookie, so it never
hands a token to the H5P frame (the embed page is told token: null). H5P content is third-party
script, and a token inside the frame could be read by it. Instead the front serves the H5P service
on its own origin at /h5p/* (a same-origin proxy to <tenant API>/h5p/*) and adds the session
token on the server, only for the learner’s own player calls
(ADR 0045):
Method and path (after /h5p/) |
Purpose |
|---|---|
GET embed/play/<id> |
The embed page |
GET contents/<id>/play |
The play model |
GET, POST contentUserData/<id>/<type>/<sub> |
Load and save the learner’s state |
POST finishedData |
Save the result |
Every other route and method (libraries, editor, content management) is forwarded without the
session, and a _token query parameter sent by the caller is dropped. The proxy marks its requests
with X-Ulams-Session-Proxy: 1 so the H5P service leaves the learner’s token out of the URLs it puts
in the play model; the frame’s calls then come back through the proxy. Learners’ answers count for
progress and the content restores where they left off.
For operators this changes nothing to configure: H5P traffic of logged-in learners passes through the
web container, which must be able to reach the tenant API host (it already must for everything
else), and the proxy adds a little latency.
H5P runs on the application origin, not on the content origin (it calls its service with fetch),
so a separate content domain does not change anything here; see
Content origin.