Container Isolation

Automatic container isolation for stdio backends using Docker or Podman, including image building, per-backend options, and configuration.

Argus automatically wraps every stdio backend in an isolated container (Docker or Podman) when a supported runtime is detected on the host. No configuration is needed -- container isolation is transparent and applied by default with secure settings:

  • Filesystem: --read-only root filesystem + tmpfs for /tmp and /home/nonroot
  • Privileges: --cap-drop ALL, --security-opt no-new-privileges
  • Resources: --memory 512m --cpus 1
  • User: UID 65532 (nonroot), matching distroless/Chainguard standards
  • Init: --init for proper signal handling

Image Building Pipeline

Argus builds a purpose-built Docker image for each backend using Jinja2 templates. The command determines which template is used:

CommandTemplateBase Image
uvx, uv, python, python3uvx.dockerfile.j2python:3.13-slim
npx, node, tsxnpx.dockerfile.j2node:22-alpine
gogo.dockerfile.j2golang:1.24-alpine

Each template creates a non-root user (UID 65532), installs the required packages, and sets HOME=/home/nonroot and TMPDIR=/tmp.

If no container runtime is available, or the command has no recognized template mapping, Argus falls back to running the backend as a bare subprocess (with a log warning).

Per-Backend Container Options

Individual backends can customize their container settings using the nested container key:

Note:

Earlier versions of the documentation showed container options as flat fields on the backend (container_isolation, builder_image, system_deps). This is incorrect. Container options must be nested under a container key within the backend definition. Using the old flat field names will have no effect.

backends:
  my-backend:
    type: stdio
    command: npx
    args: ["-y", "my-mcp-server"]
    container:
      enabled: true               # default: inherits global flag
      builder_image: "node:22-alpine"   # override base image
      system_deps: ["curl", "jq"]       # additional OS packages
      additional_packages: []           # extra language-level packages

Disabling Container Isolation

Globally via environment variable:

ARGUS_CONTAINER_ISOLATION=false

Or the feature flag in your config:

feature_flags:
  container_isolation: false

Or per-backend:

backends:
  my-backend:
    container:
      enabled: false

Pre-Building Images

By default Argus builds the container image automatically the first time it encounters each backend. To pre-build all images ahead of time:

argus-mcp build --config config.yaml

To skip the build and fail fast if the image is absent:

ARGUS_CONTAINER_BUILD_IF_MISSING=false

This is useful in production deployments where images should be pre-built and pushed to a registry, not built on demand.


Resource Limits and Runtime

Override the default resource constraints or choose a specific container runtime:

FieldTypeDefaultDescription
runtime"docker" | "podman" | "kubernetes" | nullnull (auto-detect)Container runtime to use. When null, Argus detects Docker or Podman automatically.
memorystring | nullnull (512m)Memory limit for the container. Uses Docker memory syntax: 512m, 1g, 2048m.
cpusstring | nullnull (1)CPU limit for the container. Fractional values allowed: 0.5, 1, 2.
networkstring | nullnull (bridge)Network mode. Use none to fully isolate the container; bridge to allow outbound internet access.
backends:
  my-backend:
    type: stdio
    command: npx
    args: ["-y", "my-mcp-server"]
    container:
      runtime: "docker"      # explicitly use Docker, not auto-detect
      memory: "1g"           # increase for memory-intensive servers
      cpus: "2"              # allow 2 CPUs
      network: "none"        # fully isolate from network

Volume Mounts and Extra Arguments

Mount host directories into the container or pass extra Docker flags:

FieldTypeDefaultDescription
volumesList[str][]Volume mount specifications in Docker format: host_path:container_path:mode. Mode is ro (read-only) or rw (read-write). Supports ${HOME} and other env var expansions.
extra_argsList[str][]Additional arguments passed directly to the docker run command, inserted before the image name. Use for flags not covered by other fields (e.g., --device, --cap-add).
backends:
  sqlite-db:
    type: stdio
    command: uvx
    args:
      - "mcp-server-sqlite"
      - "--db-path"
      - "/data/db.sqlite"
    container:
      network: none
      volumes:
        - "${HOME}/.mcp-data:/data:rw"   # persist database file on host

  git-server:
    type: stdio
    command: uvx
    args: ["mcp-server-git", "--repository", "/workspace"]
    container:
      network: none
      volumes:
        - "${HOME}/projects:/workspace:ro"   # read-only workspace mount
      extra_args:
        - "--device=/dev/fuse"               # pass host device into container

Build Customization

system_deps vs. build_system_deps

Both fields install OS packages, but at different stages:

