Adoption Demo

Copy Markdown

Lockspire includes a small Phoenix host app at examples/adoption_demo. It exists so maintainers can launch a realistic host app, open the Lockspire operator UI, and smoke the OAuth/OIDC path without wiring a separate project.

Docker is the default maintainer path. It starts Phoenix/Bandit plus PostgreSQL, keeps state scoped to one Compose project, and prints the URLs worth opening. The demo is repo-local adopter proof, not a production deployment guide, not hosted authentication, and not an expansion beyond docs/supported-surface.md. The canonical support contract still lives in docs/supported-surface.md.

Quick Start

From the repo root:

make demo

Open:

http://lockspire-demo.localhost/lockspire/admin

Login with the host-owned demo account:

ops

Useful next commands:

make demo-smoke
make demo-logs
make demo-stop

What Starts

The demo starts a Phoenix SaaS host with Lockspire mounted as the embedded provider. The ready output leads with the operator job:

  • Operator admin: http://lockspire-demo.localhost/lockspire/admin
  • Login: ops
  • Smoke: make demo-smoke
  • Logs: make demo-logs
  • Stop: make demo-stop

Additional routes are available through make demo-info:

JobURLLogin
Homehttp://lockspire-demo.localhostnone
Operator adminhttp://lockspire-demo.localhost/lockspire/adminops
Developer appshttp://lockspire-demo.localhost/developer/appsalice
Device verificationhttp://lockspire-demo.localhost/verifyalice or bob

Seeded accounts:

LoginRoleAccount
aliceSaaS useralice@acme.test
bobSaaS userbob@globex.test
opsOperatorops@acme.test

Seeded OAuth clients include acme-ledger-public, acme-tv-device, and acme-ledger-backend.

Daily Commands

CommandUse WhenEffect
make demoStart the UIStarts detached, waits for readiness, prints admin-first URLs.
make demo-infoYou need the routes againReprints URLs, seeded accounts, client shapes, and smoke commands.
make demo-smokeYou want proofRuns scripts/demo/adoption_smoke.sh against the running demo URL.
make demo-logsStartup or runtime looks oddFollows the demo web logs.
make demo-stopYou are done for nowStops containers and preserves volumes.
make demo-resetYou want fresh seeded DB stateRemoves only the active project's db_data volume.
make demo-cleanYou want cleanup previewDry run; prints allowlisted cleanup candidates.
make demo-clean-executeYou already stopped the projectRemoves only allowlisted demo resources.

The Make targets delegate to scripts/demo/admin-ui, which delegates to the lower-level examples/adoption_demo/bin/docker-* scripts. The lower-level scripts remain available when you need explicit flags:

examples/adoption_demo/bin/docker-up
examples/adoption_demo/bin/docker-up --direct
examples/adoption_demo/bin/docker-stop
examples/adoption_demo/bin/docker-reset --db-only
examples/adoption_demo/bin/docker-reset --cache-only
examples/adoption_demo/bin/docker-reset --all
examples/adoption_demo/bin/docker-cleanup --execute

Startup Output

After HTTP readiness succeeds, startup prints the same information as examples/adoption_demo/bin/docker-info, with the admin URL and ops login first.

LOCKSPIRE_DEMO_BASE_URL is the single public URL truth for endpoint generation, issuer, seeded URLs, startup output, docs, and smoke proof. Keep it aligned with the URL you type into the browser.

Reprint the banner:

make demo-info

Raw Compose reprint commands still work:

docker compose -f examples/adoption_demo/docker-compose.yml exec web ./bin/docker-info

If the demo was started with an alternate Compose project:

COMPOSE_PROJECT_NAME=lockspire-adoption-demo-alt docker compose -f examples/adoption_demo/docker-compose.yml exec web ./bin/docker-info

Smoke It

Run:

make demo-smoke

The wrapper delegates to scripts/demo/adoption_smoke.py, which remains the black-box OAuth/OIDC proof. It checks discovery, JWKS, anonymous admin rejection, operator admin access, authorization code + PKCE, device authorization, userinfo, and the protected demo API.

