Skip to main content

Custom Base Images

AgentVisor™ ships official host and guest images based on UBI 9 (Red Hat Universal Base Image). If your organization requires a different Linux distribution — for example Chainguard/Wolfi for provenance attestations, SBOM generation, or FIPS-validated packages — you can build replacement images as long as they satisfy the image contract described on this page.

When to use this​

  • You need a specific distro for compliance reasons (FIPS, CIS hardening, SBOM attestation).
  • Your container registry policy prohibits UBI/RHEL base images.
  • You want to apply your own hardening or vulnerability scanning baseline.

Release artifacts​

Starting with the first release that includes this feature, each GitHub release includes additional artifacts alongside the CLI binary. Find them at https://github.com/manetu/agentvisor/releases/tag/<VERSION>.

ArtifactDescription
agentvisor-host-runtime-linux-amd64-VERSION.tar.gzLicensed+obfuscated host-runtime (x86-64)
agentvisor-host-runtime-linux-arm64-VERSION.tar.gzLicensed+obfuscated host-runtime (ARM 64)
agentvisor-guest-runtime-linux-amd64-VERSION.tar.gzGuest-runtime binary (x86-64)
agentvisor-guest-runtime-linux-arm64-VERSION.tar.gzGuest-runtime binary (ARM 64)
agentvisor-mcp-guest-shim-linux-amd64-VERSION.tar.gzmcp-tools guest-hardening shim binary (x86-64)
agentvisor-mcp-guest-shim-linux-arm64-VERSION.tar.gzmcp-tools guest-hardening shim binary (ARM 64)
agentvisor-<PYVER>-py3-none-any.whlPython SDK wheel (proto/gRPC code bundled)
checksums.txtSHA-256 sums for all of the above
Wheel naming

The Python wheel uses PEP 440 version normalization: the leading v is stripped and the first - (build-pipeline separator) is converted to +. For example, release tag v0.5.0 with pipeline ID 21 produces agentvisor-0.5.0+21-py3-none-any.whl. The recommended way to obtain the wheel filename is to read it directly from checksums.txt rather than constructing it — see the Quick start example below.

Signing

SHA-256 checksums are published with every release. Cosign signing via Sigstore is on the roadmap.

Compatibility guarantee​

Always use the host-runtime and guest-runtime from the same release tag. Mixing versions across releases is unsupported — internal gRPC interfaces are not stable across release boundaries.

Download from GitHub Releases​

Download tarballs and wheel files directly. Suitable for distroless or air-gapped builds.

VERSION=v1.2.3
ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')

# Get checksums first
curl -fsSL \
https://github.com/manetu/agentvisor/releases/download/${VERSION}/checksums.txt \
-o checksums.txt

# Download and verify host-runtime
curl -fsSL \
https://github.com/manetu/agentvisor/releases/download/${VERSION}/agentvisor-host-runtime-linux-${ARCH}-${VERSION}.tar.gz \
-o agentvisor-host-runtime.tar.gz
sha256sum --check --ignore-missing checksums.txt # verify before use

Inside a Dockerfile, pass the checksums as build args and verify inline (see the reference Dockerfiles for a complete example).


Host image contract​

Every custom host image must satisfy these requirements. They mirror what the official AgentVisor host image builds.

Binaries​

PathDescription
/usr/local/bin/agentvisor-host-runtimeAgentVisor host binary (ENTRYPOINT). Path is fixed; not configurable.
/usr/local/bin/runscgVisor runsc binary (with its gvisor-bin/ helper directory alongside)
/usr/bin/newuidmapSUID helper for rootless user-namespace mapping
/usr/bin/newgidmapSUID helper for rootless user-namespace mapping
Preserve newuidmap/newgidmap SUID bits

When stripping SUID bits for hardening, exclude /usr/bin/newuidmap and /usr/bin/newgidmap. These SUID bits are required for rootless gVisor. All other SUID bits can be safely removed.

User and group​

EntryUID/GIDPurpose
agentvisor groupGID 1000Shared group for socket directory access
agentvisor userUID 1000Runtime user; image runs as USER 1000
/etc/subuid entryagentvisor:100000:65536Sub-UID range for rootless namespace mapping
/etc/subgid entryagentvisor:100000:65536Sub-GID range for rootless namespace mapping

Directories​

PathOwnerModePurpose
/var/run/agentvisoragentvisor:agentvisor0770Runtime state root — contains the runtime (gVisor state) and bundles (OCI bundle staging) subdirectories
/opt/guest-rootfs(none required)(none required)Mount point for the guest root filesystem layer; the official image leaves it root:root

Required environment variables​

These must be set (either via ENV in the Dockerfile or at container startup):

VariableRequired value
AGENTVISOR_SOCKET_PATH/var/run/agentvisor/host.sock
AGENTVISOR_GUEST_ROOTFS_PATH/opt/guest-rootfs
AGENTVISOR_GUEST_RUNSC_PATH/usr/local/bin/runsc
AGENTVISOR_GUEST_RUNTIME_ROOT/var/run/agentvisor/runtime
AGENTVISOR_GUEST_BUNDLE_DIR/var/run/agentvisor/bundles
AGENTVISOR_GUEST_SANDBOXgvisor
HOME/home/agentvisor

Dockerfile boilerplate​

USER 1000
ENTRYPOINT ["/usr/local/bin/agentvisor-host-runtime"]

Guest image contract​

Every custom guest image must satisfy these requirements. They mirror what the official AgentVisor guest image builds.

Binary​

