Skip to content

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.

  • Docker with Compose v2 (docker compose).
  • Node.js 22.12 or newer (22 and 24 are tested). The repository pins 22 in .nvmrc and requires >=22.12 in the root package.json (engines).
  • Yarn 1 through Corepack. The root package.json sets packageManager: 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.

  1. Clone the repository and pick the Node version:

    Terminal window
    git clone https://github.com/ulams-dev/ulams.git
    cd ulams
    nvm use # reads .nvmrc (22); any Node >= 22.12 works
    corepack enable
  2. Install every JavaScript workspace (one yarn.lock for admin, front, front/web, front/sdk, front/ui, front/docs-site, api/h5p and api/pdf):

    Terminal window
    corepack yarn install
  3. 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 the api, h5p and pdf images.

    Terminal window
    corepack yarn dev:api

    On start the api container writes api/.env from its LARAVEL_* 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; if api/vendor does not exist yet, install the PHP dependencies once:

    Terminal window
    docker compose -f api/docker-compose.yml exec api composer install
  4. Start the apps you need, each in its own terminal:

    Terminal window
    corepack yarn dev # admin on :8000 and the legacy React front on :3000
    corepack yarn dev:web # reference frontend (front/web, Astro) on :4321
    corepack yarn dev:docs # this documentation site on :4322

    yarn dev:admin and yarn dev:front start only one of the two. The admin’s dev script loads admin/.env with env-cmd, so that file must exist (it is git-ignored). Leave REACT_APP_API_URL out 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

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):

  1. 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 bash
    php 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"
    exit

    make -C api demo-create-tenants runs the same six commands.

  2. 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
  3. 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
  4. 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.

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.

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.

Terminal window
corepack yarn build # build all workspaces (Turborepo, cached)
corepack yarn typecheck
corepack yarn lint
corepack yarn test # unit tests of admin, front, api/h5p, api/pdf, sdk, ui and web
corepack yarn test:web:e2e # Playwright smoke and accessibility tests of front/web