Skip to main content
Version: v4

Deploy on one VM with Docker Compose

The fastest production-shaped deployment: one virtual machine, four containers. It runs the same on an OVHcloud or Hetzner VPS, an EC2 instance, a Compute Engine or Azure VM. For several agents or a team, prefer Kubernetes with Helm.

Goal​

A VS Agent reachable at https://agent.example.com, with its admin API bound to the host only, data on named volumes, TLS handled automatically.

Prerequisites​

  • A VM with Docker Engine and the Compose plugin, ports 80 and 443 open, 2 GB of memory.
  • A DNS A or AAAA record for your hostname pointing at the VM.
  • The values of What VS Agent needs to run decided.

Steps​

1. Lay out the files​

Create a directory with three files. The Compose file:

examples/vs-11-docker-compose/docker-compose.yml
# VS Agent on one host: the agent, its PostgreSQL database and Redis.
# TLS and the public hostname are terminated by Caddy in front (see Caddyfile).
# Copy .env.example to .env and fill it in before `docker compose up -d`.
services:
vs-agent:
image: veranalabs/vs-agent:${VS_AGENT_VERSION:-2.0.0}
restart: unless-stopped
env_file: .env
environment:
PUBLIC_API_PORT: "3001"
ADMIN_API_PORT: "3000"
POSTGRES_HOST: postgres
POSTGRES_USER: vsagent
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
REDIS_URL: redis://redis:6379
# The admin API is reached only from this host; never publish port 3000.
ADMIN_API_TRUSTED_NETWORKS: 127.0.0.0/8,::1/128,172.16.0.0/12
volumes:
- agent-data:/root/.afj
ports:
- "127.0.0.1:3000:3000"
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy

postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: vsagent
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?set POSTGRES_PASSWORD in .env}
POSTGRES_DB: vsagent
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U vsagent"]
interval: 5s
timeout: 3s
retries: 20

redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis-data:/data
healthcheck:
test: ["CMD-SHELL", "redis-cli ping"]
interval: 5s
timeout: 3s
retries: 20

caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- caddy-data:/data
depends_on:
- vs-agent

volumes:
agent-data:
postgres-data:
redis-data:
caddy-data:

The TLS proxy configuration, with your hostname:

examples/vs-11-docker-compose/Caddyfile
# Replace agent.example.com with the host of PUBLIC_API_BASE_URL.
# Caddy obtains and renews the TLS certificate itself.
agent.example.com {
reverse_proxy vs-agent:3001
}

The environment, copied to .env and filled in:

examples/vs-11-docker-compose/.env.example
# ---- identity and corporation -------------------------------------------------
# The corporation this agent acts for (its id on the registry, from the Frontend).
VERANA_CORPORATION_ID=
# BIP-39 mnemonic of the agent's own account. Generate it once, keep it secret, back it up.
VERANA_ACCOUNT_MNEMONIC=

# ---- network --------------------------------------------------------------------
VERANA_CHAIN_ID=vna-testnet-1
VERANA_RPC_ENDPOINT_URL=https://rpc.testnet.verana.network
VERANA_INDEXER_BASE_URL=https://idx.testnet.verana.network
# The ECS ecosystem you trust for identity credentials (see Environments and endpoints).
TRUSTED_ECS_ECOSYSTEM_DIDS=

# ---- the service --------------------------------------------------------------------
# Public origin of the agent. The DID is derived from it: choose it once.
PUBLIC_API_BASE_URL=https://agent.example.com
AGENT_PUBLIC_DID_METHOD=webvh
AGENT_MODE=standalone

# Claims of the ECS Service credential the agent issues to itself.
ECS_CLAIMS_SERVICE_NAME=My First Verifiable Service
ECS_CLAIMS_SERVICE_TYPE=WEB_PORTAL
ECS_CLAIMS_SERVICE_DESCRIPTION=A service made verifiable with VS Agent on the Verana testnet.
ECS_CLAIMS_SERVICE_MINIMUM_AGE_REQUIRED=0
# Placeholders served by the agent itself; replace with your own resources later.
ECS_CLAIMS_SERVICE_LOGO_URI=https://agent.example.com/vt/default/logo.svg
ECS_CLAIMS_SERVICE_TERMS_AND_CONDITIONS_URI=https://agent.example.com/vt/default/terms.html
ECS_CLAIMS_SERVICE_PRIVACY_POLICY_URI=https://agent.example.com/vt/default/privacy.html

# Claims the agent proposes for its ECS Organization credential (the validator may adjust them).
ECS_CLAIMS_ORG_NAME=Example Company Ltd
ECS_CLAIMS_ORG_LOGO_URI=https://agent.example.com/vt/default/logo.svg
ECS_CLAIMS_ORG_REGISTRY_ID=12345678
ECS_CLAIMS_ORG_ADDRESS=1 Example Street, Example City
ECS_CLAIMS_ORG_COUNTRY_CODE=CH

# ---- optional ---------------------------------------------------------------------
# EVENTS_WEBHOOK_URL=https://backend.example.com/vs-agent/events
# EVENTS_WEBHOOK_API_KEY=
# OID4VC_CONFIG_FILE_LOCATION=/config/openid4vc.json
AGENT_LOG_LEVEL=info
ADMIN_API_LOG_LEVEL=info

# ---- database ----------------------------------------------------------------------
POSTGRES_PASSWORD=change-me

2. Generate the agent's mnemonic​

Any BIP-39 generator works; veranad keys add agent --keyring-backend test prints one and the matching address. Put the mnemonic in .env, store it in your secret manager, and never commit the file. Fund the address with a few test coins.

3. Start​

docker compose up -d
docker compose logs -f vs-agent

The first start creates the DID and publishes the DID Document; later starts load it.

Verify​

curl -s http://127.0.0.1:3000/v2/agent/health/ready # 200
curl -s http://127.0.0.1:3000/v2/agent/info | jq # did, version
curl -s https://agent.example.com/.well-known/did.json | jq .id # the same DID, from the internet

The DID Document must be reachable over HTTPS from outside the VM, otherwise nobody can resolve you.

Operations​

  • Upgrade: change VS_AGENT_VERSION in .env, docker compose pull && docker compose up -d. Read the release notes first; see Upgrade VS Agent.
  • Backup: the postgres-data and agent-data volumes, plus .env. See Back up and restore.
  • Logs: docker compose logs; levels through AGENT_LOG_LEVEL and ADMIN_API_LOG_LEVEL.

Troubleshooting​

SymptomCauseFix
Caddy cannot obtain a certificateDNS does not point at the VM yet, or port 80 is closedFix DNS or the firewall, restart Caddy
Agent exits at start naming a variableA required variable is missing or invalidCompare with the .env.example
Agent exits saying the DID location differsPUBLIC_API_BASE_URL changed after first startRestore the original value
Admin API answers 403 from your backendThe backend's address is outside ADMIN_API_TRUSTED_NETWORKSAdd its network, or switch to corporation mode

Next​