PathDescription
/usr/local/bin/agentvisor-guest-runtimeAgentVisor guest binary (ENTRYPOINT). If your image places it elsewhere, set AGENTVISOR_GUEST_BINARY_PATH to that in-image path (gVisor sandbox only; Docker always uses the image ENTRYPOINT).

Users and groups​

UserUIDPrimary GIDPurpose
agentvisor10001000Socket directory ownership; host-side identity in Docker transport
guest20001000 (agentvisor)Guest-runtime process after privilege drop; shares socket GID
agent200165534 (nobody)Python agent processes; cannot access the agentvisor socket

Home directories (/home/agentvisor, /home/guest, /home/agent) should be created if your build uses --create-home with useradd. The official UBI image uses --no-create-home, but the Chainguard reference image below uses --create-home for broader compatibility.

Directories​

PathOwnerModePurpose
/app/agentagentvisor:agentvisor0770Agent source code (fixed path — required by the sandbox)
/app/agent/.agentvisoragentvisor:agentvisor0770Schema cache
/var/run/agentvisoragentvisor:agentvisor0770Socket directory
/app/agent is a fixed contract path

The path /app/agent is hardcoded in the host-to-guest sandbox specification. Custom guest images must place agent code at this path (or accept it being mounted there). This is analogous to how nginx always reads config from /etc/nginx/nginx.conf.

Environment variables​

VariableValueNotes
PYTHONDONTWRITEBYTECODE1Recommended in image
Host socket is injected at runtime

AGENTVISOR_GUEST_HOST_SOCKET is set by the host at container launch time. You do not need to bake it into the image. Setting AGENTVISOR_HOST_SOCKET in a Dockerfile ENV has no effect — the variable read by the guest-runtime is AGENTVISOR_GUEST_HOST_SOCKET.

Python variant requirements​

Required additionally when io.agentvisor.image.variant=python:

RequirementNotes
Python ≥ 3.10 reachable as python3Symlink at /usr/bin/python3 or /usr/local/bin/python3
agentvisor wheel installed system-wideFrom agentvisor-VERSION-py3-none-any.whl (proto/gRPC code is bundled — no separate proto wheel needed)
requests installedStandard HTTP library used by agent examples

OCI labels​

The host-runtime reads these labels from the guest image at startup to auto-configure the interpreter and exec mode. Set them with LABEL directives in your Dockerfile.

LabelExampleDescription
io.agentvisor.typeguestAdvisory; logged at startup for observability
io.agentvisor.image.variantpython or minimalInformational; logged at startup
io.agentvisor.framework.providerlanggraph, crewai, adk, strands, interactiveSets the agent framework interpreter
io.agentvisor.exec-modefalseSet to true for interactive/exec-mode images
LABEL io.agentvisor.type=guest \
io.agentvisor.image.variant=python \
io.agentvisor.framework.provider=langgraph \
io.agentvisor.exec-mode=false

Dockerfile boilerplate​

# No USER directive — guest-runtime starts as UID 0 for privilege separation,
# then drops to UID 2000 (guest) internally.
ENTRYPOINT ["/usr/local/bin/agentvisor-guest-runtime"]

Hardening recommendations​

The official guest image applies defense-in-depth hardening after package installation. We recommend replicating the key steps in your custom image:

# Remove namespace/mount/privilege-escalation tools
RUN rm -f /usr/bin/unshare /usr/bin/nsenter /usr/bin/mount /usr/bin/umount \
/usr/sbin/chroot /usr/bin/newgrp /usr/bin/sg 2>/dev/null || true

# Strip SUID/SGID bits from all remaining binaries
RUN find / -xdev -type f \( -perm -4000 -o -perm -2000 \) \
-exec chmod u-s,g-s {} + 2>/dev/null || true

# Remove the package manager (prevents runtime package installation)
RUN apt-get remove --purge -y apt ... || apk del apk-tools ...

See the Security deployment page for the rationale behind each step.

Thread and process creation under gVisor​

Manetu's own guest and mcp-tools images are built on a glibc release that does not exercise the code path described below, so this class of issue has never been observed against an official image. If your custom base image uses a different or newer glibc (for example a Debian- or Ubuntu-based image), this note applies to you.

Modern glibc (2.34+) tries a newer syscall before falling back to an older one when creating threads or processes, and requires a specific "not implemented" signal to know when to fall back rather than fail outright. Under the gvisor sandbox, AgentVisor's guest-runtime installs a small in-guest protection that supplies exactly this signal, restoring normal thread and process creation for any base image regardless of glibc version. This same protection is also what blocks that newer syscall from being used to create a user namespace and escalate privileges (see User Namespace Blocking) — it isn't only a compatibility fix. It runs automatically whenever guest.hardening.kernel_hardening or guest.hardening.filesystem_hardening resolves to enabled; both default to enabled under sandbox: gvisor — see Granular Hardening Configuration.

If you build a custom guest image under sandbox: gvisor and explicitly disable both kernel_hardening and filesystem_hardening, verify that basic Python threading works in your image before deploying it, and be aware that the user-namespace privilege-escalation protection above is also gone in that configuration. Disabling only one of the two leaves this protection in place, since either is sufficient to enable it.


MCP Tools image contract​

Every custom mcp-tools image must satisfy these requirements. They mirror what the official agentvisor-mcp-tools image builds. This image sandboxes stdio MCP servers (npx/uvx-based tools) used by the MCP Gateway — it is a separate, independently-versioned image from the host/guest images above, selected via mcp.tools_rootfs_path/mcp.tools_image rather than agentvisor build's --host-image/ --guest-image flags.

