Quick start
Needs review
Needs review: First run on a clean machine: whether the api container needs a manual `composer install` (vendor/ is hidden by the bind mount), whether an empty admin/.env is enough for `env-cmd`, and whether `make -C api h5p-seed` is required before the demo seeders.
This page gets the whole stack running on your machine: the API in Docker, the admin and the learner frontends from source, and the three demo academies. For a deeper tour of the development setup see local development; for production see self-hosting.
Requirements
Section titled “Requirements”- Docker with Compose v2 (
docker compose). - Node.js 22.12 or newer (22 and 24 are tested). The repository pins
22in.nvmrcand requires>=22.12in the rootpackage.json(engines). - Yarn 1 through Corepack. The root
package.jsonsetspackageManager: yarn@1.22.22. - Free ports 80 and 443 (Caddy), 3000, 4321, 4322 and 8000 (dev servers), 8025 and 8078 (MailHog and Adminer).
*.localhost host names resolve to 127.0.0.1 in current browsers, so no /etc/hosts entries
are needed. All hosts are listed on local hosts and ports.
Install and run
Section titled “Install and run”-
Clone the repository and pick the Node version:
Terminal window git clone https://github.com/ulams-dev/ulams.gitcd ulamsnvm use # reads .nvmrc (22); any Node >= 22.12 workscorepack enable -
Install every JavaScript workspace (one
yarn.lockfor admin, front, front/web, front/sdk, front/ui, front/docs-site, api/h5p and api/pdf):Terminal window corepack yarn install -
Start the API stack. This runs
docker compose -f api/docker-compose.yml up -d: Caddy, php-fpm, PostgreSQL, Valkey, MinIO, MailHog, Adminer, the H5P and PDF services, mjml and Soketi. The first run builds theapi,h5pandpdfimages.Terminal window corepack yarn dev:apiOn start the
apicontainer writesapi/.envfrom itsLARAVEL_*variables, runs the platform migrations, syncs tenants, creates the Passport keys and seeds permissions (api/init.sh). The repository is bind-mounted into the container; ifapi/vendordoes not exist yet, install the PHP dependencies once:Terminal window docker compose -f api/docker-compose.yml exec api composer install -
Start the apps you need, each in its own terminal:
Terminal window corepack yarn dev # admin on :8000 and the legacy React front on :3000corepack yarn dev:web # reference frontend (front/web, Astro) on :4321corepack yarn dev:docs # this documentation site on :4322yarn dev:adminandyarn dev:frontstart only one of the two. The admin’sdevscript loadsadmin/.envwithenv-cmd, so that file must exist (it is git-ignored). LeaveREACT_APP_API_URLout of it to keep per-tenant hosts working: when it is set, the admin always talks to that one API.
Check it works:
| URL | What |
|---|---|
| http://localhost:8000 | Admin (platform), admin@ulams.app / secret |
| http://localhost:4321 | Reference frontend on the platform host: the ulams product landing |
| http://localhost:3000 | Legacy React learner app (platform) |
| http://localhost:4322 | These docs |
| http://localhost:8025 | MailHog, catches outgoing e-mail |
Create the demo academies
Section titled “Create the demo academies”The six demo tenants are created with the tenancy package and seeded with their courses.
Run the tenant commands inside the API container, on the platform (without --domain):
-
Create the tenants. Each command provisions a database, a bucket, an env file, migrations, Passport keys and demo users; it is safe to re-run and skips finished steps.
Terminal window docker compose -f api/docker-compose.yml exec api bashphp artisan ulams:tenant:create coffee --name="The Coffee Atlas" --theme=coffee --accent="#C2552D"php artisan ulams:tenant:create oncall --name="On-Call" --theme=oncall --accent="#58A6FF"php artisan ulams:tenant:create nightsky --name="Night Sky Explorers" --theme=nightsky --accent="#FFD23F"php artisan ulams:tenant:create gravity --name="Gravity Lab" --theme=gravity --accent="#3DD6F5"php artisan ulams:tenant:create poland --name="Poland, Measured" --theme=poland --accent="#C8102E"php artisan ulams:tenant:create ulam --name="The Scottish Book" --theme=ulam --accent="#1D3B8F"exitmake -C api demo-create-tenantsruns the same six commands. -
Import the H5P content types into the H5P service. The demo seeders build H5P topics through the service and skip them, with a warning, if it cannot be reached.
Terminal window make -C api h5p-seed -
Seed the demo courses: all six on the platform, then one per tenant. The gravity, poland and ulam courses play interactive packages that are built on the host first.
Terminal window make -C api demo-packages demo-seed demo-seed-tenants -
Optional: turn on demo mode (password-less login and an hourly reset) for the six tenants. The platform landing then shows a card per demo with “Open as learner” and “Open as admin”, each signing you in with one click.
Terminal window make -C api demo-mode-on
Open http://coffee.app.localhost, http://oncall.app.localhost, http://nightsky.app.localhost, http://gravity.app.localhost, http://poland.app.localhost or http://ulam.app.localhost.
Caddy sends *.app.localhost to the reference frontend on port 4321, so yarn dev:web must be
running. The same tenants in the legacy React app are at http://<slug>.app.localhost:3000.
Make targets
Section titled “Make targets”api/makefile wraps common docker compose exec calls. Run them from the repository root with
make -C api <target>, or from api/. The makefile includes api/.env, which the api
container writes when it starts, so start the stack first.
| Target | What it does |
|---|---|
migrate-fresh |
migrate-fresh-quick followed by h5p-seed |
migrate-fresh-quick |
migrate:fresh --seed on the platform database, new Passport keys and personal access client |
h5p-seed |
Imports the curated H5P content types and samples into the H5P service |
demo-packages |
Builds the gravity, poland and five ulam interactive packages on the host into the seeder’s cache (run it before seeding those three tenants; Chromium is needed for the gravity posters) |
demo-seed |
Seeds all six demo courses on the platform tenant (api.localhost) |
demo-seed-tenant DOMAIN=coffee.localhost |
Seeds one tenant with its own course (experience = first label of the domain), then processes its video queue once |
demo-seed-tenants |
demo-seed-tenant for coffee, oncall, nightsky, gravity, poland and ulam |
demo-create-tenants |
ulams:tenant:create for the six demo tenants, with their names, themes and accents |
demo-mode-on |
Writes DEMO_MODE=true for the six demo tenants (ulams:tenant:create <slug> --demo=on) |
demo-reset DOMAIN=coffee.localhost |
Wipes and reseeds one demo tenant now (the scheduler does it hourly) |
demo-reset-all |
demo-reset for all six demo tenants, one after the other |
swagger-generate |
Regenerates the OpenAPI spec (php artisan l5-swagger:generate) |
backup-postgres |
Dumps the platform database to api/docker/postgres-backups/backup-<timestamp>.sql and backup-latest.sql |
import-postgres BACKUP_FILE=backup-latest.sql |
Restores a dump from that folder |
test-phpunit |
Runs PHPUnit in the api container |
bash |
Opens a shell in the api container |
tinker |
Runs php artisan tinker in a loop |
DEMO_REFRESH=1 makes the demo seed targets rebuild existing demo courses instead of keeping
them. Other targets in the makefile (composer-update, refresh, init, restart_queue_cron,
update-composer-to-git) are inherited from Wellms and refer to a ulams_queue_cron service
that is commented out in api/docker-compose.yml; avoid them.
Compose profiles
Section titled “Compose profiles”api/docker-compose.yml has one optional profile:
| Profile | Service | Start with |
|---|---|---|
av |
clamav, virus scanning of uploads (UPLOADS_SCANNER=clamd) |
docker compose -f api/docker-compose.yml --profile av up -d clamav |
Every other service starts with yarn dev:api.
Everyday commands
Section titled “Everyday commands”corepack yarn build # build all workspaces (Turborepo, cached)corepack yarn typecheckcorepack yarn lintcorepack yarn test # unit tests of admin, front, api/h5p, api/pdf, sdk, ui and webcorepack yarn test:web:e2e # Playwright smoke and accessibility tests of front/webcorepack yarn test:api # PHPUnit inside the api containerdocker compose -f api/docker-compose.yml exec api ./vendor/bin/phpunit --testsuite courses