This document covers two perspectives:
  1. Provider — how to deploy a TEE-hardened P-Node on a Confidential VM.
  2. Consumer — how to verify a provider’s image is genuine and untampered.
For the shortest path to deploying on SecretVM, jump to SecretVM quickstart. For a conceptual overview of the two-hop trust chain, see TEE overview.

What is the TEE image?

The -tee Docker image (ghcr.io/morpheusais/morpheus-lumerin-node-tee) is a hardened version of the standard proxy-router image with all non-secret configuration baked in at build time:
  • Blockchain config (contract addresses, chain ID, blockscout URL) is frozen and cannot be changed at runtime.
  • Chat context storage is disabled (PROXY_STORE_CHAT_CONTEXT=false).
  • Logging is set to production mode (JSON, no color, minimal verbosity).
  • Environment is locked to production.
Two network variants are produced by CI/CD:
BranchImage tagNetworkChain ID
main...-tee:vX.Y.Z / ...-tee:latestBASE Mainnet8453
test...-tee:vX.Y.Z-test / ...-tee:latest-testBASE Sepolia84532
Both share identical hardened settings; only the baked-in network’s contracts and chain ID differ. The attestation manifest’s baked_env.network field identifies which variant an image is. Only 5 variables are configurable at runtime — the per-provider secrets:
VariableDescription
WALLET_PRIVATE_KEYProvider’s wallet private key
ETH_NODE_ADDRESSRPC endpoint for BASE Mainnet (e.g., Alchemy, Infura)
MODELS_CONFIG_CONTENTJSON model configuration (model IDs, backend URLs, slots)
WEB_PUBLIC_URLPublic-facing URL for the API (default: http://localhost:8082)
COOKIE_CONTENTAPI auth credentials (default: admin:admin)
Because configuration is frozen in the image, when run inside a TEE (Intel TDX or AMD SEV-SNP) the hardware can measure and attest that the software has not been modified.

Part 1: Provider — Setting up a TEE P-Node

Prerequisites

  • Funded wallet (MOR + ETH) on the target network and access to its private key.
  • An RPC endpoint for the target network (e.g. wss://base-mainnet.g.alchemy.com/v2/<your_key>).
  • Your AI model backend reachable on a private endpoint.
  • A models-config.json content (see models-config schema).
  • Access to a Confidential VM provider supporting Intel TDX or AMD SEV-SNP (e.g., SecretVM).
Mainnet vs testnet: Use the mainnet TEE image (:latest or versioned tag without -test) for production. Use testnet (:latest-test / *-test tags) on Base Sepolia. Substitute the testnet image and a Sepolia RPC if testing.

Step 1: Prepare the Docker Compose file

Each CI/CD build produces a deployed compose file that pins the TEE image by immutable SHA-256 digest. RTMR3 is computed from the exact compose content. Download docker-compose.tee.deployed.yml from the GitHub Release for your target version, or use the template at proxy-router/docker-compose.tee.yml and fill in the digest yourself. The deployed compose looks like (digest differs per version):
services:
  proxy-router:
    image: ghcr.io/morpheusais/morpheus-lumerin-node-tee@sha256:<digest>
    restart: unless-stopped
    ports:
      - 3333:3333
    volumes:
      - proxy_data:/app/data
    environment:
      - WALLET_PRIVATE_KEY=${WALLET_PRIVATE_KEY}
      - ETH_NODE_ADDRESS=${ETH_NODE_ADDRESS}
      - MODELS_CONFIG_CONTENT=${MODELS_CONFIG_CONTENT}
      - WEB_PUBLIC_URL=${WEB_PUBLIC_URL:-https://localhost}
      - COOKIE_CONTENT=${COOKIE_CONTENT:-admin:admin}
    env_file:
      - usr/.env
    networks:
      - traefik
    labels:
      - traefik.enable=true
      - traefik.http.routers.proxy-router.rule=PathPrefix(`/`)
      - traefik.http.routers.proxy-router.entrypoints=websecure
      - traefik.http.routers.proxy-router.tls=true
      - traefik.http.services.proxy-router.loadbalancer.server.port=8082
  traefik:
    image: traefik:v2.10
    command:
      - --api.insecure=false
      - --providers.docker=true
      - --providers.docker.exposedbydefault=false
      - --entrypoints.websecure.address=:443
      - --entrypoints.websecure.http.tls.options=default@file
      - --providers.file.directory=/etc/traefik/dynamic
      - --providers.file.watch=true
    ports:
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - /mnt/secure/cert:/certs:ro
    networks:
      - traefik
    configs:
      - source: tls_config
        target: /etc/traefik/dynamic/tls.yml
    env_file:
      - usr/.env
volumes:
  proxy_data: null
networks:
  traefik:
    driver: bridge
configs:
  tls_config:
    content: |-
      tls:
        certificates:
          - certFile: /certs/secret_vm_fullchain.pem
            keyFile: /certs/secret_vm_private.pem
        stores:
          default:
            defaultCertificate:
              certFile: /certs/secret_vm_fullchain.pem
              keyFile: /certs/secret_vm_private.pem
The only line that changes between versions is the proxy-router image digest. The Traefik sidecar, TLS config, and network setup stay constant.
Why digest, not tag? Tags are mutable. The digest (@sha256:...) is an immutable content hash. Using the digest in the compose file guarantees RTMR3 is cryptographically bound to one specific image binary.Exact byte content matters. The compose file must end with exactly one newline after proxy_data: null. RTMR3 is computed from the exact byte content — a single extra or missing byte changes the hash. The SecretVM portal normalizes trailing whitespace down to one newline, so pasting as-is is safe.
Published images and digests: GHCR packages.

Step 2: Prepare your secrets

Set 5 values in your TEE platform’s encrypted secrets section:
WALLET_PRIVATE_KEY=<your_private_key>
ETH_NODE_ADDRESS=wss://base-mainnet.g.alchemy.com/v2/<your_alchemy_key>
MODELS_CONFIG_CONTENT=<your_models_json_single_line>
WEB_PUBLIC_URL=https://your-public-domain.com:8082
COOKIE_CONTENT=admin:<your_secure_password>
MODELS_CONFIG_CONTENT must be a single-line JSON string:
MODELS_CONFIG_CONTENT={"models":[{"modelId":"0x626bcb19...","modelName":"hermes-4-14b","apiType":"openai","apiUrl":"http://your-model:8080/v1/chat/completions","concurrentSlots":6,"capacityPolicy":"simple"}]}
Schema: models-config.json.

Step 3: Deploy on SecretVM (or other TEE platform)

For SecretVM specifics (web portal vs CLI, advanced features, recommended platform = Intel TDX), see SecretVM quickstart. For other TEE platforms, consult their documentation for Docker Compose with encrypted secret injection. The image and compose are platform-agnostic — any environment supporting linux/amd64 Docker inside a TEE works.

Step 4: Verify the node is running

curl https://<your-node-url>/healthcheck
Expected:
{ "status": "healthy", "version": "<current-version>", "uptime": "1m30s" }
Then proceed with Register on chain (with the tee model tag).

Step 5: Verify attestation (SecretVM)

After deployment, SecretVM exposes attestation endpoints on :29343. Verify your own deployment:
  1. Quick verification at https://secretai.scrtlabs.com/attestation — paste your compose and the VM URL.
  2. Programmatic verification — see Part 2.

Part 2: Consumer — Verifying a TEE provider image

As a consumer you can independently verify a provider before using it. Verification will eventually be built into the C-Node automatically for tee-tagged models, but you can do it manually today.

Install cosign

# macOS
brew install cosign
# Linux (Debian/Ubuntu)
sudo apt install cosign
# Or: https://github.com/sigstore/cosign/releases

Step 1: Verify the image signature

Confirms the image was built by the official MorpheusAIs CI/CD pipeline.
cosign verify \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'MorpheusAIs/Morpheus-Lumerin-Node' \
  ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version>
Successful output mentions: cosign claims validated, transparency log verified, code-signing cert verified by trusted CA. The output JSON includes the exact GitHub commit, branch, and workflow that produced the image. If verification fails, do not trust the image.

Step 2: Inspect the TEE attestation manifest

cosign verify-attestation \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com \
  --certificate-identity-regexp 'MorpheusAIs/Morpheus-Lumerin-Node' \
  --type https://morpheusais.github.io/tee-attestation/v1 \
  ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version> \
  2>/dev/null | jq -r '.payload' | base64 -d | jq '.predicate'
Key fields:
FieldWhat to check
tee_imageFull image reference with @sha256: digest
tee_image_digestImmutable sha256:... of this image
compose_sha256Hash of the deployed compose (digest-pinned)
compose_image_referenceThe exact image@sha256: reference inside the compose
measurements.intel_tdx.rtmr3Expected RTMR3 — compare against live hardware quote
measurements.intel_tdx.secretvm_releaseSecretVM release the RTMR3 was computed against
build.commitGit commit that produced this image
build.run_urlDirect link to the GitHub Actions run
baked_env.networkmainnet or testnet
baked_env.PROXY_STORE_CHAT_CONTEXTShould be false
baked_env.ENVIRONMENTShould be production
baked_env.ETH_NODE_CHAIN_ID8453 or 84532
baked_env.DIAMOND_CONTRACT_ADDRESSDiamond proxy for the target network
baked_env.MOR_TOKEN_ADDRESSMOR token for the target network
runtime_secrets_onlyThe 5 variables that can differ per provider

Step 3: View all supply-chain artifacts

cosign tree ghcr.io/morpheusais/morpheus-lumerin-node-tee:<version>
Shows signatures (.sig), attestations (.att), and SBOMs (.sbom).

Step 4: Verify the running provider (SecretVM attestation)

If the provider is on SecretVM:
  1. Go to https://secretai.scrtlabs.com/attestation
  2. Paste the docker-compose.tee.yml (exact byte content — see warning above).
  3. Enter the provider’s VM URL or paste their attestation quote.
  4. Click Verify.
Three layers must pass: hardware (genuine Intel TDX / AMD SEV-SNP), VM (firmware + kernel + initramfs match a known SecretVM release), software (RTMR3: rootfs + Docker Compose match what was deployed). The expected RTMR3 is published in the signed manifest at measurements.intel_tdx.rtmr3. Compare it against the value reported by the hardware quote.

Step 5: Verify RTMR3 independently (optional)

# Check the pinned version and rootfs variant
cat .github/tee/secretvm.env
# → SECRETVM_RELEASE=v0.0.25
# → SECRETVM_ROOTFS_VARIANT=rootfs-prod-tdx

# Download the same rootfs used by CI/CD
curl -L -o rootfs-prod-tdx.iso \
  "$(grep SECRETVM_ROOTFS_TDX_URL .github/tee/secretvm.env | cut -d= -f2-)"

# Compute RTMR3 from the deployed compose + rootfs
python3 proxy-router/scripts/compute-rtmr3.py docker-compose.tee.deployed.yml rootfs-prod-tdx.iso
Output should match measurements.intel_tdx.rtmr3. If not, check that you’re using the same rootfs version as the CI/CD build.

What this guarantees (and what it does not)

See the conceptual breakdown in TEE overview, which mirrors and summarizes the per-phase guarantees and remaining gaps documented here.

Upgrading SecretVM artifacts

When SCRT Labs publishes a new SecretVM release (e.g. v0.0.25 → v0.0.26), the rootfs changes and all RTMR3 values must be recomputed. The CI/CD pipeline is fully variabilized so updating requires only editing .github/tee/secretvm.env.

When to upgrade

SCRT Labs publishes at github.com/scrtlabs/secret-vm-build/releases. No push notifications — check periodically:
curl -s https://api.github.com/repos/scrtlabs/secret-vm-build/releases \
  | jq '[.[] | {tag: .tag_name, prerelease: .prerelease, date: .published_at}] | .[0:5]'
SCRT Labs sometimes marks new releases as pre-release on GitHub while already deploying them to the SecretVM portal. The /releases/latest API only returns non-prerelease versions, so always check the full list.

How to upgrade

1

Edit secretvm.env

SECRETVM_RELEASE=v0.0.26
SECRETVM_ROOTFS_TDX_URL=https://github.com/scrtlabs/secret-vm-build/releases/download/v0.0.26/rootfs-prod-v0.0.26-tdx.iso
SECRETVM_ROOTFS_TDX_SHA256=
SECRETVM_ROOTFS_VARIANT stays rootfs-prod-tdx unless naming changes.
2

Push and run CI/CD

The pipeline downloads the new rootfs, computes its SHA-256, computes the new RTMR3, and embeds it in the signed manifest.
3

Pin the rootfs SHA-256

Copy the SHA-256 from the GitHub Actions step summary back into secretvm.env:
SECRETVM_ROOTFS_TDX_SHA256=<sha256-from-step-summary>
4

Rebuild providers

Providers running older SecretVM versions will have mismatched RTMR3. They should redeploy on the current SecretVM release.
Always use the prod rootfs variant. SecretVM runs “environment prod” even for developer-portal deployments. Using dev produces a wrong RTMR3.

Reference