Skip to main content

Docker Deployment

Deploy AgentVisor™ using Docker and Docker Compose.

Building Images

Using the CLI

# Build a combined image
agentvisor build ./my-agent -t my-agent:v1

# Build with custom base images
agentvisor build ./my-agent \
-t my-agent:v1 \
--host-image ghcr.io/manetu/agentvisor/agentvisor-host:latest \
--guest-image ghcr.io/manetu/agentvisor/agentvisor-guest:latest-python

# Build and push
agentvisor build ./my-agent \
-t registry.example.com/my-agent:v1 \
--push

Multi-Architecture Builds

Build images that run on both amd64 (Intel/AMD) and arm64 (Apple Silicon, AWS Graviton):

# Build for multiple platforms and push manifest list
agentvisor build ./my-agent \
-t registry.example.com/my-agent:v1 \
--platform amd64,arm64 \
--push

The resulting image is an OCI manifest list that automatically selects the correct architecture when pulled:

# On amd64 host - pulls amd64 image
docker pull registry.example.com/my-agent:v1

# On arm64 host - pulls arm64 image
docker pull registry.example.com/my-agent:v1

# Inspect manifest list
docker manifest inspect registry.example.com/my-agent:v1
Multi-arch best practices
  • Always use --push with multi-arch builds (manifest lists require a registry)
  • Build and push in CI/CD rather than locally for consistent results
  • Test on both architectures before deploying to production

Base Images

ImageDescription
ghcr.io/manetu/agentvisor/agentvisor-host:latestHost runtime with gVisor
ghcr.io/manetu/agentvisor/agentvisor-guest:latest-pythonPython 3.12 guest base for LangGraph agents
ghcr.io/manetu/agentvisor/agentvisor-guest:latest-minimalMinimal guest for interactive exec mode

To use a different Linux distribution as the base (e.g. Chainguard/Wolfi for SBOM or FIPS requirements), see Custom Base Images.

Running Containers

License required

Built images require a valid Manetu license key. Pass it via -e AGENTVISOR_LICENSE_KEY or use a secrets manager. See the Licensing guide for Docker Compose and Kubernetes patterns.

docker run --security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
-p 8090:8090 \
-e AGENTVISOR_LICENSE_KEY="${AGENTVISOR_LICENSE_KEY}" \
my-agent:v1

Rootless Mode (Default)

docker run --security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
-p 8090:8090 \
my-agent:v1
Why --security-opt seccomp=unconfined --security-opt apparmor=unconfined?

AgentVisor uses gVisor to sandbox the AI agent process. gVisor's rootless mode creates a user namespace via CLONE_NEWUSER, which two host mechanisms can block:

  • seccomp: Docker's default RuntimeDefault profile may block the syscall
  • AppArmor: Ubuntu 24.04+ and many cloud Kubernetes distributions (GKE, AKS) enforce AppArmor policies that restrict unprivileged user namespaces by default

Both flags allow that syscall without granting any other elevated privileges — the container runs as uid 1000 throughout.

Privileged Mode

Opt-in when you need maximum I/O performance (DirectFS) or lack unprivileged user namespace support:

docker run --user root --privileged \
-p 8090:8090 \
-e AGENTVISOR_GUEST_ROOTLESS=false \
my-agent:v1
Privilege Dropping

In privileged mode, the AgentVisor host runtime starts as root only to initialize the gVisor sandbox, then immediately drops privileges to uid/gid 1000. The elevated privileges are only used during the brief startup phase; all subsequent operations run as an unprivileged user.

Rootless trade-offs

Rootless mode uses gVisor's 9P filesystem instead of DirectFS, which may have some performance impact. See Sandbox Modes for details.

With Policy Files

# Rootless mode (default)
docker run --security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
-p 8090:8090 \
-v $(pwd)/policies:/etc/agentvisor/policies:ro \
-e AGENTVISOR_AUTHZ_TYPE=embedded \
-e AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=/etc/agentvisor/policies/domain.yml \
my-agent:v1

# Privileged mode (opt-in)
docker run --user root --privileged \
-p 8090:8090 \
-v $(pwd)/policies:/etc/agentvisor/policies:ro \
-e AGENTVISOR_AUTHZ_TYPE=embedded \
-e AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=/etc/agentvisor/policies/domain.yml \
-e AGENTVISOR_GUEST_ROOTLESS=false \
my-agent:v1

With External Temporal

docker run --security-opt seccomp=unconfined \
--security-opt apparmor=unconfined \
-p 8090:8090 \
-e AGENTVISOR_TEMPORAL_TARGET=temporal.example.com:7233 \
my-agent:v1