The explicit smoke commands remain:

LOCKSPIRE_DEMO_BASE_URL=http://lockspire-demo.localhost scripts/demo/adoption_smoke.sh
LOCKSPIRE_DEMO_BASE_URL=http://127.0.0.1:4100 scripts/demo/adoption_smoke.sh
LOCKSPIRE_DEMO_BASE_URL=http://127.0.0.1:4101 scripts/demo/adoption_smoke.sh

Expected success:

adoption demo smoke passed

Many Local Demos

Use hostname mode when running multiple local UI demos. It avoids app-specific host ports by routing through Traefik and keeps project resources isolated through COMPOSE_PROJECT_NAME.

COMPOSE_PROJECT_NAME=lockspire-adoption-demo-alt scripts/demo/admin-ui up --host lockspire-alt.localhost

Direct mode is still useful when you intentionally do not want a proxy:

examples/adoption_demo/bin/docker-up --direct --port 4101 --project lockspire-adoption-demo-alt

Use the same project name for follow-up commands:

scripts/demo/admin-ui --project lockspire-adoption-demo-alt stop
scripts/demo/admin-ui --project lockspire-adoption-demo-alt reset
scripts/demo/admin-ui --project lockspire-adoption-demo-alt cleanup

Environment Overrides

Most maintainers should use make demo. These variables exist for explicit Compose and troubleshooting workflows:

VariableDefaultPurpose
COMPOSE_PROJECT_NAMElockspire-adoption-demoCompose project namespace for containers, networks, and volumes.
LOCKSPIRE_DEMO_APP_PORT4100Direct localhost host/container port, or internal Phoenix port in Traefik mode.
LOCKSPIRE_DEMO_BASE_URLmode-specificBrowser-visible public URL truth for issuer, redirects, startup output, docs, and smoke.
LOCKSPIRE_DEMO_DB_HOST_PORTunsetRequired only when using the DB host-port override.
LOCKSPIRE_DEMO_TRAEFIK_HOSTlockspire-demo.localhostHostname routed to the demo.
LOCKSPIRE_DEMO_TRAEFIK_ROUTERproject name from launcherTraefik router label name.
LOCKSPIRE_DEMO_TRAEFIK_SERVICEproject name from launcherTraefik service label name.
LOCKSPIRE_DEMO_TRAEFIK_NETWORKlocal-dev-proxyShared external Docker network for local hostname routing.

Advanced Docker Details

The launcher starts or reuses the repo-local Traefik helper by default. If another Docker Traefik already owns 127.0.0.1:80 or 127.0.0.1:8080, the launcher auto-reuses a compatible existing Docker Traefik when it can identify exactly one proxy network.

The repo-local Traefik helper is defined in tools/traefik/docker-compose.yml.

The Traefik override uses examples/adoption_demo/docker-compose.traefik.yml. In hostname mode it removes the app's direct host-port publishing and routes by LOCKSPIRE_DEMO_TRAEFIK_HOST.

If hostname routing reports a missing network, create it idempotently:

docker network inspect "${LOCKSPIRE_DEMO_TRAEFIK_NETWORK:-local-dev-proxy}" >/dev/null 2>&1 || \
  docker network create "${LOCKSPIRE_DEMO_TRAEFIK_NETWORK:-local-dev-proxy}"

If another proxy owns port 80/8080 and auto-detection is ambiguous, run Lockspire on that proxy's Docker network:

LOCKSPIRE_DEMO_TRAEFIK_NETWORK=proxy examples/adoption_demo/bin/docker-up --no-proxy-start --host lockspire-demo.localhost

The raw Compose command remains available for maintainers who want no launcher logic:

docker compose -f examples/adoption_demo/docker-compose.yml up --build

Direct app publishing is loopback-only. It does not expose the demo on every host interface.

PostgreSQL stays internal-only by default. To inspect it from host tools, opt in with the DB host override and choose a host port:

LOCKSPIRE_DEMO_DB_HOST_PORT=15432 \
docker compose -f examples/adoption_demo/docker-compose.yml \
  -f examples/adoption_demo/docker-compose.db-host.yml up --build

The app still talks to PostgreSQL on the internal Compose service port 5432.

Cleanup Boundary

Cleanup removes only:

  • active-project Docker volumes named db_data, deps_volume, and build_volume;
  • tmp/adoption_demo.log;
  • examples/adoption_demo/_build;
  • examples/adoption_demo/deps.

tmp/admin-ui-polish/ is preserved by default. Cleanup does not delete broad tmp/, unrelated ignored files, unrelated Docker resources, or host-wide Docker state. It never uses host-wide Docker prune commands or broad Compose volume deletion.

If project containers still exist, cleanup refuses to run and prints the matching stop command.

Repo Hygiene

After stopping and cleaning up, check local demo state:

./scripts/maintainer/repo_hygiene_check.sh --project lockspire-adoption-demo --skip-mix-ci

CI source contracts use:

./scripts/maintainer/repo_hygiene_check.sh --ci

CI keeps the existing Python smoke proof plus deterministic Docker validation. It does not run the full Docker Compose lifecycle.

Local hygiene reports adoption-demo Docker leftovers and generated artifacts with PASS, WARN, and BLOCK findings. Running active-project demo containers are BLOCK findings because the demo lifecycle is unfinished.

The intended local lifecycle is start -> smoke -> stop -> cleanup -> hygiene:

make demo
make demo-smoke
make demo-stop
make demo-clean-execute
./scripts/maintainer/repo_hygiene_check.sh --project lockspire-adoption-demo --skip-mix-ci

After cleanup, hygiene should report no demo-owned BLOCK findings. Broader checks can still report unrelated repo state, such as a dirty working tree or external CI status.

Run It Host-Local

Host-local Mix/Postgres remains a fallback for maintainers who intentionally want to run outside Docker.

From the repo root:

cd examples/adoption_demo
mix deps.get
mix ecto.setup
mix phx.server

Then open http://127.0.0.1:4100.

Troubleshooting

Port Conflict

For normal use, prefer:

make demo

For direct mode, let the launcher pick the next available port:

examples/adoption_demo/bin/docker-up --direct

If you need an explicit port:

examples/adoption_demo/bin/docker-up --direct --port 4101 --project lockspire-adoption-demo-alt

For many demos at once, use hostname mode:

examples/adoption_demo/bin/docker-up --host lockspire-demo.localhost

Readiness Failure

The Docker entrypoint waits for PostgreSQL, prepares the database idempotently, starts Phoenix/Bandit, and waits for the container-local LOCKSPIRE_DEMO_READINESS_URL defaulting to http://127.0.0.1:${PORT}. LOCKSPIRE_DEMO_BASE_URL remains the public issuer/browser URL printed in startup output and used by the smoke wrapper.

Check logs:

make demo-logs

Then reprint expected URLs after the service is healthy:

make demo-info

Traefik Network

Direct Docker mode does not require the Traefik network. Hostname routing uses LOCKSPIRE_DEMO_TRAEFIK_NETWORK.

Traefik Ports

The helper uses 127.0.0.1:80 and 127.0.0.1:8080. If those are already owned by your shared local proxy, the launcher auto-reuses a compatible existing Docker Traefik when it can infer one network. If it cannot choose safely, pass the proxy network explicitly:

LOCKSPIRE_DEMO_TRAEFIK_NETWORK=proxy examples/adoption_demo/bin/docker-up --no-proxy-start --host lockspire-demo.localhost

If you do not want a proxy, use:

examples/adoption_demo/bin/docker-up --direct --port 4101

Base URL Drift

If startup output, issuer, redirects, and smoke target disagree, align LOCKSPIRE_DEMO_BASE_URL with the URL you type into the browser. For direct Docker it should match the selected host port, such as http://127.0.0.1:4101. For Traefik it should use the hostname origin, such as http://lockspire-demo.localhost.