Deploy VTA appliance

Deploy a VTA appliance using Affinidi Portal to get started.

By the end of this guide, you will have a running VTA provisioned through Affinidi Portal, with PNM connected and ready to use.

The VTA is hosted as a managed appliance by Affinidi. You define the configuration through the portal, and Affinidi provisions, runs, and operates the underlying infrastructure for you, inside a Trusted Execution Environment (TEE). See VTA overview for the full picture.

You need this guide if you are starting from scratch. If you already have a running VTA, skip to Create your first context.

Deployment overview

Deploying an Affinidi-hosted VTA has three phases: install the PNM CLI, deploy the VTA in Affinidi Portal, then bootstrap PNM against it. Instead of you registering an Administrator DID yourself, PNM verifies the enclave’s attestation on first connect and only installs your admin credential if it matches. This is a Trust On First Use (TOFU) bootstrap: it refuses to proceed on any mismatch.

Install PNM CLICreate VTA ConfigurationAFFINIDI PORTALBootstrap PNMConfirm AccessPIN THE ENCLAVE & CONNECT via PNM CLI
PhaseWhereWhat you do
1 - Install the PNM CLIYour machineInstall the pnm binary. You need it in Phase 3, after the VTA is deployed.
2 - Deploy the VTAAffinidi PortalCreate a VTA configuration and wait for the status to reach Complete. Copy the VTA DID, VTA URL, and PCR0 measurement.
3 - Bootstrap PNM against the VTAPNM (Personal Network Manager) CLIRun pnm bootstrap connect with the PCR0 measurement pinned, so PNM verifies the enclave before installing your admin credential. Confirm with pnm health.

Want to run the infrastructure yourself instead? See Self-hosted (open source).

Prerequisites

Step 1. Install the PNM CLI

To access and manage your VTA, you need the Personal Network Manager (PNM) CLI installed on your machine. You use it in Step 3 to connect to your VTA once it is deployed.

Install PNM

Run the command below to install a specific version of the PNM CLI from crates.io. Cargo fetches and builds the crate for you, so you do not need to clone the repository.

cargo install pnm-cli@0.16.4 --locked --registry crates-io

Confirm the installation

pnm help

You should see a list of subcommands, including bootstrap, health, keys, and contexts.

Step 2. Create the VTA in the Affinidi Portal

  1. Log in to Affinidi Portal and select your project.

  2. Go to Verifiable Trust Agent in the left sidebar.

  3. Click Create configuration and fill in the fields:

    Create VTA configuration in Affinidi Portal

    Create VTA configuration in Affinidi Portal

    FieldDescription
    Name of configurationA display name for this VTA instance in the portal.
    Description - optionalA short note on the purpose of this instance.
    Identity typeAlways did:webvh for an Affinidi-hosted VTA.
    Mediator DID - optionalAdd a mediator DID to enable DIDComm-based message routing. Must use did:web, did:webvh, or did:peer (for example, did:webvh:example.com).
    Appliance sizeChoose a size. Available sizes depend on your plan: Basic on every plan, Enhanced from Standard upward, Custom on Premium and Affinidi plans.
  4. Click Create and wait for the status to show Complete.

    VTA configuration page showing VTA DID and VTA URL after deployment

    Wait for the deployment to complete.

  5. Once complete, copy three values from the configuration page:

    ValueStarts withUsed for
    VTA DIDdid:webvh:Issuer identity on all credentials this VTA produces. You pin this in Step 3, so PNM connects only to this VTA.
    VTA URLhttps://Public REST endpoint for admin calls and integrations. Also the fallback bootstrap target while the VTA DID is still publishing.
    PCR0 measurementA hex stringThe measurement of the software running inside the VTA’s Trusted Execution Environment. You pin this in Step 3, so PNM refuses to connect to anything other than this exact build.
    VTA configuration page showing VTA DID and VTA URL after deployment

    Copy the relevant fields to continue setting up PNM.

Step 3. Bootstrap PNM against the VTA

PNM now verifies the enclave’s attestation and installs your admin credential, as described in Deployment overview above.

Back in your terminal, run:

pnm bootstrap connect \
    --vta-did     <vta-did> \
    --expect-pcr0 <pcr0>

Replace <vta-did> and <pcr0> with the VTA DID and PCR0 measurement copied from Step 2. A filled-in command looks like this:

pnm bootstrap connect \
    --vta-did     did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io \
    --expect-pcr0 8f1d3c5a7b9e...c9a1f3b5

