Local Development
Run a complete Hoppla instance on a laptop — no Kubernetes, no cluster — so your engineers can build integrations, test white-label branding, or explore the product. It uses the same prebuilt images you deploy to production (nothing is built from source), fronted by a small reverse proxy so the dashboard, API, session cookies, and realtime updates all work on one http://localhost origin.
This is a development instance
It runs with a single database role (so PostgreSQL row-level security is bypassed locally — application-level tenant scoping still applies), relaxes captcha and secure-cookie requirements for http://localhost, and ships a shared throwaway APP_KEY. Never use it in production. For production, use install.sh and the Helm chart.
What you need
- Docker with Compose v2 (
docker compose version) - Your registry credentials and a development license file from your welcome pack
The bundle lives in the distribution repo under dist/selfhosted/local/. It contains a docker-compose.yml, a ready-to-use .env.example, a config.js, and a Caddyfile.
Get the development license
The development license is issued from the Hoppla admin console like any other, with one difference: its environment is development, which is domain-unbound — it carries no bound domains and produces no binding warning, so it runs on any host your engineers use (localhost, a shared dev box, CI). Ask your Hoppla contact to include a development license in your welcome pack (a single pack can bundle production, staging, testing, and development licenses together).
Start it
cd dist/selfhosted/local
# 1. Configuration — the defaults work as-is for local dev.
cp .env.example .env
# 2. Authenticate Docker to the registry (once), using the classic
# read:packages token from your welcome pack.
echo "<YOUR_GHCR_TOKEN>" | docker login ghcr.io -u "<YOUR_GITHUB_USERNAME>" --password-stdin
# 3. Drop in your development license.
cp /path/to/hoppla.license ./license/hoppla.license
# 4. Bring the stack up (pulls the images on first run).
docker compose up -d
# 5. Create the first admin + organization (idempotent).
docker compose exec api php artisan hoppla:installOpen http://localhost and log in. Outbound mail is captured by Mailpit at http://localhost:8025 — no real email leaves the machine.
Verify the license and instance health at any time:
docker compose exec api php artisan license:inspect
docker compose exec api php artisan hoppla:doctorInjecting the license
The application reads its license from one of two places (see Configuration):
- A file —
HOPPLA_LICENSE_PATH(default/var/hoppla/license/hoppla.license). The local bundle mounts./license/hoppla.licensethere for you. - An inline value —
HOPPLA_LICENSEset to the full armored license text (the-----BEGIN HOPPLA LICENSE----- … -----END…block). Useful when a file mount is awkward, e.g. CI or a hosted dev environment.
If you set HOPPLA_LICENSE, you can skip the file mount entirely.
Previewing your rebrand
White-label branding — brand name, colors, logo, login page, portal domains — is configured inside the app once you're logged in, and it renders on this local instance exactly as it would in production. That makes local the right place to build and preview a reseller rebrand before shipping it. See White-Label for the full walkthrough.
Updating
Bump HOPPLA_VERSION in .env (check VERSIONS.md in the distribution repo for the latest), then:
docker compose pull
docker compose up -d # schema migrations run automatically on the way upTroubleshooting
| Symptom | Fix |
|---|---|
Images won't pull (denied / unauthorized) | Re-run docker login ghcr.io … with a classic read:packages token — fine-grained tokens don't authenticate to GHCR. |
http://localhost shows nothing / port 80 in use | Set HTTP_PORT in .env to a free port (e.g. 8080) and open that. Live (websocket) updates assume the standard port; everything else works on any port. |
| "No license configured" | Confirm ./license/hoppla.license exists, then docker compose restart api worker scheduler. |
| Login rejected / captcha error | Ensure .env keeps APP_ENV=local and TURNSTILE_REQUIRED=false (the defaults). |
| Checks never run | The scheduler and worker services must be up (docker compose ps) — they need the license mounted too. |
To wipe everything and start fresh: docker compose down -v (deletes the database volume).