ADR-0013: Docker infrastructure and image distribution

View Source

Status

Proposed

Context

The Docker executor (Conjure.Executor.Docker) is the recommended production execution backend (ADR-0010). Currently:

  1. The Dockerfile is embedded as a string in docker.ex
  2. No pre-built images are available
  3. Users must build the image themselves
  4. The priv/docker/ directory does not exist

This creates friction for adoption:

# Current state: users must build before using
Conjure.Executor.Docker.build_image()  # Builds from embedded Dockerfile

Production deployments would benefit from:

  • Pre-built, tested images
  • Versioned image tags matching library versions
  • CI/CD integration for automated builds
  • Customization options for enterprise environments

Decision

We will establish Docker infrastructure with the following components:

1. External Dockerfile

Move the Dockerfile from embedded string to priv/docker/Dockerfile:

# priv/docker/Dockerfile
FROM ubuntu:24.04

LABEL org.opencontainers.image.source="https://github.com/holsee/conjure"
LABEL org.opencontainers.image.description="Conjure sandbox execution environment"
LABEL org.opencontainers.image.version="${VERSION}"

# System packages
RUN apt-get update && apt-get install -y --no-install-recommends \
    python3.12 python3-pip python3-venv \
    nodejs npm \
    bash git curl wget jq \
    poppler-utils qpdf \
    && rm -rf /var/lib/apt/lists/*

# Python packages (matching Anthropic's environment)
RUN pip3 install --break-system-packages \
    pyarrow openpyxl xlsxwriter xlrd pillow \
    python-pptx python-docx pypdf pdfplumber \
    pypdfium2 pdf2image pdfkit tabula-py \
    reportlab img2pdf pandas numpy matplotlib \
    pyyaml requests beautifulsoup4

# Non-root user for security
RUN useradd -m -s /bin/bash -u 1000 sandbox
USER sandbox
WORKDIR /workspace

ENV PYTHONUNBUFFERED=1
ENV NODE_ENV=production

2. Image Variants

Provide multiple image variants for different use cases:

priv/docker/
 Dockerfile              # Default full-featured image
 Dockerfile.minimal      # Minimal image (bash only)
 Dockerfile.python       # Python-focused (no Node.js)
 Dockerfile.node         # Node.js-focused (no Python)

3. Image Distribution

Publish images to GitHub Container Registry (ghcr.io):

  • ghcr.io/holsee/conjure-sandbox:latest
  • ghcr.io/holsee/conjure-sandbox:0.1.0
  • ghcr.io/holsee/conjure-sandbox:0.1.0-minimal
  • ghcr.io/holsee/conjure-sandbox:0.1.0-python

4. GitHub Actions Workflow

# .github/workflows/docker.yml
name: Docker Images

on:
  release:
    types: [published]
  push:
    branches: [main]
    paths: ['priv/docker/**']

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      - uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - uses: docker/build-push-action@v5
        with:
          context: .
          file: priv/docker/Dockerfile
          push: true
          tags: |
            ghcr.io/${{ github.repository_owner }}/conjure-sandbox:${{ github.ref_name }}
            ghcr.io/${{ github.repository_owner }}/conjure-sandbox:latest

5. Default Image Configuration

Update Conjure.Executor.Docker to use published images by default:

@default_image "ghcr.io/holsee/conjure-sandbox:latest"

# With fallback to local build
def ensure_image(opts \\ []) do
  image = Keyword.get(opts, :image, @default_image)

  case pull_image(image) do
    :ok -> {:ok, image}
    {:error, _} -> build_image(opts)  # Fallback to local build
  end
end

6. Mix Task Integration

The mix conjure.docker.build task reads from priv/docker/:

def run(args) do
  dockerfile = Path.join(:code.priv_dir(:conjure), "docker/Dockerfile")
  # Build using external Dockerfile
end

Consequences

Positive

  • Faster onboarding—users can pull pre-built images
  • Versioned images ensure reproducibility
  • CI/CD builds ensure images are tested
  • External Dockerfile enables customization
  • Multiple variants serve different needs

Negative

  • Must maintain image builds in CI
  • Registry costs (GitHub provides generous free tier)
  • Image size concerns (full image ~1GB)
  • Version coordination between library and images

Neutral

  • Users can still build locally if preferred
  • Enterprise users can fork and customize
  • Image updates independent of library releases possible

Alternatives Considered

Docker Hub Distribution

Using Docker Hub instead of GHCR. Rejected because:

  • GHCR integrates better with GitHub Actions
  • No rate limiting for authenticated users
  • Keeps everything in one ecosystem

No Pre-built Images

Require users to always build locally. Rejected because:

  • Significant friction for new users
  • Build times slow down development
  • Inconsistent environments across users

Single Image Only

Only provide one full-featured image. Partially accepted—we start with variants but prioritize the default image.

References