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-onlyroot filesystem + tmpfs for/tmpand/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:
--initfor 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:
| Command | Template | Base Image |
|---|---|---|
uvx, uv, python, python3 | uvx.dockerfile.j2 | python:3.13-slim |
npx, node, tsx | npx.dockerfile.j2 | node:22-alpine |
go | go.dockerfile.j2 | golang: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:
| Field | Type | Default | Description |
|---|---|---|---|
runtime | "docker" | "podman" | "kubernetes" | null | null (auto-detect) | Container runtime to use. When null, Argus detects Docker or Podman automatically. |
memory | string | null | null (512m) | Memory limit for the container. Uses Docker memory syntax: 512m, 1g, 2048m. |
cpus | string | null | null (1) | CPU limit for the container. Fractional values allowed: 0.5, 1, 2. |
network | string | null | null (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:
| Field | Type | Default | Description |
|---|---|---|---|
volumes | List[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_args | List[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:
| Field | Stage | Included in Final Image? | Use For |
|---|---|---|---|
system_deps | Runtime image | Yes | Packages needed at runtime (e.g., chromium, git, curl) |
build_system_deps | Builder stage only | No | Packages 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.
| Field | Type | Default | Description |
|---|---|---|---|
source_url | string | null | null | Git repository URL to clone. Must use https:// or git+ssh://. Private/loopback IPs are rejected. |
build_steps | List[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. |
entrypoint | List[str] | null | null | Container 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_env | Dict[str, str] | {} | Environment variables available only during the build stage. Not present in the final runtime image. |
source_ref | string | null | null | Git 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.
| Field | Type | Default | Description |
|---|---|---|---|
transport | "uvx" | "npx" | "go" | null | null (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_package | string | null | null | Go 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.
| Field | Type | Default | Description |
|---|---|---|---|
dockerfile | string | null | null | Relative 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.