Tech Appendix For Platform Engineers 17 min read Updated May 2026

Custom Docker Images for AI Agents

Your IT team images laptops with the corporate stack so a new hire is productive on day one. AI workers need the same. A pre-baked Docker image is "the company laptop" — Claude Code already installed, your internal tools already there, your CLAUDE.md at /etc/claude.md. Cuts startup 2 min → 30 sec.

Three reference Dockerfiles (coding, browser, vertical agent). CI/CD via GitHub Actions. Versioning + rollback. Multi-stage builds. Image-bloat hygiene with measurements. CVE scanning workflow. Warm-pool + custom-image production pattern. Cost math at scale. 15 FAQs.

Quick Answer

Build a Dockerfile that includes your standard tools (Claude Code / Codex CLI, Python deps, internal CLI, your CLAUDE.md, git config, ssh known_hosts). Push to ECR. Reference the image when creating sandboxes. Cold-start drops from ~120 sec to ~30 sec; warm-pool size requirements drop accordingly. At 1,000 daily runs, saves ~$8/day in cold-start tax. Use immutable date-based tags (v2026.05.10-001), never :latest in production. Multi-stage builds keep image under 2 GB.

Why custom images matter for AI agent fleets

Three reasons custom images are the right pattern for production AI agent fleets:

  1. Cold start is dead time. A typical agent run is 30 sec - 5 min. If 90 sec of that is "install Claude Code, pip install dependencies, pull docker images" — every run has a 30-50% overhead tax. At 1,000 runs/day, that's $8/day in compute spent on setup, not work.
  2. Reproducibility. Production agents need known-good environments. Pinned dependency versions, validated tool versions, tested integrations. Custom images make this an artifact you can reason about — a specific image tag is a specific environment.
  3. Audit and supply chain. Compliance regimes increasingly want to know "exactly what software was on the box when the agent ran?" Custom images with signed manifests answer that. Default base images leave that question open.

Operators who don't bake images live with the cold-start tax + the reproducibility flake + a fuzzy supply-chain story. Operators who do bake spend two engineering weeks on the pipeline, then never think about it again. The math is dramatic.

When to bake your own image (and when not to)

Custom images aren't free — there's a CI/CD pipeline to build, an image registry to manage, a versioning discipline to follow, and a CVE-scanning cadence to maintain. The cost is small but real. Use this decision tree:

Bake if:

Don't bake if:

Decision-table by use case

Use caseBake custom image?Why
AP clerk processing 50 invoices/dayYes50 daily runs × 90 sec install = 75 min/day wasted
Paralegal doing 10 NDA reviews/dayMarginal — yes if internal tools10 runs × 60 sec = small saving; bake if you have firm-specific tools
Always-on monitoring agentYesStarted rarely, but reproducibility matters; CVE scanning matters
One-off research investigationNoSingle invocation; the install cost is one-time
Customer-support tier-1 (high volume)YesHundreds of daily runs; cold-start tax compounds
Custom Docker image you'd ship to customersYes — requiredKnown artifact for SaaS multi-tenant
Development / prototyping agentNoIteration speed matters more than startup time
GPU ML inference agentYesCUDA + drivers must be pinned and tested

Reference Dockerfile #1: Coding agent (Claude Code / Codex CLI)

dockerfile — coding agent base
# Start from Sandbox Platform's blessed base image
FROM registry.ab0t.com/agent-base:1.0

