Security Model for iii Docker Containers: Distroless Runtime and Defence-in-Depth

The iii engine implements a defence-in-depth security model using distroless base images, non-root execution, read-only filesystems, and minimal Linux capabilities to eliminate shell access and reduce attack surface.

The iii project from iii-hq/iii packages its workflow execution engine as a hardened Docker container designed for production security. Understanding the security model for iii Docker containers is essential for deploying this open-source engine in enterprise environments. The architecture leverages distroless runtime images and multiple hardening layers to prevent privilege escalation and container escapes.

Distroless Base Image and Non-Root Execution

The foundation of the security model starts with the base image selection. According to the source code in engine/Dockerfile, the iii engine uses gcr.io/distroless/cc-debian12:nonroot as its base image. This distroless image contains only the runtime libraries necessary to execute the compiled binary and explicitly excludes shells, package managers, or standard Linux utilities.

This choice eliminates an entire class of attacks where an attacker attempts to spawn an interactive shell or install additional tooling inside a compromised container. The image runs as a built-in non-root user (nonroot), and the Dockerfile never switches to the root user during the build process.

FROM gcr.io/distroless/cc-debian12:nonroot

ARG TARGETARCH
COPY iii-${TARGETARCH} /app/iii

ENV III_EXECUTION_CONTEXT=docker \
    III_ENV=development

EXPOSE 49134 3111 3112 9464
ENTRYPOINT ["/app/iii"]
CMD ["--config", "/app/config.yaml"]

Runtime Filesystem and Capability Hardening

Beyond the base image, the production deployment configuration implements additional hardening measures documented in engine/README.md. These settings create an immutable runtime environment that restricts what a compromised process can modify or execute.

Read-Only Filesystem and Temporary Storage

Production containers launch with the --read-only flag, making the root filesystem immutable. A temporary in-memory filesystem (--tmpfs /tmp) provides writable space for ephemeral data without persisting changes to the container layers.

Linux Capability Reduction

The runtime drops all Linux capabilities using --cap-drop=ALL, then selectively re-adds only NET_BIND_SERVICE to allow binding to privileged ports if required. This eliminates dangerous capabilities such as SYS_ADMIN or NET_ADMIN that could enable container escapes.

No-New-Privileges Protection

The --security-opt=no-new-privileges:true flag ensures that processes cannot gain additional privileges through mechanisms like setuid binaries, even if they attempt to execute privileged operations.

docker run \
  --read-only \
  --tmpfs /tmp \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges:true \
  -v ./iii-config.yaml:/app/iii-config.yaml:ro \
  -p 3111:3111 -p 49134:49134 -p 3112:3112 -p 9464:9464 \
  iiidev/iii:latest

Network Surface Minimization

The security model restricts network exposure by publishing only the specific ports required for engine operation. As documented in the README, these include:

  • 3111: Primary engine port
  • 3112: Secondary engine port
  • 49134: Internal communication
  • 9464: Metrics endpoint

This selective port binding reduces the network attack surface exposed to other services or the internet.

CI/CD Security Scanning and SBOM Generation

The continuous integration pipeline enforces security through automated vulnerability scanning. According to .github/workflows/docker-engine.yml, every image build undergoes scanning with Trivy to detect critical and high-severity CVEs before publication. The workflow also generates a Software Bill of Materials (SBOM) to provide provenance and transparency for downstream consumers.

- name: Run Trivy vulnerability scanner
  continue-on-error: true
  uses: aquasecurity/trivy-action@0.35.0
  with:
    image-ref: ${{ env.DOCKERHUB_REPO }}:${{ needs.setup.outputs.version }}
    format: 'sarif'
    output: 'trivy-results.sarif'
    severity: 'CRITICAL,HIGH'
    exit-code: '0'

Worker Container Isolation

The security model extends beyond the main engine to user-provided worker functions. Separate Docker images for Python and Node workers enforce the same non-root execution principles. The Python worker image, defined in crates/iii-worker/images/python/Dockerfile, creates a dedicated python-user (UID 1000) and installs only minimal build tools required for the language runtime. Similarly, the Node worker uses a dedicated node user.

FROM python:3.12-slim

ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    LANG=C.UTF-8 \
    DEBIAN_FRONTEND=noninteractive

ARG USER_NAME="python-user"
ARG USER_UID="1000"
ARG USER_GID="100"

RUN apt-get update && apt-get install -y --no-install-recommends \
        build-essential curl git wget libssl-dev ca-certificates \
    && apt-get clean && rm -rf /var/lib/apt/lists/* \
    && useradd -m -s /bin/bash -N -u $USER_UID $USER_NAME \
    && mkdir -p /home/$USER_NAME/work \
    && chown -R $USER_NAME:$USER_GID /home/$USER_NAME \
    && pip install --no-cache-dir --upgrade pip setuptools wheel \
    && pip install --no-cache-dir black flake8 mypy pytest pytest-cov requests ipython

USER $USER_NAME
WORKDIR /home/$USER_NAME/work
CMD ["tail", "-f", "/dev/null"]

This approach ensures that arbitrary user code executes within the same restricted environment as the main engine, preventing privilege escalation from worker processes.

Summary

The security model for iii Docker containers creates a minimal, auditable runtime through multiple defence-in-depth layers:

  • Distroless foundation: The gcr.io/distroless/cc-debian12:nonroot base image eliminates shells and package managers from the engine container.
  • Non-root execution: Both the engine and worker containers run as unprivileged users without UID 0 access.
  • Immutable filesystems: Production deployments use --read-only root filesystems with ephemeral /tmp storage.
  • Capability minimization: All Linux capabilities drop except NET_BIND_SERVICE, with --no-new-privileges preventing escalation.
  • Supply chain verification: CI pipelines scan images with Trivy and generate SBOMs for vulnerability tracking.
  • Network restriction: Only ports 3111, 3112, 49134, and 9464 are exposed, limiting the attack surface.

Frequently Asked Questions

What makes distroless images more secure than standard container images?

Distroless images contain only the application and its runtime dependencies, excluding package managers, shells, and standard Linux utilities. In engine/Dockerfile, the iii project uses gcr.io/distroless/cc-debian12:nonroot, which prevents attackers from spawning interactive shells or installing additional tooling if they compromise the container. This significantly reduces the attack surface compared to Debian or Alpine base images that include full operating system utilities.

How does iii prevent privilege escalation from worker functions?

The iii project isolates user-provided code in separate worker containers defined in crates/iii-worker/images/python/Dockerfile and crates/iii-worker/images/node/Dockerfile. These images create dedicated non-root users (python-user and node) with UID 1000, install only minimal language runtimes, and never execute as root. This containment ensures that arbitrary user code runs with the same privilege restrictions as the main engine, preventing container escapes through setuid binaries or kernel exploitation.

Which Linux capabilities does the iii container retain?

The production runtime configuration drops all Linux capabilities using --cap-drop=ALL and selectively re-adds only NET_BIND_SERVICE if the engine needs to bind to privileged ports below 1024. This configuration, documented in engine/README.md, removes dangerous capabilities such as SYS_ADMIN, NET_ADMIN, and SYS_PTRACE that attackers commonly exploit for container escapes or network sniffing.

How does the iii project detect vulnerabilities in container images?

The continuous integration pipeline defined in .github/workflows/docker-engine.yml automatically scans every build with Trivy, a vulnerability scanner that detects CVEs in OS packages and application dependencies. The workflow specifically flags critical and high-severity vulnerabilities and generates a Software Bill of Materials (SBOM) to provide provenance tracking. This ensures that known security issues are identified before images publish to Docker Hub.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →