Quick start
Start the published standalone image with one compose file, create a record and read it back, in five minutes.
The stack runs the published image, ghcr.io/burgyn/alvo (for linux/amd64 and linux/arm64). Its edge tag
follows the repository’s main branch; Alvo is pre-v0.1, so there is no release tag yet.
Before you start
Section titled “Before you start”- Docker with Compose v2,
curlandopenssl. - Port 8080 free on this machine. The stack listens on
127.0.0.1:8080only.
Run it
Section titled “Run it”curl -fsSLO https://raw.githubusercontent.com/Burgyn/MMLib.Alvo/main/docker-compose.quickstart.ymlexport ALVO_DEMO_KEY_SECRET="$(openssl rand -hex 16)"export ALVO_ADMIN_PASSWORD="$(openssl rand -hex 12)"docker compose -f docker-compose.quickstart.yml up --wait --wait-timeout 90curl -sS localhost:8080/api/owners -H "X-Alvo-Api-Key: demo.$ALVO_DEMO_KEY_SECRET"Line by line: download the compose file; generate the secret of the stack’s one API key and the dashboard administrator’s password (compose refuses to start without either, because the image ships no credential); pull the images and start Alvo next to PostgreSQL, waiting up to 90 seconds until the descriptor is applied; list the owners, which is an empty page for now.
How long the first run takes. The first run pulls two images, Alvo’s and PostgreSQL’s, about 700 MB on disk
together (measured with a local linux/arm64 build of the image), so it takes as long as your connection needs for
that. With both images already pulled, the stack was ready about 5 seconds after up on an Apple M4 laptop with
Docker Desktop. Later runs reuse the images.
Create, then list
Section titled “Create, then list”The stack’s key is demo, with the built-in roles admin and authenticated; its secret is the
ALVO_DEMO_KEY_SECRET you exported. Create an owner, then list the owners again:
curl -sS -X POST http://localhost:8080/api/owners \ -H "X-Alvo-Api-Key: demo.$ALVO_DEMO_KEY_SECRET" \ -H "Content-Type: application/json" \ -d '{"name":"Fleet Desk Ltd","email":"office@fleetdesk.example"}'201 Created
HTTP/1.1 201 CreatedContent-Type: application/json; charset=utf-8ETag: "639273155268054220"Location: /api/owners/6e9f8524-2c97-486a-bbb3-04be686418e5
{ "id": "6e9f8524-2c97-486a-bbb3-04be686418e5", "created_at": "2026-10-11T11:38:46.805422+00:00", "created_by": "6c51972a-4b35-8468-8cdb-d8f54a226a0a", "email": "office@fleetdesk.example", "name": "Fleet Desk Ltd", "phone": null, "updated_at": "2026-10-11T11:38:46.805422+00:00", "updated_by": "6c51972a-4b35-8468-8cdb-d8f54a226a0a"}curl -sS -X GET http://localhost:8080/api/owners \ -H "X-Alvo-Api-Key: demo.$ALVO_DEMO_KEY_SECRET"200 OK
HTTP/1.1 200 OKContent-Type: application/json; charset=utf-8
{ "items": [ { "id": "6e9f8524-2c97-486a-bbb3-04be686418e5", "created_at": "2026-10-11T11:38:46.805422+00:00", "created_by": "6c51972a-4b35-8468-8cdb-d8f54a226a0a", "email": "office@fleetdesk.example", "name": "Fleet Desk Ltd", "phone": null, "updated_at": "2026-10-11T11:38:46.805422+00:00", "updated_by": "6c51972a-4b35-8468-8cdb-d8f54a226a0a" } ], "next": null, "count": null}The Location header points at the new record, and the list now holds it. These responses were captured from a real
run when the site was built, so your ids, timestamps and ETag will differ. Every write needs the
Content-Type: application/json header as well as the key.
What just happened
Section titled “What just happened”The stack serves examples/vehicle-registry,
which the image carries at /alvo/examples/vehicle-registry/vehicles.alvo.json. The descriptor has three entities:
owners, their vehicles, and the vehicles’ inspections. From that one file Alvo created the tables in PostgreSQL and serves a REST API for each entity, with the access rules the descriptor declares:
any authenticated caller may read owners, and only an admin may create one. Nobody wrote a controller, a migration or
an authorization check.
Explore the API
Section titled “Explore the API”Each descriptor generates its own OpenAPI document. While the stack runs:
http://localhost:8080/scalaris an API browser over every route this descriptor produced.http://localhost:8080/openapi/v1.jsonis the document itself, for a client generator or a coding agent.
Both are readable without a key; every data route needs one.
Open the dashboard
Section titled “Open the dashboard”The same stack serves the admin dashboard at http://localhost:8080/admin. Sign in as admin@alvo.local with the
password you exported in ALVO_ADMIN_PASSWORD; compose hands it to the host as a mounted file, and the account is
created on the first start against an empty database. The admin dashboard
walks through its screens.
Serve another example
Section titled “Serve another example”The image carries every example that applies, under /alvo/examples/<example>/<file>.alvo.json.
ALVO_DESCRIPTOR picks one, for example the smallest real backend, projects and their tasks:
docker compose -f docker-compose.quickstart.yml down --volumesALVO_DESCRIPTOR=/alvo/examples/simple-tasks/tasks.alvo.json docker compose -f docker-compose.quickstart.yml up --waitThe down --volumes deletes the database first, because a database holds the schema of the descriptor it served and
Alvo refuses to drop the old tables on its own. The demo key’s roles are built in, so it authenticates against every
example; what it may do there is that descriptor’s rules. Examples lists them all, and the
header of docker-compose.quickstart.yml says what to add for the multi-tenant field-service.
Tear down
Section titled “Tear down”Stop the stack and delete its database volume. Run it in the same shell, because compose reads
ALVO_DEMO_KEY_SECRET for every command, down included:
docker compose -f docker-compose.quickstart.yml down --volumesWhat can go wrong
Section titled “What can go wrong”| Status | Problem type | When | Fix | Returned by |
|---|---|---|---|---|
| 401 | unauthenticated | The X-Alvo-Api-Key header was sent but cannot be used: a typo, or a secret from another shell. | Send demo. followed by the secret the stack was started with. | every host |
A request with no key at all is anonymous, not unauthenticated: it reads an empty page or is refused with 403
forbidden, because no rule admits an anonymous caller.
If docker compose up fails with required variable ALVO_DEMO_KEY_SECRET is missing a value, export the secret in
this shell first, and ALVO_ADMIN_PASSWORD with it. If the stack serves another descriptor than the vehicle registry,
ALVO_DESCRIPTOR is still exported in this shell: unset ALVO_DESCRIPTOR.
Reference
Section titled “Reference”- The descriptor:
examples/vehicle-registry, and the API it generates. - Problem types:
unauthenticated,forbidden. - The compose file:
docker-compose.quickstart.yml, whose header lists every variable it reads; the host in depth:docs/architecture/host.md.
Tutorial: your first backend: write a descriptor of your own, one step at a time. To serve a descriptor you already have, see Run your own descriptor.