Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ updates:
groups:
npm-minor-and-patch:
update-types: ["minor", "patch"]
# The infra images the release publishes. Each base is pinned by digest, and a release rebuilds an image only when its
# sources change, so a bump here is what picks up a base's fixes: the next release builds and publishes the image.
- package-ecosystem: "docker"
directories:
- "/src/agent_env/env/gateway"
- "/src/agent_env/env/envs/service_db"
- "/src/agent_env/env/envs/website_browser"
schedule:
interval: "weekly"
open-pull-requests-limit: 5
groups:
infra-base-images:
patterns: ["*"]
- package-ecosystem: "github-actions"
directory: "/"
schedule:
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,8 @@ package to PyPI. Run by hand on `main`, the workflow publishes the images alone.
Its jobs that run agent-env (`.github/scripts/infra_images.py`) hold no token that writes; the jobs that push
images or publish to PyPI install nothing and check what they're handed (`.github/scripts/infra_build.py`,
standard library and the docker CLI only).
Actions are pinned to commit SHAs; Dependabot updates them, the pinned uv, `uv.lock` and the UI's npm packages.
Actions are pinned to commit SHAs; Dependabot updates them, the pinned uv, `uv.lock`, the UI's npm packages and
the infra images' base images, each pinned by digest so that a bump rebuilds and republishes the image.

## Conventions

Expand Down
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ the previous release, so a clear pull request title is also the release note. A
A release also publishes the infra images agent-env ships, once the `PUBLISH_INFRA_IMAGES` repository variable is
`true`: one per Dockerfile under `src/agent_env/env`, at `ghcr.io/scaleapi/agentenv-<name>:src-<hex>` for linux/amd64
and linux/arm64, where the tag is the build context's source digest, so an unchanged Dockerfile is not built again.
Each of them pins its base images by digest, and Dependabot bumps those pins, so a base's fixes reach the published
image: a bump changes the sources, and the next release builds it again.
Installs pull them instead of building the infra images: a run on local stores registers a kind's images when GHCR has
every one for the sources installed, by the digest the wheel records or, with no record, the one GHCR serves, and
builds them otherwise; a sandbox that can't pull one builds it from its build context. The wheel and the release notes
Expand Down
2 changes: 1 addition & 1 deletion src/agent_env/env/envs/service_db/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM public.ecr.aws/docker/library/postgres:16-alpine
FROM public.ecr.aws/docker/library/postgres:16-alpine@sha256:721873c34ceb9f8d8fc265984940dc982404c105f19ad51be9fdc5970a6080ea

# ServiceDBEnv - PostgreSQL database for MCP services
#
Expand Down
4 changes: 2 additions & 2 deletions src/agent_env/env/envs/service_db/Dockerfile.db-mcp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# The Docker Hub image (crystaldba/postgres-mcp:latest) ships MCP 1.6.0,
# which only supports stdio and SSE transports.

FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim@sha256:e5b65587bce7de595f299855d7385fe7fca39b8a74baa261ba1b7147afa78e58 AS builder
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
ENV UV_PYTHON_DOWNLOADS=0

Expand All @@ -24,7 +24,7 @@ RUN sed -i 's/mcp = FastMCP("postgres-mcp")/from mcp.server.transport_security i

RUN uv sync --frozen --no-dev

FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm
FROM public.ecr.aws/docker/library/python:3.12-slim-bookworm@sha256:6a0e06f5ba0b373d051aabd3fffb9b873f27f9a2a3a14bcbe37dee6f0e3bbc70
RUN groupadd -r app && useradd -r -g app app
COPY --from=builder --chown=app:app /app /app

Expand Down
16 changes: 16 additions & 0 deletions tst/unit/test_release_infra_images.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import pytest
import yaml

from agent_env.env import bootstrap
from agent_env.env.published_images import RELEASE_FILE

REPO = Path(__file__).resolve().parents[2]
Expand Down Expand Up @@ -135,3 +136,18 @@ def test_what_a_registry_can_rate_limit_is_tried_again():
assert 'infra_build.py retry docker push "$BUILT"' in build
assert "infra_build.py retry docker buildx imagetools create" in publish
assert JOBS["infra-build"]["strategy"]["max-parallel"] <= 4


def test_every_infra_image_pins_its_bases_by_digest_and_dependabot_bumps_them():
"""A release rebuilds an image only when its sources change, so a base behind a bare tag never picks up its
fixes; pinned, the pin is a source, and Dependabot bumping it is what rebuilds and republishes the image."""
dependabot = yaml.safe_load((REPO / ".github" / "dependabot.yml").read_text())
(docker,) = [update for update in dependabot["updates"] if update["package-ecosystem"] == "docker"]

for image in bootstrap.PUBLISHED_IMAGES:
text = image.dockerfile.read_text()
stages = set(re.findall(r"^FROM\s+\S+\s+AS\s+(\S+)", text, re.IGNORECASE | re.MULTILINE))
bases = [ref for ref in re.findall(r"^FROM\s+(?:--\S+\s+)*(\S+)", text, re.IGNORECASE | re.MULTILINE)
if ref not in stages]
assert bases and all(re.search(r"@sha256:[0-9a-f]{64}$", ref) for ref in bases), (image.name, bases)
assert f"/{image.dockerfile.parent.relative_to(REPO)}" in docker["directories"], image.name
Loading