First admin and first tenant
Needs review
Needs review: The platform admin panel recipe (MULTI_DOMAINS plus a per-host REACT_APP_API_URL in the admin container, admin/config/php/index.php) is read from the code and not tried with the published image.
The platform administrator
Section titled “The platform administrator”The platform is the API on api.<domain>: it holds the tenant registry and is itself a complete
LMS. Its first administrator is created by PermissionsSeeder, which init.sh runs on every
start (unless DISABLE_DB_SEED=true). The seeder creates the user only when both hold:
- the platform
userstable is empty, and INITIAL_USER_PASSWORDis set.
Variable (on api, as LARAVEL_…) |
Default |
|---|---|
INITIAL_USER_EMAIL |
admin@ulams.app |
INITIAL_USER_PASSWORD |
none: no admin is created |
INITIAL_USER_FIRST_NAME / INITIAL_USER_LAST_NAME |
Root / Admin |
The production example maps them from PLATFORM_ADMIN_EMAIL and PLATFORM_ADMIN_PASSWORD.
After the first sign-in, change the password in the panel. Once a user exists the variables do
nothing, so you can remove the password from .env afterwards.
Provisioning a tenant
Section titled “Provisioning a tenant”Tenants are managed from the command line on the platform (no --domain), inside the api
container. What the steps do is described in Tenants and
Tenancy.
-
Create the tenant:
Terminal window docker compose exec api php artisan ulams:tenant:create acme \--name="Acme Academy" --theme=coffee --accent="#C2552D" --users=0The command creates the PostgreSQL role and database
ulams_acme, the bucketulams-acmewith public read, the env file.env.acme.api.<domain>, runs the migrations, generates the tenant’s Passport keys and LTI keys, seeds permissions and creates the initial accounts. It is resumable: if a step fails, fix the cause and run the same command again. -
Wait about five seconds. The example’s copy loop puts the new env file into the
tenant_envvolume, which makes the H5P service serve the tenant and lets Caddy issue certificates for its four host names. -
Open
https://acme.admin.<domain>and sign in asadmin@acme.<domain>(the address comes fromTENANCY_EMAIL_DOMAIN) with the password fromTENANT_DEMO_PASSWORD. Change it right away. The learner front is athttps://acme.app.<domain>.
Leave demo mode off for real tenants: it is off unless you pass --demo=on, and it allows
password-less login and resets the tenant every hour (see Demo mode).
Other tenant commands
Section titled “Other tenant commands”docker compose exec api php artisan ulams:tenant:list # slugs, hosts, statusdocker compose exec api php artisan ulams:tenant:list --hosts # API hosts onlydocker compose exec api php artisan ulams:tenant:create acme --redo=migratedocker compose exec api php artisan ulams:tenant:sync-env --migratedocker compose exec api php artisan ulams:tenant:delete acme --forceulams:tenant:delete drops the database and role, the bucket, the env file, the storage
directory and the tenant’s Redis keys. There is no undo: take a backup
first.
The platform admin panel
Section titled “The platform admin panel”The admin image resolves the API from its own host name with
REACT_APP_TENANT_API_HOST_PATTERN. The bare platform host admin.<domain> has no slug, so the
panel falls back to REACT_APP_API_URL from the build, which is empty in the published image.
Setting REACT_APP_API_URL on the container would point every host at the platform. The page
script of the image (admin/config/php/index.php) can inject values for one host only:
admin: environment: REACT_APP_TENANT_API_HOST_PATTERN: "{slug}.admin.lms.example.com=>https://{slug}.api.lms.example.com" MULTI_DOMAINS: admin.lms.example.com # host upper-cased, dots and dashes as underscores, then the variable name ADMIN_LMS_EXAMPLE_COM_REACT_APP_API_URL: https://api.lms.example.comAlternatively run a second admin container for the platform with REACT_APP_API_URL set and
route admin.<domain> to it.