Binary​

PathDescription
/usr/local/bin/mcp-guest-shimGuest-hardening shim. Required in gVisor sandbox mode only — see the note below.
gVisor-only requirement

The shim installs the in-guest clone3(CLONE_NEWUSER) blocking that the shared OCI seccomp profile cannot express under the runsc enforcer (see User Namespace Blocking). Docker mode runs the mcp-tools image directly as a container under its own capability-drop/seccomp hardening and never invokes this binary at all. By default, AgentVisor aborts a gVisor-mode sandbox launch if the shim is missing from the resolved rootfs/image or not executable; set mcp.tools_on_missing_shim: warn to launch anyway without that protection.

User and group​

EntryUID/GIDPurpose
mcp user/groupUID/GID 1000Hardcoded assumption shared by both the gVisor and Docker MCP launchers

Directories​

PathOwnerModePurpose
/home/mcpmcp:mcp0755Default working directory and $HOME for stdio MCP server processes
/cachemcp:mcp(none required)Mount point for npm/uv package caches — gVisor bind-mounts a host directory here, Docker mode mounts a 2GB tmpfs; OCI requires the destination to pre-exist as a directory
/etc/resolv.conf(any)regular fileMust exist as a regular file (not be absent). In gVisor mode the launcher bind-mounts the host's /etc/resolv.conf over it for correct DNS resolution; Linux bind mounts require the destination to already exist

Runtime dependencies​

RequirementPurpose
node / npx reachable on PATHnpm-based MCP servers, e.g. npx -y @modelcontextprotocol/server-filesystem
python3 and uv (providing uvx) reachable on PATHPyPI-based MCP servers, e.g. uvx mcp-server-fetch, and mcp.prepull
Environment is injected at launch, not baked into the image

HOME, NPM_CONFIG_CACHE, UV_CACHE_DIR, and UV_TOOL_DIR are set by the launcher (gVisor or Docker) at the start of each stdio server sandbox — you do not need to set these yourself.

Dockerfile boilerplate​

LABEL io.agentvisor.type="mcp-tools"

USER mcp
WORKDIR /home/mcp

Hardening recommendations​

Unlike the host and guest images, the official mcp-tools image keeps its package manager and node/python3/uv tooling installed permanently — npx/uvx already pull arbitrary packages from the network at runtime on behalf of the agent's configured MCP servers, so removing apk/microdnf buys no additional isolation here. The one build-time-only tool worth removing is whatever you used to create the mcp user (shadow on Wolfi, shadow-utils on UBI):

RUN apk del shadow && rm -f /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow-

Reference: Chainguard / Wolfi​

The following reference Dockerfiles build replacements for the official AgentVisor images (host, guest, and mcp-tools) using Chainguard/Wolfi as the base OS. Save them to a local directory (e.g., agentvisor-chainguard/) before running the Quick start commands below.

Dockerfile.host
# AgentVisor Host Runtime — Chainguard / Wolfi reference image
#
# This Dockerfile builds a replacement for the official agentvisor-host image
# using cgr.dev/chainguard/wolfi-base as the base OS. It produces a functionally
# equivalent image without the UBI/RHEL dependency.
#
# It is intended as a copy-paste starting point. Customers are responsible for
# adapting and validating this Dockerfile for their environment.
#
# Prerequisites:
# - Binaries published to a GitHub release: https://github.com/manetu/agentvisor/releases
# - Locate SHA-256 checksums in checksums.txt attached to the same release
#
# Build example (see Quick start section for the full recommended workflow):
# export VERSION=v1.2.3
# export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
# curl -fsSL https://github.com/manetu/agentvisor/releases/download/${VERSION}/checksums.txt \
# -o checksums.txt
# export HOST_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-host-runtime-linux-"a {print $1}' checksums.txt)
#
# docker buildx build \
# --build-arg AGENTVISOR_VERSION=${VERSION} \
# --build-arg AGENTVISOR_HOST_RUNTIME_SHA256=${HOST_SHA} \
# -f Dockerfile.host \
# -t my-agentvisor-host:${VERSION} \
# .

FROM cgr.dev/chainguard/wolfi-base:latest

ARG AGENTVISOR_VERSION
ARG AGENTVISOR_HOST_RUNTIME_SHA256
# Pin to a dated gVisor release (format release-YYYYMMDD.0 on
# https://github.com/google/gvisor/tags) rather than "latest" -- two builds
# of the same image tag must install the same sandbox kernel, or a security
# regression can't be attributed to a runsc version.
ARG GVISOR_VERSION=20260928

RUN test -n "${AGENTVISOR_VERSION}" || { echo "ERROR: AGENTVISOR_VERSION is required"; exit 1; }
RUN test -n "${AGENTVISOR_HOST_RUNTIME_SHA256}" || { echo "ERROR: AGENTVISOR_HOST_RUNTIME_SHA256 is required"; exit 1; }

# Install runtime dependencies.
# - shadow: provides useradd/groupadd and the SUID newuidmap/newgidmap helpers
# required for gVisor rootless user-namespace mapping.
# - ca-certificates-bundle: trust store for outbound TLS from the host-runtime.
# - curl: used only in this RUN block for binary download; removed afterward.
RUN apk add --no-cache shadow ca-certificates-bundle curl

# Create the agentvisor user (UID 1000). Corresponds to USER 1000 / ENTRYPOINT
# user in the official host image.
RUN groupadd -g 1000 agentvisor && \
useradd -u 1000 -g agentvisor -s /bin/bash --create-home agentvisor

