Install the platform
This page takes you from a clean machine to a local OktoID deployment that is
complete for this version. You set it up in a browser, through the setup
wizard, one step at a time. Each step writes something, then proves it with
checks before the next step opens. At the end the setup service has stopped,
its address answers 410 Gone, and the deployment has an identity provider, an
anchor organization, a proven security module, an identifier for the anchor and
a trust tree with the anchor at its root.
There is a second way in. pnpm stack:up without --setup takes every answer
from the answer file and sets the stack up without asking, which is what
Install and sign in uses. A stack is set up one way or
the other, never both. Use this page when a person should see and confirm each
decision.
Before you start
You need a machine prepared as the repository's
docs/development/getting-started.md describes:
- Node 24 and pnpm, with
pnpm installrun in the repository; - Docker with Compose;
- the names
*.local.oktoid.appresolving to127.0.0.1; - a browser that trusts the stack's development root (the next section shows where it is).
Run every command on this page from the repository root, as your own user. The installer refuses to run as root (uid 0): the setup files would then belong to root and the setup service would be given the root group.
The stack must never have been set up. If it ever ran, start from an empty one:
pnpm stack:reset
A reset deletes everything, including the development root. Trust the new one after the next start, as Install and sign in shows.
The wizard reads its first answers from the deployment's answer file,
infra/environments/local/answers.yaml, and shows them on its screens for you
to confirm or change. What you change is stored by the platform, not written
back to the file.
Start the stack with the setup wizard
pnpm stack:up --setup
The first start builds the platform images and takes several minutes. Near the end it says which way the stack is being set up, and why:
setup mode: wizard (no setup exists yet and --setup was given)
bootstrap (the unattended converge) was not run: the wizard converges
It ends with the usual addresses, then two more lines, then the commands that trust the development root:
setup: https://setup.local.oktoid.app/
sign-in code: the file <repository>/infra/compose/.state/local/setup/sign-in-code (mode 0600). It is never printed; read it where you can trust the screen.
The sign-in code is the only thing that lets anyone into setup, and setup can create the deployment's accounts. So the installer never prints it: it writes it to a file that only your user can read. The code works once, and for 24 hours.
Sign in to setup
Read the code where nobody can see your screen:
cat infra/compose/.state/local/setup/sign-in-code
It is 52 characters, the letters A to Z and the digits 2 to 7.
Open https://setup.local.oktoid.app/. The page is Sign in to setup. Paste the code into Sign-in code from the installer and choose Sign in. Letter case, spaces and hyphens do not matter.
Signing in spends the code and deletes its file. From now on you hold a setup session:
- it ends after 30 minutes without activity, and after 4 hours however active you are. Two minutes before the idle limit, the page asks whether to stay signed in;
- when it ends, make a new code with
pnpm stack:setup-codeand sign in again. Setup opens at the step where it stopped, and nothing you saved is lost.
Five wrong codes stop setup. The fifth is answered with Too many wrong
codes. Setup has stopped, and the service exits. From then on the setup
address shows Setup is not running, and reloading it does not start the
service. Run pnpm stack:setup-code, as that page says, or pnpm stack:up
again: either writes a new code and starts the service, and nothing you saved
is lost. If the five attempts were
not yours, someone else can reach this address; stop and find out who before
going on.
If the page says the code has already been used and you did not use it, someone else did. Stop, and tell whoever is responsible for the machine's security.
How every step works
The wizard's steps carry the full setup's numbers, 1 to 12. This version has 1 to 5, 8, 9, 10 and 12; the list on the left says which numbers later versions add, and the numbers never change.
Each screen has the same shape:
- The form shows what the step will write, filled in from the answer file.
A secret is never typed: a secret field takes a reference to the secret store,
such as
secret://oktoid/mail/smtp, and setup refuses a value. - The action (its label differs per step) writes the step and runs its checks. The checks appear one by one as they finish.
- The checks each prove one thing and show Passed, Failed, Needs attention, Waiting, Not run or, when a run was cut off, Interrupted. When a check fails, a banner names it, the row opens with what setup expected, what it got, the cause, and what to do. The checks after it show Not run.
- Continue opens only when every check of the step has passed. Step 12 is the exception, and says why.
A finished step is read-only. You can open it again from the list on the left to read it, but setup will not change what it wrote.
Step 1: verify the identity provider
The page is Verify the identity provider. On a local stack the provider is Keycloak, and the connection panel says Keycloak, deployed by the installer. This step only reads: nothing is written to the provider or to the platform.
Choose Run the checks. Two checks run:
| Check | What it proves |
|---|---|
| The identity provider answered, and accepted the administrator credential | The platform's own network reaches the provider, and the credential setup was given works |
| What setup may do in the provider was read | Which of the four management permissions the credential holds: with all four, setup creates everything itself |
On a stack that was never set up, the second check passes and says that the credential may create the realm, which does not exist yet: what it may do inside the realm is checked when step 2 creates it. That is the expected result here, not a warning.
When both pass, choose Continue.
If the provider does not answer, the cause is provider_unreachable. Setup
reaches the provider from inside the stack, not from your browser, so an address
that opens in your browser can still be closed to the platform. Check the
provider's log with pnpm stack:logs keycloak and run the checks again.
Step 2: claims, mail, clients and the root account
The page is Claims, mail, clients and the root account. This is the first step that writes, and it writes only to the identity provider: the realm, the claim mappings, the mail settings, the six clients and the root account, the person who runs this deployment.
Check the root account's Full name and Email address
(root@local.oktoid.app on the local stack). The box One person runs and
governs this stack is ticked, because the local answer file gives the root
account the SOLE_ADMIN bundle. Only a development stack offers this box; under
staging and production it does not exist and two people are required.
Choose Apply this step. Four checks run: the two from step 1 again, then:
| Check | What it proves |
|---|---|
| The realm, the claims, the mail settings and the clients were written and read back | Everything this step wrote is at the provider as setup wrote it |
| The root account was created and its invitation sent | The account exists, and the mail server took the invitation |
The invitation goes to Mailpit, which catches every message on a local stack: http://127.0.0.1:18025. You do not need to follow it now. Step 12 reads whether the root account has signed in, and setup can be completed before it has; Install and sign in, steps 2 and 3, shows how to follow the invitation.
If the root account exists and the invitation was not sent, the page says so and offers Send the invitation again. Everything else in the step was written, and you can continue.
Choose Continue.
Step 3: name the deployment and create the anchor
The page is Name the deployment and create the anchor. The anchor is the organization at the root of the deployment's registry: every other organization sits beneath it. This step writes it, the root account's grant on it, and a grant for each machine client that acts for it. Nothing is published yet.
| Field | On the local stack | |
|---|---|---|
| Deployment name | oktoid | Permanent. Lowercase letters, digits and hyphens: it becomes the first part of every path and part of every certificate name |
| Domain | local.oktoid.app | Permanent. It is inside every credential the deployment issues |
| Legal name of the anchor organization | OktoID Local | Shown as the trust anchor's name. Its spelling can be corrected later |
| This deployment runs with no connection to outside networks | not ticked | Air-gapped. Step 8 then refuses a public ledger |
The deployment name and the domain cannot be changed after this step without reissuing everything made from them. Read them twice.
Choose Create the anchor. A dialog asks Create the anchor with these
permanent values? Type the deployment name, oktoid, and choose Create the
anchor again. Two checks run:
| Check | What it proves |
|---|---|
| The deployment's answers are complete and valid | The answers as a whole pass the answer file's rules |
| The anchor organization, the root account's grant and the clients' grants were written | The registry holds them |
Choose Continue.
If a run is cut off, for example because the service restarted, the checks that were being made show Interrupted, not Failed: they have no result. Choose Run the step again; nothing is written twice. The name and domain stay fixed on that second run, because the organization may already exist.
Step 4: create the first accounts
The page is Create the first accounts. It shows the root account, read-only: it was created at step 2. It may create one more account, the governing account: the trust anchor administrator, who admits organizations to the trust tree, sets policy and signs statements, and has no access to infrastructure.
On a deployment that keeps duties apart, the governing account is required, and it must be a different person from the root account, with their own address. The root account runs the platform and cannot govern it; a permission check refuses it.
On the local stack the root account also governs, so the panel says Optional on this stack. Choose one:
- Continue without one. The root account governs. The step's audit record says the governing account was skipped, and in which environment.
- Give a Full name and Email address and choose Create the governing account. Three checks run: somebody is named who can govern; the governing account was created and granted; its invitation was sent. The invitation arrives in Mailpit.
Then choose Continue.
Step 5: connect the security module
The page is Connect the hardware security module. The checks run as soon as the page opens. On the local stack the module is SoftHSM, a software module that stands in for hardware.
What this step proves is the module that credential (the service that will
hold the deployment's signing keys) signed in to when it started: setup sends
credential a request, and credential answers from its own session. The setup
service holds no module PIN and opens no session of its own. So this step needs
credential running.
| Check | What it proves |
|---|---|
| The PKCS#11 library was loaded | credential loaded the module's library |
| A session was opened and signed in | credential holds a signed-in session on its token |
| The module's identification was read | The module reports its manufacturer and model |
| A test key was generated, used to sign, verified and destroyed | The module can make a key and sign with it, and the test key is gone afterwards |
The module shows the PKCS#11 library path and the slot, the token's label as
credential's configuration names it. Under the checks, Recorded about the
module shows the Identification the module reported and the Key store
as tested and available, and Partitions lists the stack's three tokens (oktoid,
oktoid-agent, oktoid-audit) with what each holds. Found means the module
listed that token. Not looked for means the library credential loaded does
not see it, which is normal for SoftHSM, and says nothing about whether it
exists. Setup cannot change the partitions in this version.
When all four pass, choose Continue.
If the session check fails because the PIN was refused, the page offers no
Run the checks again: credential signs in only when it starts, and never
sends the PIN again for a check, so repeated checks cannot lock the module. Correct
the PIN reference in credential's configuration and restart credential; the
checks then run again when you reopen the page.
Step 8: verifiable data registries
The page is Verifiable data registries. It decides how identifiers are published, and it runs no checks: Continue saves the choices, and step 9 applies them.
- The anchor's identifier uses
did:webvh, which keeps a signed history of every change. It is fixed once created: every organization's trust chains to this identifier, so a change of its key must be provable. Setup offers no other method for the anchor. - Identifier methods for organizations decides what organizations admitted
later may choose.
did:webvhis always on and preselected.did:webis also on when the step opens, unless the answer file'sidentifiers.methodsleaves it out; the local answer file does not set it. Read thedid:webcard before you keep it: it keeps no history and has no key pre-rotation, so nobody can tell a key change the organization made from one made by someone who took over its domain. To turn it off, clear thedid:webcard before you choose Continue. Turned off,did:webis refused for every organization that asks for it; identifiers that already exist keep their method. - Base address for identifiers is inside every identifier minted here.
- Public ledgers (cheqd, Hedera) arrive in a later version and are refused until then.
The base address and the anchor's method become permanent at step 9, and the methods card becomes read-only then. Choose Continue.
Step 9: bring up the anchor's root agent
The page is Bring up the anchor's root agent. It shows what it takes from
earlier steps, how wallets are stored (one store with a profile for each
organization, set once for the deployment), and what it creates: the anchor's
place in the shared agent, its identifier keys, generated in the module and never
exported, and the anchor's identifier document, published on your domain with
its history, and also as did:web for verifiers that read only that form.
Three values become permanent here: the identifier method, the base address
and how wallets are stored. Choose Bring up the agent, type the deployment
name, oktoid, and choose Bring up the agent again. Four checks run:
| Check | What it proves |
|---|---|
| The identifier methods and registries are declared, and the anchor's method is permitted | Step 8's answers are complete and consistent |
| The shared agent was registered, or found registered | The agent that holds the anchor's keys is known to the platform |
| A test object was written to each registry and read back | Each registry takes a write and serves it |
| The permitted methods were stored, and the anchor's identifier published | The identifier exists and is published |
The last check's detail names the methods organizations may use: methods webvh, web when did:web was left on at step 8, methods webvh when it was turned off.
The page then shows the anchor's identifier as published:
did:webvh:<scid>:oktoid.identity.local.oktoid.app. Setup resolved it with the
platform's own resolver. For a check by a resolver OktoID did not write, run this
on the server, with the identifier the page shows:
pnpm oktoid resolve-identifier did:webvh:<scid>:oktoid.identity.local.oktoid.app
Create an identifier explains what it prints.
Choose Continue.
Step 10: found the trust tree
The page is Found the trust tree. It admits the anchor to its own trust tree and publishes version 1 of the governance framework: the document that says who governs the deployment and what each role must meet to be admitted. It comes after step 9 because the framework names the anchor by the identifier step 9 created; setup fills the identifier in, and it is never typed.
What the anchor publishes about itself:
- Contact mailbox: at least one, at most eight. Each is published as written,
so use a mailbox for a role, never a person's address.
The local stack's is
trust@local.oktoid.app. - Where a dispute is taken: an
httpsaddress, published in the framework.
The governance framework, version 1: on the local stack the form is filled
in from the framework the answer file names,
infra/environments/local/framework.v1.yaml. When the answer file names none,
setup proposes a first version from the anchor's name and its first contact;
edit it to say what your governing authority has agreed.
Version 1 is published once and never edited. A change is a new version, made outside setup.
Choose Found the trust tree. Three checks run:
| Check | What it proves |
|---|---|
| The anchor's contacts, dispute route and framework are all declared | They are given together, as the framework requires |
| The framework was read and is a valid framework | It passes the framework's schema |
| The anchor was admitted to its own trust tree, and framework version 1 published | The anchor's signed statement about itself and the framework are public |
Choose Continue.
Step 12: publish and self-test
The page is Publish and self-test. Each earlier step checked its own work when it finished; this one reads what setup made once more, from outside where it can, and then records the configuration as applied. Until that record is written the step is open and setup cannot be completed.
Choose Run the self-test. Six checks run:
| Check | What it proves |
|---|---|
| The stored answers are the configuration's authority | The answers setup holds, not the answer file, are what the deployment converges from |
| The stored configuration is the answers setup holds | The newest stored configuration version is what you confirmed on the screens |
| The anchor's trust statement was fetched through its published address and verified | Fetched the way another organization would fetch it, and its chain and governance framework verified. It is self-asserted: the anchor is its own trust anchor |
| The anchor's identifier was resolved by the platform, from the registry it publishes from | The identifier resolves. This is the platform checking itself; the page names the independent check, as step 9 does |
| The root account signed in for the first time | Read from the platform. It shows Waiting until the root account accepts its invitation |
| The configuration was recorded as applied, and its audit record acknowledged | The deployment holds this configuration, and the audit trail took the record |
The last check runs only when none before it failed. Two results do not hold the step open:
- The root account's sign-in shows Waiting until the person the step 2 invitation names sets a password and a second factor and signs in once. Setup cannot do that part. How the invitation is accepted, under the row, lists what that person does. You can complete setup without it; after they sign in, Run the checks again reads the sign-in back. That second run writes nothing.
- The trust statement shows Needs attention when its address did not answer. That says nothing about whether it verifies. Run the checks again once the edge answers, or run the command the banner names on the server, before you complete setup.
When a check fails, the configuration is not recorded as applied and the step stays open. For a failure of the trust statement or of the identifier, the banner offers Open step 10 or Open step 9. Fix the cause and choose Run the self-test again.
Once the step is done, Published lists the anchor's trust statement, its
identifier document, and the same document as did:web, each Live when this
run read it back. Step 12 is the last screen, so it has no Continue: its main
action is now Complete setup.
Complete setup
Choose Complete setup on step 12. The dialog Complete setup for oktoid?
says that the deployment becomes operational for what this version has, and that
setup closes: this address stops answering. When the root account has not signed
in yet, the dialog says so too: nobody can run the deployment until that person
accepts the invitation and signs in, and once setup closes this page can no
longer send the invitation again. If the invitation may need sending again,
choose Not yet and do it from step 2 first; after completion,
pnpm stack:root-invite sends it again from the server. Otherwise choose
Complete setup.
The page Setup is complete for this version lists what was set up and the step that did each, what later versions add (the certificate authority and the key ceremony, the document signer certificate, the credential catalog, a separate platform operator account, the full self-test and the seal), and what happens next. It is shown once: reloading it shows only that setup is closed.
Completion happens in a fixed order, and the first two are what close setup:
- The platform records that setup is complete.
- The setup service refuses every request from then on, the sign-in included.
- It writes a marker for the edge, then exits.
- The edge answers
410 Goneon the setup address.
Check both from the terminal:
curl -sS -o /dev/null -w '%{http_code}\n' \
--cacert infra/compose/.state/local/ca/root.pem https://setup.local.oktoid.app/
docker ps -a --filter name=oktoid-local-setup --format '{{.Names}} {{.Status}}'
The first prints 410. The second shows the setup container as Exited (0).
Then check the whole stack:
pnpm stack:verify
If its line http://api.local.oktoid.app/ redirects to HTTPS fails with no answer on port 80, the edge container is the cause, not setup. pnpm stack:up
checks ports 80 and 443 and restarts the edge once when one does not answer, so
look back at its output for a WARNING: port ... still does not answer line.
The repository's docs/development/troubleshooting.md has the entry, under
TLS, with the restart to run by hand.
From now on pnpm stack:up does not start the setup service. It says so:
setup mode: unattended (setup is complete; the stored configuration is converged and the setup host is not started)
and converges the stored configuration, not the answer file. To change that
configuration as a file, export it and import the edited copy, as the
repository's docs/development/local-stack.md describes under The setup
service. pnpm stack:up --setup is now refused with exit 2: reopening setup
is its own command.
When a later version adds a step
Setup opens again only when you install a version of the platform whose setup has a step this deployment has not done, and only by a command you run:
pnpm stack:setup-reopen
The command compares the version that completed setup with the version you installed. If the installed one adds nothing, it refuses with exit 78, names both versions, and writes nothing. Otherwise it records the reopening in the audit trail first, then writes a new sign-in code and prints only its file's path:
sign-in code: <repository>/infra/compose/.state/local/setup/sign-in-code (mode 0600; installer run <id>)
Sign in as in Sign in to setup. The page is Setup was reopened at step n: the steps done before are listed as done and are not changed; the new steps are marked New in this version. The deployment stays operational while they are open. Complete setup again when they pass.
When something goes wrong
| You see | It means | Do this |
|---|---|---|
setup is complete (the marker ... exists) and exit 2 from pnpm stack:up --setup, at once | Setup is complete | Nothing, or pnpm stack:setup-reopen after installing a version that adds a step |
stack:up prints sign-in code: not changed | A code was used and setup is not complete, so no new code is minted while someone may be mid-wizard | If your session ended, pnpm stack:setup-code |
| Your setup session ended after 30 minutes without activity, or reached its 4-hour limit | The session is over and the code is spent | pnpm stack:setup-code, then sign in again |
| Too many wrong codes. Setup has stopped, or Setup is not running at the setup address | Five wrong codes stopped the service | pnpm stack:setup-code or pnpm stack:up for a new code; either starts the service again |
| This code has expired | It is more than 24 hours old | pnpm stack:up for a new code |
| This code was not issued by an installer command | The code file was not written by the installer | Do not use it. Run pnpm stack:up, and if you did not put that file there, tell your security contact |
| This step is not open yet | A step before it is not done | Setup returns you to the step to do now |
| This step is done and cannot be changed here | A finished step is read-only in setup | Export the stored configuration, edit it, and import it (docs/development/local-stack.md) |
| Another run is still in progress | A step is running | Wait; the page shows its checks as they finish |
stack:setup-code refused with exit 2 | No setup was started, or it is complete | pnpm stack:up --setup to start one; pnpm stack:setup-reopen to reopen one |
What to do next
- Sign in as the root account and make a first API request:
Install and sign in, from step 2. Set
CAandAPIfirst, as the end of its step 1 shows. - Admit the first organization to the trust tree, as the trust anchor administrator: Admit an organization and publish it.