The qwen-code container¶
qwen-code packages Qwen Code, the
open-source terminal coding agent, in the repository's standard hardened
workload environment: non-root execution, a read-only root filesystem,
minimal Linux capabilities, and deny-by-default outbound networking.
It uses python:3.14-slim-trixie plus Node LTS. Qwen Code is installed from
the official @qwen-code/qwen-code npm package. Python, uv, and uvx are
included so Qwen Code can run Python-based MCP servers and project tooling
without a custom derivative image.
Build¶
Matching your host user's UID/GID¶
The image defaults to UID/GID 1000:1000. Everything the agent writes to
the bind-mounted project directory or the persistent home volume is owned
by whatever UID/GID the container ran as — if that doesn't match your host
account, those files aren't owned by you on the host (permission errors,
needing sudo to clean up). Rebuild with matching values instead:
docker build --build-context shared=agent-images/shared \
--build-arg UID=$(id -u) --build-arg GID=$(id -g) \
-t qwen-code:$(whoami) agent-images/qwen-code
Dockerfile splits into a base stage that installs everything —
apt/Node.js/uv baseline, packages-apt.txt/packages-uv.txt, Qwen Code
itself, and the account-scoped packages-npm.txt/tools-uv.txt — under a
fixed placeholder account (UID/GID 1000:1000), and a final stage that
only remaps that account to the UID/GID build args (usermod/groupmod
plus a chown -R of what base already installed) when they differ from
the placeholder. Nothing in final reinstalls packages, so rebuilding for a
different UID after the first build is fast.
On a host shared by multiple accounts, tag the image per user
(qwen-code:alice, qwen-code:bob) rather than overwriting a shared
qwen-code tag — and use that same tag, plus a per-user home volume
(qwen-home-alice rather than the shared qwen-home), in the run command
below. The shell-shortcut
functions do both
substitutions automatically from a single CONTAINED_QWEN_TAG variable; if
invoking docker run directly instead, substitute your tag into both the
qwen-home volume name and the trailing qwen-code image reference in the
command below — copying it unchanged still runs the shared
qwen-code:latest.
Run¶
This example configures Qwen Code against DashScope's public OpenAI-compatible endpoint. Supply the API key at runtime; do not put it in the image, build arguments, or a checked-in file.
docker run -it --rm \
--security-opt=no-new-privileges \
--read-only --tmpfs /tmp --tmpfs /run \
--cap-drop=ALL --cap-add=NET_ADMIN --cap-add=NET_RAW --cap-add=SETUID --cap-add=SETGID \
-e OPENAI_API_KEY \
-e OPENAI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1 \
-e OPENAI_MODEL=qwen3-coder-plus \
-e 'AGENT_ALLOWED_EGRESS=dashscope.aliyuncs.com' \
-v qwen-home:/home/qwen \
-v "$PWD":"/workspace/$(basename "$PWD")" \
-w "/workspace/$(basename "$PWD")" \
qwen-code
Hardening flags¶
| Flag | Purpose |
|---|---|
--security-opt=no-new-privileges |
Prevents escalation through setuid binaries. |
--read-only |
Keeps the root filesystem immutable. |
--tmpfs /tmp and --tmpfs /run |
Provide ephemeral writable locations for the egress-control tooling and runtime files. |
--cap-drop=ALL |
Removes Docker's default capabilities. |
NET_ADMIN and NET_RAW |
Required for the default-deny iptables/ip6tables rules and domain allowlists. |
SETUID and SETGID |
Let gosu switch from the setup user to the unprivileged qwen user. |
The qwen-home volume persists Qwen Code's configuration, authentication
state, MCP definitions, and user-installed npm/Python tools under
~/.qwen. As with the other workload images, mounting each project below
/workspace/<project-name> keeps its working path distinct while the home
volume is shared across runs.
Custom configuration¶
Qwen Code's global settings are below /home/qwen/.qwen, but their
single-file bind-mount and mutation behavior has not yet been verified for
this image. Prefer runtime provider environment variables for temporary
routing until that behavior is tested. See custom configuration
files and the version-recorded
candidate under local models.
Providers and egress control¶
The container defaults to deny-all when neither AGENT_ALLOWED_EGRESS nor a
mounted /etc/agent/egress-allowlist.txt file is provided. The mounted file
takes precedence. The sample
agent-images/qwen-code/examples/egress-allowlist.txt lists the mainland and
international DashScope endpoints; use only the endpoint your account uses.
Mount a host-maintained policy file read-only with
-v "/path/to/egress-allowlist.txt":/etc/agent/egress-allowlist.txt:ro.
Qwen Code also supports Qwen OAuth, OpenAI-compatible providers, Anthropic,
Gemini, Ollama, and vLLM. Add only the model-provider endpoints that you
configure, along with any intentionally used MCP server, source-control host,
or package registry. A local model service on the Docker host needs an
explicitly reachable address and an allowlist entry; it is not implicitly
reachable from the container. Use -e 'AGENT_ALLOWED_EGRESS=*' only when
unrestricted egress is intentional; quoting prevents shells such as zsh from
treating * as a filename pattern.
Nested sandboxing¶
Qwen Code has an optional Docker/Podman sandbox. This image sets
QWEN_SANDBOX=false, which overrides Qwen Code's sandbox setting and command
flag. The outer qwen-code container is the sandbox: it owns the project
mount, read-only root filesystem, capability reduction, and egress policy.
Do not mount a Docker or Podman socket into this workload and do not override
QWEN_SANDBOX; doing so would weaken the containment model by giving the
agent access to another container runtime.
Gateway-client mode¶
Setting AGENT_GATEWAY_HOST switches the image from its in-container
allowlist to an SSH tunnel through agent-gateway. The
gateway then owns all egress enforcement, so
AGENT_ALLOWED_EGRESS/egress-allowlist.txt are ignored by the workload.
Use the same gateway key, host-key pinning, bootstrap-rule, and Cloudflare
Access configuration described in
the Claude Code gateway guide.
Optional build-time tools¶
Edit packages-apt.txt, packages-npm.txt, tools-uv.txt, or
packages-uv.txt and rebuild to add general-purpose tools. tools-uv.txt
(uv tool install) is for standalone Python CLI tools, each in its own
isolated venv; packages-uv.txt (uv pip install --system) is for plain
importable libraries with no console-script of their own, installed into the
image's system Python instead. No credentials, extensions, skills, or
plugins are seeded in the image; Qwen Code stores user-selected state in the
persisted home volume.
The image also sets UV_LINK_MODE=copy — see claude-code's uv cache
notes for why.