# Enable rootless gVisor user-namespace mapping.
# runsc creates a user namespace via CLONE_NEWUSER and relies on the kernel's
# newuidmap/newgidmap SUID helpers to allow the agentvisor (UID 1000) user to
# remap to the 100000-165535 sub-UID range without CAP_SYS_ADMIN on the host.
RUN echo "agentvisor:100000:65536" >> /etc/subuid && \
echo "agentvisor:100000:65536" >> /etc/subgid

# Create directories required by the host-runtime (owned by the agentvisor user).
RUN mkdir -p /var/run/agentvisor/runtime \
/var/run/agentvisor/bundles \
/opt/guest-rootfs && \
chown -R agentvisor:agentvisor /var/run/agentvisor /opt/guest-rootfs && \
chmod 0770 /var/run/agentvisor

# Download and verify the host-runtime binary.
# Note: wolfi-base does not include /usr/local/bin — create it first.
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \
mkdir -p /usr/local/bin && \
curl -fsSL \
"https://github.com/manetu/agentvisor/releases/download/${AGENTVISOR_VERSION}/agentvisor-host-runtime-linux-${ARCH}-${AGENTVISOR_VERSION}.tar.gz" \
-o /tmp/agentvisor-host-runtime.tar.gz && \
echo "${AGENTVISOR_HOST_RUNTIME_SHA256} /tmp/agentvisor-host-runtime.tar.gz" | sha256sum -c - && \
tar -C /usr/local/bin -xzf /tmp/agentvisor-host-runtime.tar.gz agentvisor-host-runtime && \
chmod +x /usr/local/bin/agentvisor-host-runtime && \
rm /tmp/agentvisor-host-runtime.tar.gz

# Download and checksum-verify runsc from the official gVisor release
# channel before it is made executable -- runsc enforces the entire agent
# sandbox boundary, so an unverified binary would let a compromised or
# MITM'd download in with nothing here able to detect it.
# gVisor URLs use x86_64/aarch64, not amd64/arm64.
# See: https://gvisor.dev/docs/user_guide/install/
RUN ARCH=$(uname -m) && \
BASE_URL="https://storage.googleapis.com/gvisor/releases/release/${GVISOR_VERSION}/${ARCH}" && \
curl -fsSL "${BASE_URL}/gvisor.tar.bz2" -o /tmp/gvisor.tar.bz2 && \
curl -fsSL "${BASE_URL}/gvisor.tar.bz2.sha512" -o /tmp/gvisor.tar.bz2.sha512 && \
(cd /tmp && sha512sum -c gvisor.tar.bz2.sha512) && \
mkdir -p /tmp/gvisor && \
tar -C /tmp/gvisor -xjf /tmp/gvisor.tar.bz2 && \
cp -a /tmp/gvisor/gvisor-bin /usr/local/bin/gvisor-bin && \
cp /tmp/gvisor/runsc /tmp/gvisor/containerd-shim-runsc-v1 /usr/local/bin/ && \
chmod +x /usr/local/bin/runsc /usr/local/bin/containerd-shim-runsc-v1 && \
rm -rf /tmp/gvisor /tmp/gvisor.tar.bz2 /tmp/gvisor.tar.bz2.sha512

# Harden: strip dangerous SUID password-management binaries but PRESERVE
# newuidmap and newgidmap (required for rootless gVisor).
# Also remove shadow credentials files — no runtime password auth is needed.
RUN rm -f /usr/bin/chage /usr/bin/gpasswd /usr/bin/newgrp /usr/bin/passwd && \
rm -f /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow-

# Remove the package manager and curl (defense-in-depth: reduces attack surface
# against runtime package installation on any writable tmpfs).
RUN apk del curl && \
apk del apk-tools && \
rm -rf /var/cache/apk /etc/apk /lib/apk

# ---- Environment contract (must match the "Required environment variables" table above) ----
ENV HOME=/home/agentvisor \
AGENTVISOR_SOCKET_PATH=/var/run/agentvisor/host.sock \
AGENTVISOR_GUEST_ROOTFS_PATH=/opt/guest-rootfs \
AGENTVISOR_GUEST_RUNSC_PATH=/usr/local/bin/runsc \
AGENTVISOR_GUEST_RUNTIME_ROOT=/var/run/agentvisor/runtime \
AGENTVISOR_GUEST_BUNDLE_DIR=/var/run/agentvisor/bundles \
AGENTVISOR_GUEST_SANDBOX=gvisor

LABEL io.agentvisor.type=host

USER 1000

ENTRYPOINT ["/usr/local/bin/agentvisor-host-runtime"]
Dockerfile.guest
# AgentVisor Guest Runtime — Chainguard / Wolfi reference image
#
# This Dockerfile builds a replacement for the official agentvisor-guest image
# using cgr.dev/chainguard/wolfi-base as the base OS. It produces two targets:
# - base: Minimal image with the guest-runtime binary only (no Python)
# - python: Full image with Python 3 and the AgentVisor SDK (default target)
#
# Build examples (see Quick start section for the full recommended workflow):
# export VERSION=v1.2.3
# export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
# curl -fsSL https://github.com/manetu/agentvisor/releases/download/${VERSION}/checksums.txt \
# -o checksums.txt
# export GUEST_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-guest-runtime-linux-"a {print $1}' checksums.txt)
# export SDK_WHL=$(awk '$2 ~ /^agentvisor-.*\.whl$/ {print $2}' checksums.txt)
# export SDK_SHA=$(awk -v w="${SDK_WHL}" '$2 == w {print $1}' checksums.txt)
#
# docker buildx build \
# --target python \
# --build-arg AGENTVISOR_VERSION=${VERSION} \
# --build-arg AGENTVISOR_GUEST_RUNTIME_SHA256=${GUEST_SHA} \
# --build-arg AGENTVISOR_SDK_WHL=${SDK_WHL} \
# --build-arg AGENTVISOR_SDK_SHA256=${SDK_SHA} \
# -f Dockerfile.guest \
# -t my-agentvisor-guest:${VERSION}-python \
# .

