ADR-0013: Docker infrastructure and image distribution
View SourceStatus
Proposed
Context
The Docker executor (Conjure.Executor.Docker) is the recommended production execution backend (ADR-0010). Currently:
- The Dockerfile is embedded as a string in
docker.ex - No pre-built images are available
- Users must build the image themselves
- 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 DockerfileProduction 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=production2. 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:latestghcr.io/holsee/conjure-sandbox:0.1.0ghcr.io/holsee/conjure-sandbox:0.1.0-minimalghcr.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:latest5. 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
end6. 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
endConsequences
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.