Skip to content
Alvo is pre-v0.1: the image runs from its edge tag, and no NuGet package or release is published yet.Pre-v0.1: no release yet.Roadmap and status

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.

  • Docker with Compose v2, curl and openssl.
  • Port 8080 free on this machine. The stack listens on 127.0.0.1:8080 only.
Terminal
curl -fsSLO https://raw.githubusercontent.com/Burgyn/MMLib.Alvo/main/docker-compose.quickstart.yml
export 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 90
curl -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.

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:

Request
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

Response
HTTP/1.1 201 Created
Content-Type: application/json; charset=utf-8
ETag: "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"
}
Request
curl -sS -X GET http://localhost:8080/api/owners \
-H "X-Alvo-Api-Key: demo.$ALVO_DEMO_KEY_SECRET"

200 OK

Response
HTTP/1.1 200 OK
Content-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.

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.

Each descriptor generates its own OpenAPI document. While the stack runs:

  • http://localhost:8080/scalar is an API browser over every route this descriptor produced.
  • http://localhost:8080/openapi/v1.json is the document itself, for a client generator or a coding agent.

Both are readable without a key; every data route needs one.

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.

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:

Terminal window
docker compose -f docker-compose.quickstart.yml down --volumes
ALVO_DESCRIPTOR=/alvo/examples/simple-tasks/tasks.alvo.json docker compose -f docker-compose.quickstart.yml up --wait

The 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.

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:

Terminal
docker compose -f docker-compose.quickstart.yml down --volumes
StatusProblem typeWhenFixReturned by
401unauthenticatedThe 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.

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.