Docker Compose

Development Setup

# docker-compose.yml
services:
temporal:
image: temporalio/auto-setup:latest
ports:
- "7233:7233"
- "8233:8233" # UI
environment:
- DB=memory

agentvisor:
build:
context: .
dockerfile: Dockerfile
ports:
- "8090:8090"
environment:
- AGENTVISOR_TEMPORAL_TARGET=temporal:7233
- AGENTVISOR_GUEST_SANDBOX=none
volumes:
- ./my-agent:/app/agent:ro
- ./policies:/etc/agentvisor/policies:ro
depends_on:
- temporal
Sandbox mode in production

Images built with agentvisor build use gVisor by default. These production examples deploy with gVisor for maximum isolation. Docker sandbox mode (set AGENTVISOR_GUEST_SANDBOX=docker) is also production-grade and works on any host that supports Docker. See Sandbox Modes for the full comparison.

Production Setup — gVisor Privileged (Opt-In)

Use privileged mode when you need DirectFS performance or lack unprivileged user namespace support:

services:
temporal:
image: temporalio/auto-setup:latest
ports:
- "7233:7233"
environment:
- DB=postgresql
- POSTGRES_HOST=postgres
depends_on:
- postgres

postgres:
image: postgres:15
environment:
- POSTGRES_USER=temporal
- POSTGRES_PASSWORD=temporal
volumes:
- temporal-data:/var/lib/postgresql/data

agentvisor:
image: my-agent:v1
user: root
privileged: true
ports:
- "8090:8090"
environment:
- AGENTVISOR_LICENSE_KEY=${AGENTVISOR_LICENSE_KEY}
- AGENTVISOR_TEMPORAL_TARGET=temporal:7233
- AGENTVISOR_GUEST_ROOTLESS=false
- AGENTVISOR_AUTHZ_TYPE=embedded
- AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=/etc/agentvisor/policies/domain.yml
volumes:
- ./policies:/etc/agentvisor/policies:ro
depends_on:
- temporal
deploy:
resources:
limits:
memory: 2G
cpus: "2"

volumes:
temporal-data:

Production Setup — gVisor Rootless (Default)

Rootless mode requires no elevated capabilities. This is the recommended default:

services:
temporal:
image: temporalio/auto-setup:latest
ports:
- "7233:7233"
environment:
- DB=postgresql
- POSTGRES_HOST=postgres
depends_on:
- postgres

postgres:
image: postgres:15
environment:
- POSTGRES_USER=temporal
- POSTGRES_PASSWORD=temporal
volumes:
- temporal-data:/var/lib/postgresql/data

agentvisor:
image: my-agent:v1
security_opt:
- seccomp=unconfined
- apparmor=unconfined
ports:
- "8090:8090"
environment:
- AGENTVISOR_LICENSE_KEY=${AGENTVISOR_LICENSE_KEY}
- AGENTVISOR_TEMPORAL_TARGET=temporal:7233
- AGENTVISOR_AUTHZ_TYPE=embedded
- AGENTVISOR_AUTHZ_EMBEDDED_POLICY_DOMAIN_FILES=/etc/agentvisor/policies/domain.yml
volumes:
- ./policies:/etc/agentvisor/policies:ro
depends_on:
- temporal
deploy:
resources:
limits:
memory: 2G
cpus: "2"

volumes:
temporal-data:

Health Checks

services:
agentvisor:
image: my-agent:v1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8090/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s

Logging

services:
agentvisor:
image: my-agent:v1
environment:
- AGENTVISOR_LOG_LEVEL=info
- AGENTVISOR_LOG_FORMAT=json
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"

Resource Limits

services:
agentvisor:
image: my-agent:v1
environment:
- AGENTVISOR_GUEST_MEMORY_LIMIT=536870912
- AGENTVISOR_GUEST_CPU_LIMIT=1.0
deploy:
resources:
limits:
memory: 2G
cpus: "2"
reservations:
memory: 512M
cpus: "0.5"

Networking

Internal Network

services:
agentvisor:
networks:
- internal
- external

temporal:
networks:
- internal

networks:
internal:
internal: true
external:

Exposed Ports

PortService
8090HTTP API
7233Temporal gRPC
8233Temporal UI

Secrets

services:
agentvisor:
environment:
- API_KEY_FILE=/run/secrets/api_key
secrets:
- api_key

secrets:
api_key:
file: ./secrets/api_key.txt