# =============================================================================
# python-deps: install Python wheels into a prefix using wolfi-base (same base
# as the final image) so Python versions match. The /tmp/pyinstall prefix is
# then COPY --from'd into the final image so no pip is needed at runtime.
# =============================================================================
FROM cgr.dev/chainguard/wolfi-base:latest AS python-deps

ARG AGENTVISOR_VERSION
ARG AGENTVISOR_SDK_WHL
ARG AGENTVISOR_SDK_SHA256

RUN test -n "${AGENTVISOR_VERSION}" || { echo "ERROR: AGENTVISOR_VERSION is required"; exit 1; }
RUN test -n "${AGENTVISOR_SDK_WHL}" || { echo "ERROR: AGENTVISOR_SDK_WHL is required"; exit 1; }
RUN test -n "${AGENTVISOR_SDK_SHA256}" || { echo "ERROR: AGENTVISOR_SDK_SHA256 is required"; exit 1; }

RUN apk add --no-cache python3 py3-pip curl

# Download and verify the SDK wheel. The proto/gRPC code is bundled inside it.
# The wheel must keep its original filename — pip validates the name format.
RUN curl -fsSL "https://github.com/manetu/agentvisor/releases/download/${AGENTVISOR_VERSION}/${AGENTVISOR_SDK_WHL}" \
-o /tmp/${AGENTVISOR_SDK_WHL} && \
echo "${AGENTVISOR_SDK_SHA256} /tmp/${AGENTVISOR_SDK_WHL}" | sha256sum -c -

# --prefix=/tmp/pyinstall lets the final stage COPY only the installed Python packages.
# /tmp is world-writable so this works under the non-root wolfi user.
RUN pip install --no-cache-dir --prefix=/tmp/pyinstall \
/tmp/${AGENTVISOR_SDK_WHL} \
requests

# =============================================================================
# base: minimal image — guest-runtime binary + required users, no Python.
# Suitable for the 'minimal' / 'interactive' image variant.
# =============================================================================
FROM cgr.dev/chainguard/wolfi-base:latest AS base

ARG AGENTVISOR_VERSION
ARG AGENTVISOR_GUEST_RUNTIME_SHA256

RUN test -n "${AGENTVISOR_VERSION}" || { echo "ERROR: AGENTVISOR_VERSION is required"; exit 1; }
RUN test -n "${AGENTVISOR_GUEST_RUNTIME_SHA256}" || { echo "ERROR: AGENTVISOR_GUEST_RUNTIME_SHA256 is required"; exit 1; }

# Install dependencies:
# - shadow: provides useradd/groupadd (removed after use)
# - nftables: guest-runtime network isolation in TCP transport mode (macOS/Windows)
# - ca-certificates-bundle: TLS trust store
# - curl: binary download (removed after use)
RUN apk add --no-cache shadow nftables ca-certificates-bundle curl

# Create the three users required by the privilege-separation model.
#
# agentvisor (UID 1000, GID 1000) — host-side identity; Docker socket auth
# guest (UID 2000, GID 1000) — guest-runtime after privilege drop;
# primary group agentvisor so it can reach the socket
# agent (UID 2001, GID 65534) — Python agent processes; uses nobody GID
# to prevent socket access
RUN groupadd -g 1000 agentvisor && \
useradd -u 1000 -g agentvisor -s /bin/bash -d /home/agentvisor --create-home agentvisor && \
useradd -u 2000 -g agentvisor -s /bin/bash -d /home/guest --create-home guest && \
useradd -u 2001 -g nobody -s /bin/bash -d /home/agent --create-home agent

# Download and verify the guest-runtime binary.
# Note: wolfi-base does not include /usr/local/bin — create it first.
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \
mkdir -p /usr/local/bin && \
curl -fsSL \
"https://github.com/manetu/agentvisor/releases/download/${AGENTVISOR_VERSION}/agentvisor-guest-runtime-linux-${ARCH}-${AGENTVISOR_VERSION}.tar.gz" \
-o /tmp/guest-runtime.tar.gz && \
echo "${AGENTVISOR_GUEST_RUNTIME_SHA256} /tmp/guest-runtime.tar.gz" | sha256sum -c - && \
tar -C /usr/local/bin -xzf /tmp/guest-runtime.tar.gz agentvisor-guest-runtime && \
chmod +x /usr/local/bin/agentvisor-guest-runtime && \
rm /tmp/guest-runtime.tar.gz

# Create filesystem contract directories.
# /app/agent — where agent source code is mounted / baked
# /app/agent/.agentvisor — schema cache directory
# /var/run/agentvisor — socket directory (mode 0770 for agentvisor group)
RUN mkdir -p /app/agent /app/agent/.agentvisor /var/run/agentvisor && \
chown -R agentvisor:agentvisor /app /var/run/agentvisor && \
chmod 0770 /var/run/agentvisor

