Bootstrap Administration
This tutorial continues from Run Anvil Locally. It assumes the anvil-local container is running, the public API is reachable on host port 50051, and the admin API on 50052 is not published to the host.
Bootstrap is the moment a brand-new Anvil storage directory gets its first system administrator. This page shows how to inspect that first credential, how anvil-admin obtains a bearer token, how to call the private admin API without exposing it, and why production administration should move from one bootstrap credential to named, auditable admin principals. For exact command shapes, use Admin CLI. For tenant-facing command shapes, use Public CLI.
The admin CLI is supporting tooling over the private admin API. It is not a filesystem repair program, and it is not a shortcut around authentication or authorisation. Except for local helper commands such as key generation, anvil-admin sends a bearer token to the admin listener and the server checks Anvil's built-in system realm.
Prerequisites
Before starting this page, verify the local setup checkpoints:
curl -fsS http://127.0.0.1:50051/ready
docker exec anvil-local sh -c \
'test -s /var/lib/anvil/bootstrap/system-admin.json && echo "system-admin credential file exists"'
The first command proves the public plane is reachable from the host. The second proves the first-start credential file exists inside the Docker volume. Neither command exposes the admin API to the host.
If your shell does not already have a system-admin bearer token, mint one from the local-system profile created in the setup tutorial:
export ANVIL_AUTH_TOKEN="$(anvil --profile local-system auth get-token)"
printf 'received admin bearer token with %s characters\n' "${#ANVIL_AUTH_TOKEN}"
The token is short-lived. When it expires, mint a new one rather than reusing old terminal output.
What bootstrap creates
Anvil stores system administration authority in the system realm. The system realm is the built-in relationship-authorisation realm that decides who may create tenants, manage application credentials during bootstrap, grant public policies, change topology, run repairs, read admin diagnostics, read admin audit events, and rotate server-side secret envelopes.
A brand-new storage directory has no system realm yet. First-start bootstrap is the server transaction that creates that realm and creates or binds the first system administration service principal if none exists. In this local setup, that first principal is the system-admin app, and its generated credential file is /var/lib/anvil/bootstrap/system-admin.json inside the container.
An app is a credentialed identity for software, automation, or operators. It is not an S3-only concept. The generated system-admin app is powerful enough for a disposable local tutorial because the node has one operator and no production tenant data. That same shape is usually too coarse for production. Production operators should create named principals for distinct duties such as topology control, tenant provisioning, audit export, repair, secret rotation, and emergency break-glass access.
What bootstrap does not create
Bootstrap is not an API bypass. It is not a recurring admin mode. It is not a second authentication system you can turn on later.
The first-start transaction happens before public or admin requests are accepted. Once the system realm exists, every admin operation follows the ordinary path: authenticate the caller, authorise the requested relation in the system realm, validate the request, mutate Anvil-owned state, and record audit evidence. If you restart a node with first-start bootstrap settings after the system realm already exists, those settings do not mint a fresh administrator.
This matters during incident response. If an operator cannot perform an admin action after first start, the fix is to repair or rotate the principal, credential, or system-realm relation. Do not add a secret admin flag, expose the admin listener, edit storage files, or use public policy grants to emulate system administration.
Inspect the first credential without leaking it
The credential file contains a client id and client secret. It is long-lived credential material, so the tutorial checks only that it exists and is valid JSON enough to read the client id. Do not print the secret value into logs.
docker exec anvil-local sh -c \
'jq -r .client_id /var/lib/anvil/bootstrap/system-admin.json | sed "s/.*/client id present/"'
If jq is not installed in the container image, use the host-side copy from the setup tutorial:
jq -r .client_id /tmp/anvil-system-admin.json | sed 's/.*/client id present/'
Success proves the bootstrap credential file is readable in the place the server wrote it. It does not prove the admin API will accept a request. API acceptance requires a bearer token and system-realm authorisation.
Understand how anvil-admin gets a bearer token
anvil-admin sends bearer tokens to the admin API. The bearer token is a short-lived request credential. It is different from the client id and client secret stored in system-admin.json.
Token resolution follows the same broad order as the public CLI:
| |
|---|
| Sent directly to the admin API. No token exchange is performed. |
ANVIL_BOOTSTRAP_CREDENTIAL_FILE | Reads client_id and client_secret, then exchanges them for a token through the public API. |
| Uses the selected profile credentials when no token or credential file is present. |
| Selects the public endpoint used for token exchange. It does not change the admin API endpoint. |
In this tutorial, passing ANVIL_AUTH_TOKEN into docker exec makes the credential source explicit:
docker exec -e ANVIL_AUTH_TOKEN="$ANVIL_AUTH_TOKEN" anvil-local \
anvil-admin --host http://127.0.0.1:50052 audit list --limit 5
The --host value is the private admin endpoint as seen from inside the container. It is not the public endpoint, and it is not reachable from the host because the setup page did not publish port 50052.
If you omit ANVIL_AUTH_TOKEN in this local container, anvil-admin can still mint a token because the setup page supplied ANVIL_BOOTSTRAP_CREDENTIAL_FILE and ANVIL_PUBLIC_ENDPOINT as container environment variables. That fallback is convenient for local inspection, but production automation should make its credential source explicit.
Call a read-only admin operation first
Start with an admin audit read because it is read-only and easy to interpret:
docker exec -e ANVIL_AUTH_TOKEN="$ANVIL_AUTH_TOKEN" anvil-local \
anvil-admin audit list --limit 10
A successful response proves three things at once: the token is valid, the admin API is reachable inside the container, and the system realm authorises this principal to read admin audit events. It also proves the host still does not need direct admin-port exposure.
A failure tells you where to look:
Connection refused means the admin endpoint is wrong or the server is not listening where the CLI expects.
Unauthenticated means the token is missing, expired, malformed, or minted by the wrong public endpoint.
Permission denied means the principal is authenticated but lacks the required system-realm relation.
Empty output is not a failure. It can mean there are no matching audit events in the requested page.
Keep this habit for other admin work: read first, mutate only after you know which private endpoint, principal, and relation are involved.
Understand the generated credential's limits
The generated system-admin credential can mint a bearer token for the bootstrap-created system administrator. In this local tutorial that is enough to prove private admin reachability, token exchange, system-realm authorisation, and admin audit reading.
It is not a recommended daily production credential. It is the first-start credential that gets a new Anvil system out of a zero-administrator state. After named production access exists, store it in protected break-glass storage. Do not bake it into release jobs, dashboards, application containers, routine repair jobs, or tenant automation.
Creating another app is only creating identity material. anvil-admin app create can create a client id and client secret for a named app, but that does not by itself grant admin authority. Public policy grants are also not a substitute: they are public/data-plane scopes, not system-realm admin relations. See Authorisation Actions and Resources for the distinction between public policy scopes, relationship authorisation, and system-realm relations.
Plan named admin principals before production
A named production admin app should represent one duty or automation boundary. Examples include:
| Likely system-realm relations | |
|---|
| tenant, app, and public-policy management | topology repair, secret rotation |
| region, cell, node, and routing management | |
| | tenant creation, repair execution |
| repair execution and diagnostics viewing | |
| secret-encryption-key management | bucket or topology changes |
The exact relation names are documented in Admin CLI. This tutorial does not invent a command for binding those relations if the documented CLI flow does not expose one. The safe production model remains: create named service principals, bind only the system-realm relations they need through the supported admin-plane workflow, verify unrelated admin calls fail, and record audit evidence for every mutation.
Do not try to model those duties with tenant public policy grants. A tenant app can own and manage tenant resources through the public plane where delegated, but it cannot grant itself system topology authority or rewrite the built-in system realm.
Verify audit evidence after mutations
Every mutating admin command should carry an audit reason. The local setup page has not yet made mutating admin changes beyond first-start bootstrap, but the same audit stream is where you verify later tenant, topology, policy, repair, and lifecycle changes.
docker exec -e ANVIL_AUTH_TOKEN="$ANVIL_AUTH_TOKEN" anvil-local \
anvil-admin audit list --limit 20
Read the response for the principal, action, resource, request id, and stated reason. A deployment that cannot explain administrative changes is not ready to host production data.
Success and failure cues
The happy path is deliberately narrow: the credential file exists, token exchange returns a non-empty bearer token, and a read-only anvil-admin audit list works from inside the container boundary. The host admin port remains unpublished.
Common failures usually mean one of three things. If token exchange fails, inspect the public endpoint and credential source. If an admin command cannot connect, inspect ANVIL_ADMIN_ENDPOINT, --host, and server logs. If the command returns permission denied, treat it as a system-realm authorisation problem; do not expose 50052, add public scopes, or edit storage files to bypass the relation check.
Where to go next
Continue with Mesh Regions, Cells, and Nodes to register the local topology descriptors through the private admin plane. When you are ready to hand work to tenants, read Tenants, Apps, and Credentials. For production posture, keep Admin Plane and Security Hardening nearby.