# Stage 1: install agent harness + system tools
RUN apt-get update && apt-get install -y --no-install-recommends \
      git ripgrep fd-find jq curl gnupg \
      build-essential pkg-config \
    && rm -rf /var/lib/apt/lists/*

# Install Claude Code (pin a specific version for reproducibility)
RUN npm install -g @anthropic-ai/claude-code@1.7.3

# Install Codex CLI as alternative harness
RUN pip install --no-cache-dir openai-codex-cli==2.4.1

# Stage 2: install Python deps the team commonly uses
COPY requirements.txt /tmp/
RUN pip install --no-cache-dir -r /tmp/requirements.txt && rm /tmp/requirements.txt

# Stage 3: install team's internal CLI from S3 or GHCR
RUN curl -sS https://internal.example.com/cli/install.sh | bash

# Stage 4: configure git defaults (the agent will commit; this is its identity)
RUN git config --global user.email "agent@yourcompany.com" \
    && git config --global user.name "AI Agent (CompanyName)" \
    && git config --global init.defaultBranch main

# Stage 5: SSH known-hosts for your internal git server
COPY known_hosts /home/ec2-user/.ssh/known_hosts
RUN chown ec2-user:ec2-user /home/ec2-user/.ssh/known_hosts \
    && chmod 644 /home/ec2-user/.ssh/known_hosts

# Stage 6: standard agent job description (mountable, but baked default exists)
COPY CLAUDE.md /etc/claude.md
ENV CLAUDE_GLOBAL_CONFIG=/etc/claude.md

# Default to ec2-user (don't run as root)
USER ec2-user
WORKDIR /workspace

# Healthcheck for systemd
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
    CMD claude --version || exit 1

# Default command — agent harness reads from CLAUDE_GLOBAL_CONFIG
CMD ["claude", "--resume"]

Build size: ~1.2 GB (base 700 MB + Claude Code + Python deps + internal CLI). Build time: ~3-5 min on a CI runner with caching.

Reference Dockerfile #2: Browser agent (Anthropic Computer Use / Operator)

dockerfile — browser agent base
# Start from a desktop-capable base for computer-use agents
FROM registry.ab0t.com/xfce-base:1.0

# Browser binaries
RUN apt-get update && apt-get install -y --no-install-recommends \
      chromium-browser firefox-esr \
      x11-utils xauth dbus-x11 \
      libnss3 libxcomposite1 libxdamage1 libxrandr2 \
      libgbm1 libxkbcommon0 libpango-1.0-0 \
    && rm -rf /var/lib/apt/lists/*

# Playwright for programmatic browser control
RUN pip install --no-cache-dir playwright==1.42.0 \
    && playwright install chromium firefox \
    && playwright install-deps

# noVNC for operator-side viewing of the agent's screen
RUN apt-get update && apt-get install -y --no-install-recommends \
      novnc websockify x11vnc xvfb \
    && rm -rf /var/lib/apt/lists/*

# Anthropic computer-use SDK
RUN pip install --no-cache-dir anthropic[computer-use]==0.45.0

# Pre-warm browser cache (saves first-launch time)
RUN chromium-browser --headless --disable-gpu --dump-dom about:blank > /dev/null

# Standard CLAUDE.md for browser agents (mountable but baked default)
COPY CLAUDE.md /etc/claude.md

USER ec2-user
WORKDIR /workspace

# Browser agents launch via a startup script that brings up Xvfb + browser + agent
CMD ["/usr/local/bin/start-browser-agent.sh"]

Build size: ~2.4 GB (XFCE base + browsers + Playwright). Larger than coding agents because the GUI stack is substantial. Worth it for browser workloads — the alternative is 60+ sec at every cold start to install the same stack.

Reference Dockerfile #3: Vertical agent (legal / finance / healthcare)

dockerfile — paralegal vertical agent
# Multi-stage: build context separately from runtime image
FROM python:3.12-slim AS builder

RUN apt-get update && apt-get install -y build-essential
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Runtime stage — slimmer
FROM registry.ab0t.com/agent-base:1.0

# Copy installed packages from builder
COPY --from=builder /root/.local /home/ec2-user/.local

# Vertical-specific tools
RUN npm install -g @anthropic-ai/claude-code@1.7.3

# Citation-verifier tool (this firm's specific compliance requirement)
COPY citation-verifier /usr/local/bin/citation-verifier
RUN chmod +x /usr/local/bin/citation-verifier

# NetDocuments / iManage CLI (firm uses this)
RUN pip install --no-cache-dir \
      netdocuments-cli==3.1.0 \
      imanage-cli==2.5.0

# Bluebook citation linter (must-have for legal work)
RUN pip install --no-cache-dir bluebook-linter==1.2.0

# Westlaw / Lexis API clients
RUN pip install --no-cache-dir westlaw-api==2.0.3 lexis-api==1.4.1

# Standard CLAUDE.md template for paralegal role
COPY claude-paralegal.md /etc/claude.md

# Environment
ENV PATH="/home/ec2-user/.local/bin:$PATH"
USER ec2-user
WORKDIR /workspace

CMD ["claude", "--resume"]

Build size: ~1.6 GB. Vertical-specific tools layered on top of the base. Update cycle for these images is typically tied to the firm's tool refreshes (quarterly is fine; weekly is overkill for stable vertical tooling).

CI/CD: GitHub Actions workflow

You don't build images by hand in production. CI builds, scans, and pushes:

yaml — .github/workflows/build-agent-image.yml
name: Build Agent Image

on:
  push:
    branches: [main]
    paths:
      - 'docker/agent/**'
      - '.github/workflows/build-agent-image.yml'
  schedule:
    # Weekly rebuild for security patches even if no code change
    - cron: '0 6 * * 1'
  workflow_dispatch:

jobs:
  build:
    runs-on: ubuntu-22.04
    permissions:
      id-token: write   # for OIDC to AWS
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-ecr-push
          aws-region: us-west-2

      - name: Login to ECR
        id: ecr-login
        uses: aws-actions/amazon-ecr-login@v2

      - name: Compute version tag
        id: version
        run: |
          DATE=$(date +%Y.%m.%d)
          BUILD_NUM=$(date +%H%M)
          echo "tag=v${DATE}-${BUILD_NUM}" >> $GITHUB_OUTPUT

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Build & push
        uses: docker/build-push-action@v5
        with:
          context: ./docker/agent
          push: true
          tags: |
            ${{ steps.ecr-login.outputs.registry }}/agent:${{ steps.version.outputs.tag }}
            ${{ steps.ecr-login.outputs.registry }}/agent:latest-staging
          cache-from: type=gha
          cache-to: type=gha,mode=max

      - name: Scan image for CVEs (Trivy)
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ steps.ecr-login.outputs.registry }}/agent:${{ steps.version.outputs.tag }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: CRITICAL,HIGH

      - name: Upload Trivy results to GitHub Security
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: 'trivy-results.sarif'

      - name: Test the image (smoke)
        run: |
          docker run --rm ${{ steps.ecr-login.outputs.registry }}/agent:${{ steps.version.outputs.tag }} \
            claude --version

      - name: Notify Slack
        if: success()
        run: |
          curl -X POST $SLACK_WEBHOOK -d "{\"text\": \"Agent image v${{ steps.version.outputs.tag }} pushed\"}"
        env:
          SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}

What this does:

Versioning: never :latest in production

Use immutable tags. Three reasons:

Three tag schemes that work

SchemeExampleBest for
Date-basedv2026.05.10-1430Continuously-rebuilt images; "image of the day" semantics
Semverv1.4.7Stable APIs; rare rebuilds; explicit breaking-change signaling
Git SHAsha-a1b2c3dTied to repo; useful for "exact source" debugging
Combinationv1.4.7-sha-a1b2c3dBest of both; supply-chain audit trail

For most AI agent fleets, date-based + git SHA in image labels is the right choice — easy to reason about, easy to rollback, captures the source-of-truth.

Rollback procedure

1

Identify the bad image

Audit log shows which image is in use. CloudWatch alarms or operator-noticed quality dip identify the bad version.

2

Update workspace default

Change the workspace's default image tag in the dashboard from the bad version to the previous-known-good version. New sandboxes use the rolled-back image immediately.

3

Recycle running sandboxes

Existing running sandboxes still use the bad image. Recycle them (rolling restart) to pick up the rolled-back version. The platform supports staged rollout: 10% / 50% / 100% over a few hours.

4

Investigate and fix

Don't just delete the bad tag — keep it so post-incident investigation can reproduce. Investigate; fix; push a new tag; ship.

Image-bloat hygiene

Bigger images = slower pulls = slower cold starts. Keep yours under 2 GB if possible. Tactics:

Multi-stage builds

Separate the build environment from the runtime environment. Compile in stage 1 (with build tools); copy artifacts to stage 2 (slim runtime).

dockerfile — multi-stage example
# Stage 1: builder (heavy)
FROM python:3.12 AS builder
RUN apt-get update && apt-get install -y build-essential cargo
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
# builder image is ~1.5 GB; we don't ship it

# Stage 2: runtime (slim)
FROM python:3.12-slim
COPY --from=builder /root/.local /home/agent/.local
ENV PATH="/home/agent/.local/bin:$PATH"
# final image is ~300 MB — only the deps, no build tools

Result: a 5× smaller image with the same runtime capabilities.

Layer discipline

Real-world bloat measurements

Without hygieneWith hygieneSavings
FROM python:3.12 (full): 1100 MBFROM python:3.12-slim: 130 MB970 MB
apt without cleanup: +400 MBapt with cleanup: +0 MB after400 MB
pip without --no-cache-dir: +200 MBpip with --no-cache-dir: +0 MB200 MB
Single stage: 2.5 GBMulti-stage: 800 MB1.7 GB

CVE scanning workflow

Custom images age. The base image gets CVE patches; your custom image won't unless you rebuild. Three layers of CVE defense:

Build-time scanning

Trivy in CI (shown above) catches known CVEs before push. Fail the build on CRITICAL or HIGH severity; warn on MEDIUM.

Registry scanning

ECR scans every image on push (if you enable it). Re-scans periodically for newly-disclosed CVEs. The dashboard surfaces vulnerable images so you know which need rebuild.

terraform — ECR with continuous scanning
resource "aws_ecr_repository" "agent" {
  name                 = "agent"
  image_tag_mutability = "IMMUTABLE"

  image_scanning_configuration {
    scan_on_push = true
  }

  encryption_configuration {
    encryption_type = "KMS"
    kms_key         = aws_kms_key.images.arn
  }
}

resource "aws_ecr_registry_scanning_configuration" "continuous" {
  scan_type = "ENHANCED"

  rule {
    scan_frequency = "CONTINUOUS_SCAN"
    repository_filter {
      filter      = "agent*"
      filter_type = "WILDCARD"
    }
  }
}

Scheduled rebuilds

Even without code changes, rebuild weekly to pick up base-image patches. The cron in the GitHub Actions workflow above does this. For high-security workloads, daily.

Pairing with warm pools (the production pattern)

Warm pools + custom images is the production pattern. Pool members are pre-launched from your custom image; the agent's tools are baked in; claim is sub-second; first action happens immediately.

LayerWhat it doesCost
Custom imageCuts cold-start from 120 sec to 30 secImage storage: ~$0.10/GB-month per region
Warm poolCuts claim time from 30 sec to 0.8 sec$0.1/hr per pool member idle
Combined120 sec → 0.8 sec; 150× fasterPool insurance + image storage

Read more in Warm Pools. The combo is what high-volume customer-facing agents use.

Cost math at scale

ScenarioCold-start timeCost / 1000 runs (ab0t.medium)
Default image + install on demand120 sec$13.30 cold-start tax
Custom image, pre-baked30 sec$3.30 cold-start tax
Custom image + warm pool of 50.8 sec~$0.50/hr pool + $0.30/day for spikes

For agencies running <100 runs/day, custom images probably aren't worth the maintenance. For platforms running 1000+ runs/day, they save $8-10/day and remove startup variability — usually a clear win.

Image storage cost

ECR charges $0.10/GB-month for storage. A 1.5 GB image = $0.15/month. Even for fleets with 20 different role-images: ~$3/month total. The cost is the time-to-build and the CI minutes, not the storage.

Real-world patterns from the field

Pattern 1: Single base image, many role overlays

A SaaS startup. One agent-base:vX.Y.Z image with shared tools (Claude Code, common Python deps, internal CLI). Per-role images (paralegal:vX.Y.Z, support:vX.Y.Z, finance:vX.Y.Z) layered on top with role-specific tools. Base updates monthly; role overlays update as needed. 6 role-images, ~$5/month total storage, all built from the same CI pipeline.

Pattern 2: Customer-specific images for SaaS multi-tenancy

A vertical-AI SaaS. Per-customer Docker images that include their specific integrations and CLAUDE.md. ~50 customers, ~50 images, all built from the same CI with templated Dockerfiles. Each customer's image references their specific service-account configurations. Storage cost ~$50/month total.

Pattern 3: GPU + ML stack pre-baked

An ML team. Custom GPU image based on nvidia/cuda:12.4-runtime-ubuntu22.04 with PyTorch, transformers, and team's internal model-serving stack. 4.8 GB image; 90 sec to pull on first launch but cached after. Saves 6+ minutes of CUDA-driver-pinning on every cold start. Critical for ML workloads where a missed driver version means workload doesn't run at all.

Pattern 4: Auto-rebuild on dependency security advisory

An agency. CI workflow watches dependabot security advisories; on a HIGH or CRITICAL advisory affecting the image, triggers an out-of-band rebuild. Combined with weekly scheduled rebuilds, image stays current. Quarterly review of vulnerability backlog confirms no stale CVEs.

Pattern 5: Pinned model-binary version for reproducibility

A finance team running compliance-sensitive agents. Image pins specific model-API client library versions; specific CLI versions; specific Python and Node minor versions. When a regulator asks "what software was on the box when this report was produced?", the image SHA + manifest answers exactly.

Anti-patterns: what not to do

Comparison vs platform alternatives

PlatformCustom-image story
Sandbox PlatformBring-your-own image from any OCI registry; ECR cross-account; per-workspace image policy; staged rollout; CVE scanning integration
E2BPre-baked templates + custom Dockerfile builds via E2B's build pipeline
ModalImage-as-code in Python; Modal builds and caches; less Docker-native
Daytonadevcontainer.json-based environments; Docker-compatible
BrowserbasePre-baked browser images; custom-image less common (browsers are the unit)

For platform engineers comfortable with Docker / ECR, Sandbox Platform's "bring your own image" is the most flexible. For teams that want image-as-code with less Docker friction, Modal's approach is friendlier. Pick by ecosystem and team preference.

Frequently asked questions

Can I use Docker Hub instead of ECR?

Yes — any OCI-compatible registry the platform's IAM can authenticate to. ECR is the default for AWS-hosted workspaces. Docker Hub / GHCR / GCR / Quay all supported with proper credentials.

What happens if my image is broken?

Sandbox provisioning fails fast with a "image pull / start" error in the audit log. The platform's blue-green pattern means a bad image doesn't take down running sandboxes. Roll back to a previous tag.

How big can the image be?

Up to 8 GB practical. Above that, pull time dominates start time. Most production images are 1.5-3 GB.

Can I have different images for different agent roles?

Yes — the typical pattern. my-paralegal:v..., my-research:v..., my-support:v.... Common pattern: one base image with shared tools, one role image per role.

How do I keep the image in sync with my CLAUDE.md?

Two patterns. (1) Bake CLAUDE.md into the image — image is self-contained but rebuild needed for every CLAUDE.md change. (2) Mount CLAUDE.md from workspace settings at runtime — image stays static. For most teams, pattern 2 is right.

What about security patching for my custom image?

Custom images age. Ship a CI rebuild on a regular cadence — weekly is generous; monthly minimum. Your registry should scan for CVEs (ECR scanning, Snyk, Trivy).

Can multiple sandbox sizes use the same image?

Yes — the image is independent of sandbox size. The same image runs on ab0t.micro, ab0t.medium, ab0t.gpu.

How do I cache pip/npm packages across image rebuilds?

Three techniques: order Dockerfile RUN steps by stability; use BuildKit cache mounts (RUN --mount=type=cache,target=/root/.cache/pip ...); use a private package mirror.

Should I use Alpine or Debian/Ubuntu base?

For AI agents in 2026: Debian/Ubuntu. Alpine's musl libc breaks binary wheels for many ML / data libraries. Debian-slim is the right balance.

Do I need to sign my Docker images?

For most workloads, no. For supply-chain-sensitive deployments (FedRAMP, financial regulators), use cosign or Notation.

How do I share an image across multiple workspaces?

Push to a registry both workspaces can read. ECR cross-account access is the AWS-native pattern.

Can I update an image without restarting all sandboxes?

New image only applies to new sandbox launches. Recycle sandboxes (rolling restart) to pick up the new image. The platform supports staged rollout with canary recycling.

How do I keep secrets out of the image?

Never bake secrets. Three patterns: env vars at sandbox-create time; mount from a vault; IAM roles for AWS access.

What if my agent harness needs a specific OS or kernel version?

Pick a base image that ships with what you need. Anthropic Computer Use needs Linux desktop env. ML workloads need nvidia/cuda. Most agent harnesses work on standard Debian/Ubuntu.

How does the platform pull my private image at sandbox-launch?

The platform uses an IAM role with ECR pull permissions on your registry. Configure once at workspace level. For non-ECR registries, configure a docker-config secret in workspace settings.

What's next

Build your first custom image

One Dockerfile, ECR push, faster sandboxes.

Open Dashboard