# Harden: remove dangerous namespace/mount/privilege-escalation tools and strip
# any remaining SUID/SGID bits. Belt-and-suspenders alongside seccomp profiles.
# Note: shadow's SUID binaries (chage etc.) are not installed by default on Wolfi,
# but the find-and-strip pass catches any apk packages that set SUID bits.
RUN rm -f /usr/bin/unshare /usr/bin/nsenter /usr/bin/mount /usr/bin/umount \
/usr/sbin/chroot /usr/bin/newgrp /usr/bin/sg 2>/dev/null || true && \
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) \
-exec chmod u-s,g-s {} + 2>/dev/null || true

# Remove build-time tools (defense-in-depth against runtime package installation).
RUN apk del shadow curl && \
apk del apk-tools && \
rm -rf /var/cache/apk /etc/apk /lib/apk

# Note: AGENTVISOR_GUEST_HOST_SOCKET is injected at container launch by the host;
# do not bake it into the image (AGENTVISOR_HOST_SOCKET has no effect).
ENV PYTHONDONTWRITEBYTECODE=1

WORKDIR /app

# NOTE: No USER directive. The guest-runtime must start as UID 0 (root) so the
# privsep model works: the spawner subprocess retains UID 0 while the main
# process drops to UID 2000 (guest) internally.

LABEL io.agentvisor.type=guest \
io.agentvisor.image.variant=minimal \
io.agentvisor.framework.provider=interactive

ENTRYPOINT ["/usr/local/bin/agentvisor-guest-runtime"]

# =============================================================================
# python: full image — base + Python 3 + AgentVisor SDK.
# Suitable for LangGraph, CrewAI, ADK, and other Python-based agents.
#
# This stage starts fresh from wolfi-base (not FROM base) so that apk-tools is
# still available to install python3. The base stage has already removed
# apk-tools as part of its hardening, so it cannot be extended with new packages.
# All base-stage setup (users, binary, dirs, hardening) is repeated here.
# =============================================================================
FROM cgr.dev/chainguard/wolfi-base:latest AS python

ARG AGENTVISOR_VERSION
ARG AGENTVISOR_GUEST_RUNTIME_SHA256
ARG AGENTVISOR_SDK_WHL
ARG AGENTVISOR_SDK_SHA256

RUN test -n "${AGENTVISOR_VERSION}" || { echo "ERROR: AGENTVISOR_VERSION is required"; exit 1; }
RUN test -n "${AGENTVISOR_GUEST_RUNTIME_SHA256}" || { echo "ERROR: AGENTVISOR_GUEST_RUNTIME_SHA256 is required"; exit 1; }
RUN test -n "${AGENTVISOR_SDK_WHL}" || { echo "ERROR: AGENTVISOR_SDK_WHL is required"; exit 1; }

RUN apk add --no-cache shadow nftables ca-certificates-bundle curl python3

# Same user setup as base stage.
RUN groupadd -g 1000 agentvisor && \
useradd -u 1000 -g agentvisor -s /bin/bash -d /home/agentvisor --create-home agentvisor && \
useradd -u 2000 -g agentvisor -s /bin/bash -d /home/guest --create-home guest && \
useradd -u 2001 -g nobody -s /bin/bash -d /home/agent --create-home agent

# Download and verify the guest-runtime binary.
# Note: wolfi-base does not include /usr/local/bin — create it first.
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \
mkdir -p /usr/local/bin && \
curl -fsSL \
"https://github.com/manetu/agentvisor/releases/download/${AGENTVISOR_VERSION}/agentvisor-guest-runtime-linux-${ARCH}-${AGENTVISOR_VERSION}.tar.gz" \
-o /tmp/guest-runtime.tar.gz && \
echo "${AGENTVISOR_GUEST_RUNTIME_SHA256} /tmp/guest-runtime.tar.gz" | sha256sum -c - && \
tar -C /usr/local/bin -xzf /tmp/guest-runtime.tar.gz agentvisor-guest-runtime && \
chmod +x /usr/local/bin/agentvisor-guest-runtime && \
rm /tmp/guest-runtime.tar.gz

# Copy pre-installed Python packages from the python-deps build stage.
# /tmp/pyinstall was built with --prefix=/tmp/pyinstall so it maps cleanly onto /usr.
COPY --from=python-deps /tmp/pyinstall /usr

# Verify the SDK is importable.
RUN python3 -c "import agentvisor; print('agentvisor SDK OK')"

# Pre-compile Python bytecode for faster startup on read-only rootfs.
RUN python3 -m compileall -q -j 0 /usr/lib/python3* 2>/dev/null || true

# Create filesystem contract directories (same as base stage).
RUN mkdir -p /app/agent /app/agent/.agentvisor /var/run/agentvisor && \
chown -R agentvisor:agentvisor /app /var/run/agentvisor && \
chmod 0770 /var/run/agentvisor

# Harden (same as base stage).
RUN rm -f /usr/bin/unshare /usr/bin/nsenter /usr/bin/mount /usr/bin/umount \
/usr/sbin/chroot /usr/bin/newgrp /usr/bin/sg 2>/dev/null || true && \
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) \
-exec chmod u-s,g-s {} + 2>/dev/null || true

RUN apk del shadow curl && \
apk del apk-tools && \
rm -rf /var/cache/apk /etc/apk /lib/apk

