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

> Discover the iii Docker containers security model with distroless runtime. Learn how defense-in-depth reduces attack surface using non-root, read-only, and minimal capabilities.

- Repository: [iii/iii](https://github.com/iii-hq/iii)
- Tags: deep-dive
- Published: 2026-05-28

---

**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.

```dockerfile
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`](https://github.com/iii-hq/iii/blob/main/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.

```bash
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`](https://github.com/iii-hq/iii/blob/main/.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.

```yaml
- 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.

```dockerfile
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`](https://github.com/iii-hq/iii/blob/main/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`](https://github.com/iii-hq/iii/blob/main/.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.