FieldStageIncluded in Final Image?Use For
system_depsRuntime imageYesPackages needed at runtime (e.g., chromium, git, curl)
build_system_depsBuilder stage onlyNoPackages needed only during build (e.g., gcc, make, cmake)
backends:
  my-compiled-server:
    type: stdio
    command: python
    args: ["-m", "my_server"]
    container:
      system_deps:
        - "libssl3"         # needed at runtime
      build_system_deps:
        - "gcc"             # needed to compile C extension during build
        - "python3-dev"     # needed for build only

Source Build Pattern

For MCP servers published only on GitHub — with no npm or PyPI package — Argus can clone and build from source at image build time.

Required fields when using source_url: build_steps and entrypoint are both required. Omitting either will cause a validation error.

FieldTypeDefaultDescription
source_urlstring | nullnullGit repository URL to clone. Must use https:// or git+ssh://. Private/loopback IPs are rejected.
build_stepsList[str][]Commands to run after cloning the repository. Each step is a separate string. Cannot contain backtick, $(), or () — use separate list entries instead of shell chaining.
entrypointList[str] | nullnullContainer entrypoint command as a list. Set to the command that starts the MCP server. Cannot contain shell metacharacters: semicolon, ampersand, pipe, backtick, dollar sign, parentheses, braces, brackets, angle brackets, !, #, ~, or backslash.
build_envDict[str, str]{}Environment variables available only during the build stage. Not present in the final runtime image.
source_refstring | nullnullGit ref (branch, tag, or commit SHA) to check out. When null, clones the default branch.

Base image selection: If any build step or entrypoint element contains npm, npx, node, yarn, or pnpm, Argus uses node:22-alpine. Otherwise it uses python:3.13-slim.

backends:
  my-github-server:
    type: stdio
    command: node
    args: ["dist/index.js"]
    container:
      network: bridge
      source_url: https://github.com/owner/my-mcp-server.git
      source_ref: "v1.2.0"          # pin to a release tag
      build_steps:
        - "npm install"
        - "npm run build"            # each step is a separate list entry
      entrypoint:
        - "node"
        - "dist/index.js"
      build_env:
        NODE_ENV: "production"       # only set during build

Note:

Shell expansion syntax is not supported in build_steps. Do not use backticks, $(), or (). Split compound operations into separate list entries:

# Wrong — validator will reject this:
build_steps:
  - "VERSION=$(cat package.json | grep version)"

# Correct — use separate steps:
build_steps:
  - "npm install"
  - "npm run build"

Go Transport Pattern

For MCP servers written in Go and published as Go modules, Argus can compile the binary from the module path using go install.

FieldTypeDefaultDescription
transport"uvx" | "npx" | "go" | nullnull (auto-detect from command)Override transport detection. Set to "go" to use the Go build pipeline. The compiled binary is placed at /app/mcp-server in the final image.
go_packagestring | nullnullGo module path passed to go install. Required when transport: go. Example: github.com/strowk/mcp-k8s-go.
backends:
  kubernetes:
    type: stdio
    command: mcp-k8s     # binary name after go install
    container:
      transport: go
      go_package: github.com/strowk/mcp-k8s-go
      network: bridge
      volumes:
        - "${HOME}/.kube:/home/nonroot/.kube:ro"   # kubeconfig mount

When transport: go is set, Argus uses a golang:1.24-alpine builder stage and places the compiled binary at /app/mcp-server. The command field in the backend config should match the binary's installed name.

Custom Dockerfile Pattern

For servers with build requirements too complex for the auto-generated templates, you can provide a custom Dockerfile.

FieldTypeDefaultDescription
dockerfilestring | nullnullRelative path to a custom Dockerfile. Must be a relative path with no .. components. The Dockerfile must be in the same directory as (or a subdirectory of) the backend config YAML file.

Dockerfile placement: The validator rejects absolute paths and any path containing ... The recommended location is alongside the config YAML file, with the filename as container.dockerfile: {server-slug}.dockerfile.

backends:
  my-custom-server:
    type: stdio
    command: python
    args: ["-m", "my_server"]
    container:
      dockerfile: my-custom-server.dockerfile   # co-located with this YAML
      network: none
      volumes:
        - "${HOME}/data:/data:ro"

The Dockerfile resides at configs/{category}/my-custom-server.dockerfile — the same directory as the YAML config.

Note:

Use the dockerfile escape hatch only when standard ContainerConfig fields cannot express your requirements. For most servers, source_url (for GitHub-only builds), go_package (for Go binaries), system_deps (for extra OS packages), and builder_image (for base image overrides) cover the common cases without requiring a custom Dockerfile.