# Container image for running Cloud-OpsBench on AWS Fargate. # # Build (from repo root): # docker build -f tests/benchmarks/cloudopsbench/infra/Dockerfile.bench -t : --platform linux/amd64 . # # Push: # aws ecr get-login-password --region us-east-1 \ # | docker login --username AWS --password-stdin .dkr.ecr.us-east-1.amazonaws.com # docker push : # # Pin the resulting image tag (or digest) in the bench pre-registration YAML. # ECR is configured with IMMUTABLE tags - a tag pushed once cannot be # overwritten. Use a unique tag per build (semver, git SHA, or build ID). # # Two-stage build: # - `builder` installs deps with build-essential available, compiling any # source-only wheels into the project venv at /app/.venv. # - `bench` (final) starts from a clean python:3.12-slim, COPYs only the # /app/.venv + source code from builder, and installs ca-certificates # for outbound HTTPS to LLM APIs. No build-essential in the runtime # image -- saves ~330 MB. # --------------------------------------------------------------------------- # # Stage 1: builder # # --------------------------------------------------------------------------- # FROM python:3.12-slim AS builder ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PIP_NO_CACHE_DIR=1 \ PIP_DISABLE_PIP_VERSION_CHECK=1 \ UV_LINK_MODE=copy \ UV_NO_CACHE=1 WORKDIR /app # Build deps - dropped before the final image. `apt-get upgrade` pulls # the latest patched versions of base-image packages (libcap2, libsystemd0, # etc.) -- the python:3.12-slim base tag tracks behind Debian's security # patches, so without this step the image inherits known HIGH CVEs. RUN apt-get update \ && apt-get -y upgrade \ && apt-get install -y --no-install-recommends \ build-essential \ ca-certificates \ curl \ && rm -rf /var/lib/apt/lists/* # uv from official image COPY --from=ghcr.io/astral-sh/uv:0.5.18 /uv /uvx /usr/local/bin/ # Dependency layer - separate from source so source changes don't bust # the (slow) dep install cache. COPY pyproject.toml uv.lock ./ RUN uv sync --frozen --no-dev --no-install-project # Source - only what the bench needs. COPY surfaces/ ./surfaces/ COPY config/ ./config/ COPY core/ ./core/ COPY integrations/ ./integrations/ COPY platform/ ./platform/ COPY tools/ ./tools/ COPY tests/__init__.py ./tests/__init__.py COPY tests/benchmarks/ ./tests/benchmarks/ # Install the project itself now that source is in place RUN uv sync --frozen --no-dev # --------------------------------------------------------------------------- # # Stage 2: bench (runtime) # # --------------------------------------------------------------------------- # FROM python:3.12-slim AS bench # OPENSRE_SHA: the source SHA the bench code was built from. Stamped into # the image at build time by the benchmark-image workflow (--build-arg # OPENSRE_SHA=). _git_sha() in tests/benchmarks/_framework/runner.py # reads this env var first; without it, the runtime falls back to # `git rev-parse` (which fails — no .git in the container) and stamps # (no-git) into provenance.json. The promotable-run integrity gate now # rejects (no-git), so a missing OPENSRE_SHA breaks the promotable cycle # loudly at run start rather than silently producing unverifiable # artifacts. Default is (no-git) so manual `docker build` without the # build arg still completes (and is appropriately marked unverifiable). ARG OPENSRE_SHA="(no-git)" ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 \ PATH="/app/.venv/bin:${PATH}" \ OPENSRE_SHA=${OPENSRE_SHA} WORKDIR /app # Runtime deps: ca-certificates (for TLS to LLM provider APIs) + awscli # (the entrypoint pulls the Cloud-OpsBench corpus from S3 at task startup — # see entrypoint.sh). No build-essential, no curl, no compilers. # `apt-get upgrade` pulls latest patched base-image packages (libcap2, # libsystemd0, etc.) -- same reason as in the builder stage. # # awscli is installed via pip (small, single Python tool) rather than apt # or the v2 binary bundle (which adds ~80 MB). Pinned for reproducibility. RUN apt-get update \ && apt-get -y upgrade \ && apt-get install -y --no-install-recommends ca-certificates \ && rm -rf /var/lib/apt/lists/* \ && pip install --no-cache-dir 'awscli==1.45.19' # Non-root user for the bench process # beyond `bench`'s home. UID 10001 is well above any system UID range. RUN groupadd --system --gid 10001 bench \ && useradd --system --uid 10001 --gid 10001 --home-dir /app --shell /usr/sbin/nologin bench # Copy the prepared venv + source from the builder stage in one go. # The venv at /app/.venv has the project installed and all deps available. # PATH (set above) makes /app/.venv/bin take precedence over system python. # --chown so the bench user can write to /app at runtime (entrypoint.sh # writes the corpus under tests/benchmarks/cloudopsbench/benchmark/). COPY --from=builder --chown=bench:bench /app /app # Entrypoint wrapper: pulls the corpus from S3 before invoking the bench # CLI. Required env (set by the ECS task definition): # BENCH_CORPUS_S3_BUCKET, BENCH_CORPUS_HF_REVISION COPY --chown=bench:bench tests/benchmarks/cloudopsbench/infra/entrypoint.sh /app/entrypoint.sh RUN chmod +x /app/entrypoint.sh \ && chown bench:bench /app # ^^^ chown the WORKDIR itself, not just its contents. Without this the bench # user can write INTO existing subtrees (the COPY --chown above covered them) # but cannot create new directories like .bench-results/ AT /app — WORKDIR # created the directory owned by root and a per-file --chown doesn't touch # the directory's own ownership. Surfaces at runtime as # `PermissionError: [Errno 13] Permission denied: '.bench-results'`. USER bench ENTRYPOINT ["/app/entrypoint.sh"] # Default args passed to the bench CLI -- show CLI help if no subcommand # passed. Override via ECS task `command` for actual runs. CMD ["--help"]