Two independent checks run before your credential installs, and both must pass:

  • The DID pins which VTA answers. PNM resolves the did:webvh locally, verifying its self-certifying identifier (SCID) and its log, then connects to the REST endpoint that document advertises. A credential minted by a different VTA is refused.
  • PCR0 pins what is running inside it. The pinned value is checked against the enclave’s attestation quote, so a genuine VTA running a build you did not expect is refused as well.

The pinned PCR0 is also the trust anchor that replaces the digest here, which is why no --expect-digest is involved: bootstrap connect mints its bundle during the call, so no out-of-band digest can exist beforehand to compare against.

Expected output:

TEE attestation verified.
  Enclave module: i-0123456789abcdef0-enc0123456789abcdef
  PCR0:           8f1d3c5a7b9e...c9a1f3b5 (pinned ✓)

Bootstrap complete.
  VTA slug:   your-appliance.vta.affinidi.io
  Client DID: did:key:z6MkjLfAT61ZbMfUTytxjp6KnDh7hy9jE5YkQJNuouJEC4cU
  VTA DID:    did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io
  Digest:     b1946ac92492d234...7c6235b4d2611184

(pinned ✓) next to PCR0 is the line to check: it confirms the attested measurement matched the value you pinned, so the credential installed against the build you expected.

Step 4. Confirm the connection

pnm --vta <vta-slug> health

Replace <vta-slug> with the VTA slug value from Step 3’s output. --vta is required if PNM has more than one VTA registered: only the first VTA you ever bootstrap on a machine becomes the default, so pnm health alone would otherwise check the wrong one.

Expected output:

── VTA ───────────────────────────────────────────
  DID           did:webvh:QmVE1TQeCtg3aavpTqasqencJpagRr8JKdGRyoZ5Qx6kRp:your-appliance.vta.affinidi.io
                ✓ resolves (webvh)
  Mode          DIDComm + REST
  URL           https://your-appliance.vta.affinidi.io (from DID)
  Service       ✓ ok

── Authentication ────────────────────────────────
  Client DID    did:key:z6MkjLfAT61ZbMfUTytxjp6KnDh7hy9jE5YkQJNuouJEC4cU
  Token         ✓ valid (expires in 60s)

── Mediator ──────────────────────────────────────
  DID           did:webvh:QmSW59XWpXN4xNX5ZJvkYmaJBpBH7d1WN1fR6ZZG8xtjGE:your-appliance.mediator.affinidi.io
                ✓ resolves (webvh)
                ✓ pong (14ms)

── VTA DIDComm ───────────────────────────────────
  Trust-ping    ✓ pong (25ms)

If any check shows ❌ or ⚠️, see Troubleshooting below.

Troubleshooting

SymptomLikely causeFix
pnm bootstrap connect fails with --expect-digest <hex> is required (or pass --no-verify-digest to opt out with a warning)pnm is older than 0.16.3, where a pinned PCR0 became a trust anchor in its own right.Upgrade with the cargo install command in Install PNM, then re-run the command from Step 3.
pnm bootstrap connect fails with unexpected argument '--vta-did'Same cause: --vta-did was added in 0.16.3.Upgrade pnm, or use the --vta-url form until you do.
Could not resolve bootstrap endpoint for VTA DID ...The VTA’s DID log has not published yet, or this machine cannot reach it. Bootstrap always resolves locally, so a reachable resolver sidecar does not satisfy it: PNM_RESOLVER_URL and [resolver_url] are deliberately ignored for this call.Retry once the DID log is published and reachable from this machine, or bootstrap with --vta-url instead, accepting that it does not pin the VTA’s identity.
pnm bootstrap connect refuses with a PCR0 mismatchThe pinned PCR0 does not match the enclave’s live attestationDo not proceed past a mismatch. Confirm the current PCR0 value on the Affinidi Portal configuration page before retrying.
pnm bootstrap connect fails with bootstrap request failed (410 Gone): {"error":"gone: TEE first-boot carve-out has already been used"}The VTA already has an admin credential installed; the attested bootstrap endpoint is single-useExpected once bootstrap has already succeeded. Run pnm --vta <vta-slug> health to confirm the existing connection instead.
pnm health shows ❌ on Trust-ping with No response from API or the ping times outVTA DIDComm layer has stalledRestart the VTA with pnm vta restart, then wait for the confirmation VTA is back. Re-run pnm health to confirm Trust-ping returns ✓ pong.

Next steps

  Create your first context and signing key

  VTA overview

  Glossary: the terms used across these pages and in pnm output.