walt.id Identity on Ubuntu 24.04 on Azure User Guide
Overview
This image runs walt.id Identity 0.22.0, the open source platform for decentralized identity and verifiable credentials, on Ubuntu 24.04 LTS. walt.id Identity gives you the building blocks to issue, hold and verify W3C Verifiable Credentials using open standards including OpenID for Verifiable Credential Issuance (OID4VCI), OpenID for Verifiable Presentations (OID4VP) and Decentralized Identifiers (DIDs).
The image ships the three core backend services of the stack, each run as its pinned upstream container (by immutable digest) under a systemd service that starts it on boot and restarts it on failure:
- The Issuer API on port 7002 issues Verifiable Credentials to wallets over OID4VCI
- The Verifier API on port 7003 requests and verifies Verifiable Presentations over OID4VP, running configurable credential and presentation policies
- The Wallet API on port 7001 is a server side holder wallet that can receive, store and present credentials, backed by a local SQLite database
What is included:
- walt.id Identity 0.22.0 (official
waltid/issuer-api,waltid/verifier-apiandwaltid/wallet-apiimages, pinned by digest), run by systemd - The Issuer API and Verifier API served on
:7002and:7003 - The Wallet API on
:7001, bound to loopback so the credential store is not exposed to the internet by default - A per-VM wallet administrator account and per-VM wallet signing keys generated on first boot and recorded in a root-only file
- A built-in end-to-end self-test that issues, holds and verifies a real credential
docker.service,waltid-issuer.service,waltid-verifier.serviceandwaltid-wallet.serviceas systemd units, enabled and active- No administrator baked into the image, and 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up for heavier use. NSG inbound: allow 22/tcp from your management network, and 7002/tcp and 7003/tcp for the Issuer and Verifier APIs. The Wallet API on 7001 is bound to loopback and is not exposed. The services speak plain HTTP; for production, front them with your own domain and TLS and set the service base URLs accordingly (see the final step).
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for walt.id Identity by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22), 7002 and 7003. Then Review + create then Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name waltid-identity \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name waltid-identity --port 7002 --priority 1010
az vm open-port --resource-group <your-rg> --name waltid-identity --port 7003 --priority 1020
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
Step 4 - Confirm the services are running
The stack runs as four systemd services: docker, waltid-issuer, waltid-verifier and waltid-wallet. Confirm they are active and that the Issuer and Verifier are listening on their public ports while the Wallet stays on loopback:
systemctl is-active docker waltid-issuer waltid-verifier waltid-wallet
sudo ss -tlnp | grep -E ':7001|:7002|:7003' | awk '{print $4}' | sort -u
Expected output:
active
active
active
active
127.0.0.1:7001
*:7002
*:7003

Step 5 - Retrieve your per-VM wallet credentials
The image ships with no administrator. On the first boot of every VM, waltid-firstboot.service generates a unique wallet administrator account and unique wallet signing keys, and records them in a root-only file. First confirm first boot completed, then read the file:
systemctl is-active waltid-firstboot.service
sudo stat -c '%n mode=%a owner=%U:%G' /root/waltid-credentials.txt
sudo cat /root/waltid-credentials.txt
The file contains WALLET_ADMIN_EMAIL, WALLET_ADMIN_PASSWORD, and the Issuer, Verifier and Wallet API URLs. Keep the password safe - it is unique to this VM.

Step 6 - Run the built-in end-to-end self-test
The image includes a self-test that exercises the whole platform: it issues a Verifiable Credential from the Issuer API, has the Wallet API claim it, asks the Verifier API for a presentation, has the Wallet present the credential, and confirms the Verifier reports the presentation as valid.
sudo /usr/local/sbin/waltid-selftest.sh
Expected final line:
SELFTEST_OK issue->hold->verify verified=true

Step 7 - Issue a Verifiable Credential with the Issuer API
The Issuer API issues credentials over OID4VCI. This example mints a fresh issuer key and DID with the onboarding endpoint, then issues an OpenBadge credential; the response is an openid-credential-offer URL a wallet can claim:
ISSUER=http://127.0.0.1:7002
ONB=$(curl -s -X POST "$ISSUER/onboard/issuer" -H 'Content-Type: application/json' \
-d '{"key":{"backend":"jwk","keyType":"secp256r1"},"did":{"method":"jwk"}}')
IKEY=$(echo "$ONB" | jq -c '.issuerKey'); IDID=$(echo "$ONB" | jq -r '.issuerDid')
curl -s -X POST "$ISSUER/openid4vc/jwt/issue" -H 'Content-Type: application/json' \
-d "$(jq -n --argjson ik "$IKEY" --arg did "$IDID" '{
issuerKey:$ik, issuerDid:$did,
credentialConfigurationId:"OpenBadgeCredential_jwt_vc_json",
credentialData:{"@context":["https://www.w3.org/2018/credentials/v1"],type:["VerifiableCredential","OpenBadgeCredential"],name:"Example badge",credentialSubject:{type:["AchievementSubject"]}},
mapping:{id:"<uuid>",issuer:{id:"<issuerDid>"},credentialSubject:{id:"<subjectDid>"},issuanceDate:"<timestamp>",expirationDate:"<timestamp-in:365d>"},
authenticationMethod:"PRE_AUTHORIZED"}')"; echo
Expected output (an offer URL a wallet can claim):
openid-credential-offer://?credential_offer_uri=http%3A%2F%2F<vm-ip>%3A7002%2Fdraft13%2FcredentialOffer%3Fid%3D...

Step 8 - Verify a presentation with the Verifier API
The Verifier API creates a presentation request over OID4VP and runs policies against the presentation. Create a request for an OpenBadge credential:
VERIFIER=http://127.0.0.1:7003
curl -s -X POST "$VERIFIER/openid4vc/verify" -H 'Content-Type: application/json' -H 'responseMode: direct_post' \
-d '{"request_credentials":[{"format":"jwt_vc_json","type":"OpenBadgeCredential"}]}'; echo
The response is an openid4vp://authorize URL a wallet resolves and answers; the verification result for the session is available at GET /openid4vc/session/<state>. The self-test in Step 6 drives this whole exchange automatically.
Step 9 - Use the Wallet API
The Wallet API is a server side holder wallet, bound to loopback so the credential store is not exposed. Reach it on the VM, or from your workstation over an SSH tunnel:
ssh -L 7001:127.0.0.1:7001 azureuser@<vm-public-ip>
# then open http://127.0.0.1:7001/swagger and log in with the
# WALLET_ADMIN_EMAIL / WALLET_ADMIN_PASSWORD from Step 5
Step 10 - Production hardening
The Issuer and Verifier serve plain HTTP and embed their own base URL into credential offers and presentation requests, so relying wallets must be able to reach that address. For production:
- Put the Issuer and Verifier behind your own reverse proxy with TLS and a real domain name
- Set the base URLs to your public HTTPS host by editing
baseUrlin/etc/waltid/issuer/config/issuer-service.confand/etc/waltid/verifier/config/verifier-service.conf, thensudo systemctl restart waltid-issuer waltid-verifier - Restrict the NSG so only the hosts that need the Issuer and Verifier can reach
7002and7003 - Keep the Wallet API on loopback, or expose it only through an authenticated gateway
The base image is fully patched at build time with unattended security upgrades enabled, so the OS keeps receiving security updates. For help, contact 24/7 cloudimg support.