# Note: AGENTVISOR_GUEST_HOST_SOCKET is injected at container launch by the host;
# do not bake it into the image (AGENTVISOR_HOST_SOCKET has no effect).
ENV PYTHONDONTWRITEBYTECODE=1

WORKDIR /app

# NOTE: No USER directive — see base stage comment.

LABEL io.agentvisor.type=guest \
io.agentvisor.image.variant=python \
io.agentvisor.framework.provider=langgraph

ENTRYPOINT ["/usr/local/bin/agentvisor-guest-runtime"]
Dockerfile.mcp-tools
# AgentVisor MCP Tools — Chainguard / Wolfi reference image
#
# This Dockerfile builds a replacement for the official agentvisor-mcp-tools image
# using cgr.dev/chainguard/wolfi-base as the base OS. It sandboxes stdio MCP servers
# (npx/uvx-based tools) used by the MCP Gateway.
#
# It is intended as a copy-paste starting point. Customers are responsible for
# adapting and validating this Dockerfile for their environment.
#
# Prerequisites:
# - agentvisor-mcp-guest-shim published to a GitHub release: https://github.com/manetu/agentvisor/releases
# - Locate SHA-256 checksums in checksums.txt attached to the same release
#
# Build example (see Quick start section for the full recommended workflow):
# export VERSION=v1.2.3
# export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')
# curl -fsSL https://github.com/manetu/agentvisor/releases/download/${VERSION}/checksums.txt \
# -o checksums.txt
# export SHIM_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-mcp-guest-shim-linux-"a {print $1}' checksums.txt)
#
# docker buildx build \
# --build-arg AGENTVISOR_VERSION=${VERSION} \
# --build-arg AGENTVISOR_MCP_GUEST_SHIM_SHA256=${SHIM_SHA} \
# -f Dockerfile.mcp-tools \
# -t my-agentvisor-mcp-tools:${VERSION} \
# .

FROM cgr.dev/chainguard/wolfi-base:latest

ARG AGENTVISOR_VERSION
ARG AGENTVISOR_MCP_GUEST_SHIM_SHA256

RUN test -n "${AGENTVISOR_VERSION}" || { echo "ERROR: AGENTVISOR_VERSION is required"; exit 1; }
RUN test -n "${AGENTVISOR_MCP_GUEST_SHIM_SHA256}" || { echo "ERROR: AGENTVISOR_MCP_GUEST_SHIM_SHA256 is required"; exit 1; }

# Runtime dependencies: node/npm for npm-based MCP servers, python3/uv for
# PyPI-based ones. shadow provides useradd/groupadd (removed after use);
# curl is used only for the binary download below (removed after use).
# Unlike the host/guest images, the package manager is intentionally kept —
# npx/uvx already pull arbitrary packages from the network at runtime, so
# removing apk buys no additional isolation here.
RUN apk add --no-cache shadow ca-certificates-bundle nodejs npm python3 py3-pip curl

# uv provides uvx (used for PyPI-based MCP servers and mcp.prepull).
RUN pip install --no-cache-dir --break-system-packages uv

# Create the mcp user (UID/GID 1000). Corresponds to the hardcoded UID/GID
# assumption shared by the gVisor and Docker MCP launchers.
RUN groupadd -g 1000 mcp && \
useradd -u 1000 -g mcp -s /bin/bash --create-home -d /home/mcp mcp && \
chmod 0755 /home/mcp

# /cache is bind-mounted (gVisor) or tmpfs-mounted (Docker) read-write at
# launch time -- OCI requires the destination to pre-exist.
RUN mkdir -p /cache && chown mcp:mcp /cache

# /etc/resolv.conf must exist as a regular file: the gVisor MCP launcher
# bind-mounts the host's /etc/resolv.conf over it so stdio servers resolve
# DNS correctly. The content here is a placeholder always overridden by that
# bind mount; it only matters as a fallback if the sandbox ever runs without
# it. COPY <<EOF is required because a RUN redirect fails once something
# bind-mounts this file read-only during a later build step.
COPY <<EOF /etc/resolv.conf
nameserver 1.1.1.1
nameserver 1.0.0.1
nameserver 8.8.8.8
EOF

# Download and verify the guest-hardening shim binary.
# Note: wolfi-base does not include /usr/local/bin — create it first.
# The final path is fixed at /usr/local/bin/mcp-guest-shim; the release
# tarball's internal filename (agentvisor-mcp-guest-shim) differs, so the
# binary is renamed on extraction.
RUN ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && \
mkdir -p /usr/local/bin && \
curl -fsSL \
"https://github.com/manetu/agentvisor/releases/download/${AGENTVISOR_VERSION}/agentvisor-mcp-guest-shim-linux-${ARCH}-${AGENTVISOR_VERSION}.tar.gz" \
-o /tmp/mcp-guest-shim.tar.gz && \
echo "${AGENTVISOR_MCP_GUEST_SHIM_SHA256} /tmp/mcp-guest-shim.tar.gz" | sha256sum -c - && \
tar -C /tmp -xzf /tmp/mcp-guest-shim.tar.gz agentvisor-mcp-guest-shim && \
mv /tmp/agentvisor-mcp-guest-shim /usr/local/bin/mcp-guest-shim && \
chmod +x /usr/local/bin/mcp-guest-shim && \
rm /tmp/mcp-guest-shim.tar.gz

# Harden: remove build-time-only tooling. shadow's useradd/groupadd have
# already run; curl was only needed for the binary download above.
RUN apk del shadow curl && \
rm -f /etc/shadow /etc/shadow- /etc/gshadow /etc/gshadow-

