fix(docker): ship Bedrock auth and current registry (#2982)

## Description

Fixes #1551 and #1692.

Every published Headroom Docker image now installs the existing
`bedrock` extra, so `--backend bedrock` can authenticate with temporary
STS, SSO, and credential-process credentials instead of failing because
`botocore` is absent.

Public Docker instructions now consistently use
`ghcr.io/headroomlabs-ai/headroom`. Several still pointed at the old
personal package, which is frozen at 0.27.0 and caused users to report
that no latest image existed.

## Type of Change

- [x] Bug fix
- [ ] New feature
- [ ] Breaking change
- [x] Documentation update
- [x] Build / CI

## Changes Made

- Add `bedrock` to the standalone Dockerfile default extras.
- Add `bedrock` to all nine root/code/slim/nonroot bake targets.
- Replace obsolete personal GHCR references in README, llms.txt, Compose
guidance, testing guidance, and wiki docs.
- Add release contract tests for Bedrock dependencies and the current
organization registry.

## Testing

- [x] Focused Docker release and Bedrock preflight tests pass.
- [x] Full updater suites pass: 69 tests.
- [x] `uv run ruff check tests/test_release_workflows.py`
- [x] `docker buildx bake --print`
- [x] `git diff --check`

## Real Behavior Proof

Before this change, every published bake target installed only `proxy`
or `proxy,code`, so `AWS_SESSION_TOKEN` selected an unavailable botocore
path. Public copy-paste commands also referenced
`ghcr.io/chopratejas/headroom`, which the existing migration code and
changelog identify as frozen at 0.27.0.

After this change, all nine parsed bake targets install `bedrock`; the
regression resolves that package extra and confirms `boto3` plus
`botocore`. Every public Docker instruction covered by the contract
names `ghcr.io/headroomlabs-ai/headroom`.

## Runtime Rollout Safety

This changes image contents and documentation only; proxy routing and
non-Docker installs are unchanged. Static AWS credentials remain
unaffected. Existing manifests using the deprecated image continue to be
migrated by the established install-state logic. Rollback is a
Docker/bake extras and documentation revert.

## Review Readiness

- [x] Two related Docker blockers batched in one PR
- [x] Regression coverage included
- [x] No unrelated lockfile changes
- [x] Ready for review
This commit is contained in:
JD Davis
2026-08-13 15:06:21 -05:00
committed by GitHub
parent ddd2a259ec
commit eafdf11a2c
9 changed files with 74 additions and 19 deletions
+4 -1
View File
@@ -47,7 +47,10 @@ COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
COPY crates/ crates/
COPY headroom/ headroom/
ARG HEADROOM_EXTRAS=proxy,code
# The standalone Dockerfile must support every backend advertised by
# `headroom proxy --backend`, including Bedrock temporary/SSO credentials.
# Those credentials require botocore (GH #1551), supplied by [bedrock].
ARG HEADROOM_EXTRAS=proxy,code,bedrock
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=cache,target=/usr/local/cargo/registry \
--mount=type=cache,target=/usr/local/cargo/git \
+1 -1
View File
@@ -415,7 +415,7 @@ Everything in this repo stays open source (Apache 2.0). The managed offering is
uv tool install --python 3.13 "headroom-ai[all]" # CLI, isolated app env
pip install "headroom-ai[all]" # Python, everything — includes the `headroom` CLI
npm install headroom-ai # TypeScript SDK (library only — no `headroom` CLI)
docker pull ghcr.io/chopratejas/headroom:latest
docker pull ghcr.io/headroomlabs-ai/headroom:latest
```
Granular extras: `[proxy]`, `[mcp]`, `[ml]` (Kompress-v2-base), `[code]`, `[memory]`, `[vector]` (optional HNSW backend — needs a C++ toolchain, not in `[all]`), `[relevance]`, `[image]`, `[agno]`, `[langchain]`, `[evals]`, `[pytorch-mps]` (Apple-GPU memory-embedder offload — set `HEADROOM_EMBEDDER_RUNTIME=pytorch_mps`). Requires **Python 3.10+**.
+1 -1
View File
@@ -107,7 +107,7 @@ There is **no native Windows wheel yet**, so pick one:
**A. Mechanism test (easiest — Docker Desktop or WSL2):**
```powershell
$env:HEADROOM_DOCKER_IMAGE = "ghcr.io/chopratejas/headroom:<branch-tag>" # ask the maintainer for the tag
$env:HEADROOM_DOCKER_IMAGE = "ghcr.io/headroomlabs-ai/headroom:<branch-tag>" # ask the maintainer for the tag
# run the Docker-native installer (scripts/install.ps1), then:
$env:GITHUB_COPILOT_TOKEN = "<your-token>"
headroom wrap copilot --subscription -- --model gpt-4o -p "Reply with: HEADROOM_OK"
+9 -9
View File
@@ -10,7 +10,7 @@ target "runtime-default" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime"
args = {
HEADROOM_EXTRAS = "proxy"
HEADROOM_EXTRAS = "proxy,bedrock"
RUNTIME_USER = "nonroot"
}
}
@@ -19,7 +19,7 @@ target "runtime" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime"
args = {
HEADROOM_EXTRAS = "proxy"
HEADROOM_EXTRAS = "proxy,bedrock"
RUNTIME_USER = "root"
}
}
@@ -28,7 +28,7 @@ target "runtime-nonroot" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime"
args = {
HEADROOM_EXTRAS = "proxy"
HEADROOM_EXTRAS = "proxy,bedrock"
RUNTIME_USER = "nonroot"
}
}
@@ -37,7 +37,7 @@ target "runtime-code" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime"
args = {
HEADROOM_EXTRAS = "proxy,code"
HEADROOM_EXTRAS = "proxy,code,bedrock"
RUNTIME_USER = "root"
}
}
@@ -46,7 +46,7 @@ target "runtime-code-nonroot" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime"
args = {
HEADROOM_EXTRAS = "proxy,code"
HEADROOM_EXTRAS = "proxy,code,bedrock"
RUNTIME_USER = "nonroot"
}
}
@@ -55,7 +55,7 @@ target "runtime-slim" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime-slim"
args = {
HEADROOM_EXTRAS = "proxy"
HEADROOM_EXTRAS = "proxy,bedrock"
RUNTIME_USER = "root"
}
}
@@ -64,7 +64,7 @@ target "runtime-slim-nonroot" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime-slim"
args = {
HEADROOM_EXTRAS = "proxy"
HEADROOM_EXTRAS = "proxy,bedrock"
RUNTIME_USER = "nonroot"
}
}
@@ -73,7 +73,7 @@ target "runtime-code-slim" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime-slim"
args = {
HEADROOM_EXTRAS = "proxy,code"
HEADROOM_EXTRAS = "proxy,code,bedrock"
RUNTIME_USER = "root"
}
}
@@ -82,7 +82,7 @@ target "runtime-code-slim-nonroot" {
inherits = ["_common", "docker-metadata-action"]
target = "runtime-slim"
args = {
HEADROOM_EXTRAS = "proxy,code"
HEADROOM_EXTRAS = "proxy,code,bedrock"
RUNTIME_USER = "nonroot"
}
}
+1 -1
View File
@@ -10,7 +10,7 @@
# 3. point your LLM client at http://localhost:8787 (proxy)
#
# Just want the proxy without the memory features? You can run the proxy image
# on its own (`docker run -p 8787:8787 ghcr.io/chopratejas/headroom`); the two
# on its own (`docker run -p 8787:8787 ghcr.io/headroomlabs-ai/headroom`); the two
# database services below are only required for the memory/relevance features.
#
# Ports exposed on the host:
+1 -1
View File
@@ -19,7 +19,7 @@ The canonical, always-current documentation index lives at the docs site below.
- Python: `pip install headroom-ai` (add `[all]` for every optional extra)
- TypeScript / Node: `npm install headroom-ai` (or `pnpm add headroom-ai`, `bun add headroom-ai`)
- Docker: `docker run -p 8787:8787 ghcr.io/chopratejas/headroom:latest`
- Docker: `docker run -p 8787:8787 ghcr.io/headroomlabs-ai/headroom:latest`
- Run the proxy: `headroom proxy --port 8787` then point any client at `http://127.0.0.1:8787`
- Wrap an agent in one command: `headroom wrap claude` (also: `codex`, `copilot`, `cursor`, `aider`, `opencode`, `cline`, `continue`, `goose`, `openhands`, `openclaw`, `vibe`, `omp`)
+52
View File
@@ -6,10 +6,62 @@ from pathlib import Path
import pytest
import yaml
from packaging.requirements import Requirement
try:
import tomllib
except ModuleNotFoundError: # pragma: no cover - Python 3.10 fallback
import tomli as tomllib # type: ignore[no-redef]
ROOT = Path(__file__).resolve().parent.parent
def test_every_published_docker_variant_includes_bedrock_auth_dependencies() -> None:
"""Every image that advertises ``--backend bedrock`` must ship botocore.
Temporary AWS credentials take LiteLLM's botocore-backed authentication
path. The default images previously installed only ``proxy``/``code``, so
the documented Docker Bedrock command failed at runtime with
``No module named 'botocore'`` (#1551). Keep the standalone Dockerfile and
every bake target on the existing ``bedrock`` package extra.
"""
dockerfile = (ROOT / "Dockerfile").read_text(encoding="utf-8")
assert "ARG HEADROOM_EXTRAS=proxy,code,bedrock" in dockerfile
bake = (ROOT / "docker-bake.hcl").read_text(encoding="utf-8")
extras_lines = [
line.strip() for line in bake.splitlines() if line.strip().startswith("HEADROOM_EXTRAS =")
]
assert len(extras_lines) == 9
assert all("bedrock" in line for line in extras_lines), extras_lines
project = tomllib.loads((ROOT / "pyproject.toml").read_text(encoding="utf-8"))
bedrock_names = {
Requirement(requirement).name
for requirement in project["project"]["optional-dependencies"]["bedrock"]
}
assert {"boto3", "botocore"} <= bedrock_names
def test_public_docker_instructions_use_the_current_organization_package() -> None:
"""Do not send users back to the personal GHCR package frozen at 0.27.0."""
public_docs = (
"README.md",
"llms.txt",
"docker-compose.yml",
"TESTING-copilot-subscription.md",
"wiki/cli.md",
"wiki/docker-install.md",
)
deprecated = "ghcr.io/chopratejas/headroom"
current = "ghcr.io/headroomlabs-ai/headroom"
for relative_path in public_docs:
content = (ROOT / relative_path).read_text(encoding="utf-8")
assert deprecated not in content, relative_path
assert current in content, relative_path
def test_docker_workflow_normalizes_repository_name_for_signing() -> None:
content = (ROOT / ".github" / "workflows" / "docker.yml").read_text(encoding="utf-8")
+2 -2
View File
@@ -637,7 +637,7 @@ Options:
(already the default).
--image TEXT Docker image to use when runtime=docker or
preset=persistent-docker. [default:
ghcr.io/chopratejas/headroom:latest]
ghcr.io/headroomlabs-ai/headroom:latest]
-?, --help Show this message and exit.
```
@@ -665,7 +665,7 @@ headroom install apply --preset persistent-docker --scope user
| `--memory` | off | Enable persistent memory in the managed runtime |
| `--telemetry` | off | Opt in to anonymous telemetry (off by default) |
| `--no-telemetry` | off | Force anonymous telemetry off (already the default) |
| `--image` | `ghcr.io/chopratejas/headroom:latest` | Docker image for Docker-backed installs |
| `--image` | `ghcr.io/headroomlabs-ai/headroom:latest` | Docker image for Docker-backed installs |
`apply` stores a manifest under
`${HEADROOM_WORKSPACE_DIR}/deploy/<profile>/manifest.json` (default
+3 -3
View File
@@ -27,7 +27,7 @@ irm https://raw.githubusercontent.com/chopratejas/headroom/main/scripts/install.
## What the installer does
1. Verifies Docker is installed and available.
2. Pulls `ghcr.io/chopratejas/headroom:latest` by default, or reuses / pulls `HEADROOM_DOCKER_IMAGE` when you set a custom image override.
2. Pulls `ghcr.io/headroomlabs-ai/headroom:latest` by default, or reuses / pulls `HEADROOM_DOCKER_IMAGE` when you set a custom image override.
3. Installs a `headroom` wrapper into `~/.local/bin` or `~/bin`.
4. Updates shell startup files so the wrapper directory is on `PATH`.
@@ -41,7 +41,7 @@ The wrapper keeps Headroom inside Docker and mounts host state back into the con
Port `8787` stays the default, so `http://localhost:8787` works the same way as a native install.
Published releases also push versioned GHCR tags such as `ghcr.io/chopratejas/headroom:0.5.26`, and those images are built with the same synced package version used for the matching PyPI and npm release.
Published releases also push versioned GHCR tags such as `ghcr.io/headroomlabs-ai/headroom:0.35.0`, and those images are built with the same synced package version used for the matching PyPI and npm release.
## How the wrapper behaves
@@ -63,7 +63,7 @@ docker run --rm -it \
-p 8787:8787 \
-v "$PWD:/workspace" \
-w /workspace \
ghcr.io/chopratejas/headroom:latest \
ghcr.io/headroomlabs-ai/headroom:latest \
headroom proxy --host 0.0.0.0 --port 8787
```