LABEL io.agentvisor.type="mcp-tools"
LABEL io.agentvisor.version="${AGENTVISOR_VERSION}"

USER mcp
WORKDIR /home/mcp

Quick start​

Save the Dockerfiles above to a local directory (e.g., mkdir agentvisor-chainguard), then:

export VERSION=v1.2.3
export ARCH=$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')

# Fetch checksums for the target release
curl -fsSL \
https://github.com/manetu/agentvisor/releases/download/${VERSION}/checksums.txt \
-o checksums.txt

# Derive SHA-256 values from checksums.txt.
# The file is already release-specific, so omit VERSION from the grep patterns
# (this also avoids the PEP 440 v-prefix difference in the wheel filename).
export HOST_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-host-runtime-linux-"a {print $1}' checksums.txt)
export GUEST_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-guest-runtime-linux-"a {print $1}' checksums.txt)
export SHIM_SHA=$(awk -v a="${ARCH}" '$2 ~ "agentvisor-mcp-guest-shim-linux-"a {print $1}' checksums.txt)
export SDK_WHL=$(awk '$2 ~ /^agentvisor-.*\.whl$/ {print $2}' checksums.txt)
export SDK_SHA=$(awk -v w="${SDK_WHL}" '$2 == w {print $1}' checksums.txt)

# Build the host image
docker buildx build \
--build-arg AGENTVISOR_VERSION=${VERSION} \
--build-arg AGENTVISOR_HOST_RUNTIME_SHA256=${HOST_SHA} \
-f agentvisor-chainguard/Dockerfile.host \
-t my-registry/agentvisor-host:${VERSION}-chainguard \
agentvisor-chainguard/

# Build the python guest image
docker buildx build \
--target python \
--build-arg AGENTVISOR_VERSION=${VERSION} \
--build-arg AGENTVISOR_GUEST_RUNTIME_SHA256=${GUEST_SHA} \
--build-arg AGENTVISOR_SDK_WHL=${SDK_WHL} \
--build-arg AGENTVISOR_SDK_SHA256=${SDK_SHA} \
-f agentvisor-chainguard/Dockerfile.guest \
-t my-registry/agentvisor-guest:${VERSION}-chainguard-python \
agentvisor-chainguard/

# Build the mcp-tools image
docker buildx build \
--build-arg AGENTVISOR_VERSION=${VERSION} \
--build-arg AGENTVISOR_MCP_GUEST_SHIM_SHA256=${SHIM_SHA} \
-f agentvisor-chainguard/Dockerfile.mcp-tools \
-t my-registry/agentvisor-mcp-tools:${VERSION}-chainguard \
agentvisor-chainguard/

Using your custom images​

agentvisor build ./my-agent -t my-agent:latest \
--host-image my-registry/agentvisor-host:v1.2.3-chainguard \
--guest-image my-registry/agentvisor-guest:v1.2.3-chainguard-python \
--mcp-tools-image my-registry/agentvisor-mcp-tools:v1.2.3-chainguard

--mcp-tools-image is only relevant when the agent's mav-agent-config.yaml configures a stdio MCP server — see The mcp-tools Sandbox Image. It is also accepted by agentvisor serve, agentvisor run, and agentvisor exec.


Verifying your custom image​

Binary sanity check​

docker run --rm my-registry/agentvisor-host:v1.2.3-chainguard \
/usr/local/bin/agentvisor-host-runtime --version

docker run --rm my-registry/agentvisor-guest:v1.2.3-chainguard-python \
/usr/local/bin/agentvisor-guest-runtime --version

# mcp-guest-shim has no --version flag -- it always execs into its argument,
# so a successful exit from the wrapped command confirms it installed the
# hardening filter and exec'd correctly.
docker run --rm my-registry/agentvisor-mcp-tools:v1.2.3-chainguard \
/usr/local/bin/mcp-guest-shim /bin/echo "shim exec OK"

Stdio MCP server smoke test​

Run a real stdio MCP server directly against the built image, using the same capability-drop/read-only/tmpfs flags the Docker MCP launcher applies at runtime (see MCP Tools image contract):

docker run --rm -i \
--cap-drop=ALL --security-opt=no-new-privileges --read-only \
--tmpfs /tmp:size=131072k,mode=1777,nosuid,nodev,exec \
--tmpfs /home/mcp:size=65536k,mode=1777,nosuid,nodev,exec \
--tmpfs /cache:size=2097152k,mode=1777,nosuid,nodev,exec \
--user 1000:1000 -e HOME=/home/mcp \
my-registry/agentvisor-mcp-tools:v1.2.3-chainguard \
npx -y @modelcontextprotocol/server-everything \
<<< '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"0.0.1"}}}'
# Expected: a JSON-RPC response containing "serverInfo"

Label check​

docker inspect my-registry/agentvisor-guest:v1.2.3-chainguard-python \
| jq '.[0].Config.Labels'
# Expected: { "io.agentvisor.type": "guest", "io.agentvisor.image.variant": "python", ... }

End-to-end smoke test​

agentvisor serve ./my-agent \
--guest-image my-registry/agentvisor-guest:v1.2.3-chainguard-python

curl http://localhost:8090/agents
# Expected: JSON list of registered agents

Compatibility & support​

The reference Dockerfiles shown above are best-effort examples and are not part of the official AgentVisor test matrix. Manetu provides these as a starting point — you are responsible for validating, patching, and maintaining images derived from them.

If you discover a bug or incompatibility in the image contract documentation, please contact support@manetu.com.