0ef5fcb1c5
CI / test-dashboard-ui (push) Blocked by required conditions
CI / test (1) (push) Blocked by required conditions
CI / test (2) (push) Blocked by required conditions
CI / test (3) (push) Blocked by required conditions
CI / test (4) (push) Blocked by required conditions
CI / test-extras (push) Blocked by required conditions
CI / test-agno (push) Blocked by required conditions
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Waiting to run
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Blocked by required conditions
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Blocked by required conditions
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Waiting to run
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Waiting to run
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Waiting to run
Docker / promote-latest (push) Blocked by required conditions
Init Native E2E / init-native (macos-latest, claude) (push) Waiting to run
Init Native E2E / init-native (macos-latest, codex) (push) Waiting to run
Init Native E2E / init-native (macos-latest, copilot) (push) Waiting to run
Install Native E2E / install-native (macos-latest) (push) Waiting to run
Wrap Native E2E / wrap-native (macos-latest) (push) Waiting to run
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
CI / lint (push) Waiting to run
CI / build-wheel (push) Waiting to run
CI / build-wheel-windows (push) Waiting to run
CI / prefetch-model (push) Waiting to run
CI / build (push) Waiting to run
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
CI / workflow-validation (push) Waiting to run
CI / docker-native-e2e (push) Waiting to run
CI / windows-native-wrapper (push) Waiting to run
CI / macos-native-wrapper (push) Waiting to run
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
230 lines
8.4 KiB
Docker
230 lines
8.4 KiB
Docker
ARG PYTHON_VERSION=3.13
|
|
ARG UV_VERSION=0.11.18
|
|
ARG DISTROLESS_IMAGE=gcr.io/distroless/python3-debian13
|
|
ARG PYTHON_SITE_PACKAGES=/usr/local/lib/python${PYTHON_VERSION}/site-packages
|
|
|
|
# ---- Build stage: compile native extensions, build wheel ----
|
|
FROM python:${PYTHON_VERSION}-slim AS builder
|
|
|
|
ARG UV_VERSION
|
|
ARG PYTHON_SITE_PACKAGES
|
|
ARG HEADROOM_BUILD_VERSION=""
|
|
|
|
# build-essential / g++ for any C extension wheels uv may need to build
|
|
# from source. curl + ca-certificates are required by the rustup
|
|
# bootstrap below. patchelf for maturin's wheel-link repair on linux.
|
|
# No OpenSSL system deps required: the rustls-everywhere refactor
|
|
# eliminated `openssl-sys` from our build tree by switching fastembed
|
|
# to `hf-hub-rustls-tls` + `ort-download-binaries-rustls-tls`.
|
|
RUN apt-get update && \
|
|
apt-get install -y --no-install-recommends \
|
|
build-essential \
|
|
g++ \
|
|
curl \
|
|
ca-certificates \
|
|
patchelf \
|
|
&& rm -rf /var/lib/apt/lists/*
|
|
|
|
RUN python -m pip install --no-cache-dir uv==${UV_VERSION}
|
|
|
|
# Rust toolchain for the headroom._core extension. With single-wheel
|
|
# architecture (post-#355), `pip install -e .` invokes maturin via
|
|
# pyproject.toml's [build-system], which calls cargo. No more separate
|
|
# headroom-core-py package.
|
|
ENV CARGO_HOME=/usr/local/cargo \
|
|
RUSTUP_HOME=/usr/local/rustup \
|
|
PATH=/usr/local/cargo/bin:${PATH}
|
|
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
|
|
| sh -s -- -y --no-modify-path --profile minimal -c rustfmt -c clippy --default-toolchain 1.95.0
|
|
|
|
WORKDIR /build
|
|
|
|
# Copy the full set of files maturin needs to build the wheel: the root
|
|
# pyproject.toml + Cargo workspace + Rust crates + Python source. The
|
|
# uv install builds + installs the wheel in one shot.
|
|
COPY pyproject.toml uv.lock README.md ./
|
|
COPY Cargo.toml Cargo.lock rust-toolchain.toml ./
|
|
COPY crates/ crates/
|
|
COPY headroom/ headroom/
|
|
|
|
ARG HEADROOM_EXTRAS=proxy,code
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
--mount=type=cache,target=/root/.cargo/registry \
|
|
--mount=type=cache,target=/build/target \
|
|
uv pip install --system ".[${HEADROOM_EXTRAS}]"
|
|
|
|
RUN --mount=type=bind,source=.,target=/context,readonly \
|
|
HEADROOM_BUILD_VERSION="${HEADROOM_BUILD_VERSION}" PYTHON_SITE_PACKAGES="${PYTHON_SITE_PACKAGES}" python - <<'PY'
|
|
import hashlib
|
|
import os
|
|
from pathlib import Path
|
|
|
|
|
|
def git_revision(context: Path) -> str | None:
|
|
git_dir = context / ".git"
|
|
head_path = git_dir / "HEAD"
|
|
if not head_path.exists():
|
|
return None
|
|
head = head_path.read_text(encoding="utf-8").strip()
|
|
if head.startswith("ref: "):
|
|
ref_name = head.removeprefix("ref: ").strip()
|
|
ref_path = git_dir / ref_name
|
|
if ref_path.exists():
|
|
head = ref_path.read_text(encoding="utf-8").strip()
|
|
else:
|
|
packed_refs = git_dir / "packed-refs"
|
|
if not packed_refs.exists():
|
|
return None
|
|
for line in packed_refs.read_text(encoding="utf-8").splitlines():
|
|
if line.startswith("#") or not line.strip():
|
|
continue
|
|
sha, _, name = line.partition(" ")
|
|
if name.strip() == ref_name:
|
|
head = sha
|
|
break
|
|
else:
|
|
return None
|
|
return head[:12] if len(head) >= 7 and all(c in "0123456789abcdef" for c in head.lower()) else None
|
|
|
|
|
|
def source_digest(root: Path) -> str:
|
|
digest = hashlib.sha256()
|
|
inputs = (
|
|
"pyproject.toml",
|
|
"uv.lock",
|
|
"README.md",
|
|
"Cargo.toml",
|
|
"Cargo.lock",
|
|
"rust-toolchain.toml",
|
|
"crates",
|
|
"headroom",
|
|
)
|
|
for name in inputs:
|
|
path = root / name
|
|
if not path.exists():
|
|
continue
|
|
files = [path] if path.is_file() else sorted(p for p in path.rglob("*") if p.is_file())
|
|
for file in files:
|
|
digest.update(file.relative_to(root).as_posix().encode("utf-8"))
|
|
digest.update(b"\0")
|
|
digest.update(file.read_bytes())
|
|
digest.update(b"\0")
|
|
return digest.hexdigest()[:12]
|
|
|
|
|
|
build_version = os.environ["HEADROOM_BUILD_VERSION"].strip()
|
|
if not build_version:
|
|
print("no Headroom build version override provided; using installed package metadata")
|
|
raise SystemExit(0)
|
|
if build_version == "source-build":
|
|
revision = git_revision(Path("/context"))
|
|
build_version = (
|
|
f"source-build+g{revision}"
|
|
if revision
|
|
else f"source-build+sha256.{source_digest(Path('/build'))}"
|
|
)
|
|
|
|
package_dir = Path(os.environ["PYTHON_SITE_PACKAGES"]) / "headroom"
|
|
(package_dir / "_build_info.py").write_text(
|
|
"BUILD_VERSION = " + repr(build_version) + "\n",
|
|
encoding="utf-8",
|
|
)
|
|
print("baked Headroom build version: " + build_version)
|
|
PY
|
|
|
|
# Build-stage smoke check: verify the extension loads end-to-end inside
|
|
# the build image before we copy site-packages into the runtime image.
|
|
# If this fails, the runtime image would fail Phase A0's fail-loud
|
|
# startup check on every restart. Run from /tmp so cwd doesn't shadow
|
|
# site-packages with /build/headroom/ (which has no _core.so since
|
|
# maturin installed the .so into site-packages).
|
|
RUN cd /tmp && python -c "from headroom._core import DiffCompressor, SmartCrusher; \
|
|
print(f'build-stage rust core verify OK: {DiffCompressor.__name__}, {SmartCrusher.__name__}')"
|
|
|
|
# Build the native Rust reverse proxy binary and stage it for the runtime
|
|
# images (issue #976). These images already run "the proxy"; bundling the
|
|
# native `headroom-proxy` binary lets operators front the Python proxy with
|
|
# the Rust SigV4 / live-zone compression path from the same image. The
|
|
# binary is copied out of the cache-mounted target dir into a persistent
|
|
# path so the COPY in the runtime stages can pick it up.
|
|
RUN --mount=type=cache,target=/usr/local/cargo/registry \
|
|
--mount=type=cache,target=/build/target \
|
|
cargo build --release --locked --bin headroom-proxy && \
|
|
cp target/release/headroom-proxy /usr/local/bin/headroom-proxy
|
|
|
|
# ---- Runtime stage (python-slim): supports root/nonroot via build arg ----
|
|
FROM python:${PYTHON_VERSION}-slim AS runtime-slim-base
|
|
|
|
ARG RUNTIME_USER=nonroot
|
|
ARG RUNTIME_HOME=/home/nonroot
|
|
ARG PYTHON_SITE_PACKAGES
|
|
|
|
RUN apt-get update && \
|
|
apt-get install -y --no-install-recommends curl && \
|
|
rm -rf /var/lib/apt/lists/*
|
|
|
|
COPY --from=builder ${PYTHON_SITE_PACKAGES} ${PYTHON_SITE_PACKAGES}
|
|
COPY --from=builder /usr/local/bin/headroom /usr/local/bin/headroom
|
|
# Native Rust reverse proxy binary (issue #976).
|
|
COPY --from=builder /usr/local/bin/headroom-proxy /usr/local/bin/headroom-proxy
|
|
|
|
RUN mkdir -p /home/nonroot /data && \
|
|
if [ "$RUNTIME_USER" = "nonroot" ]; then \
|
|
groupadd --gid 1000 nonroot && \
|
|
useradd --uid 1000 --gid nonroot --create-home nonroot && \
|
|
mkdir -p /home/nonroot/.headroom && \
|
|
chown -R nonroot:nonroot /data /home/nonroot; \
|
|
else \
|
|
mkdir -p /root/.headroom; \
|
|
fi
|
|
|
|
USER ${RUNTIME_USER}
|
|
WORKDIR ${RUNTIME_HOME}
|
|
|
|
ENV HEADROOM_HOST=0.0.0.0 \
|
|
PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1
|
|
|
|
# Declare ~/.headroom as a volume so Docker (and ACA) can attach persistent
|
|
# storage here. Bare `docker run` gets an anonymous volume as a fallback so
|
|
# state is never silently written to the ephemeral container layer.
|
|
# RUNTIME_HOME defaults to /home/nonroot (the published image default); pass
|
|
# --build-arg RUNTIME_HOME=/root when building with RUNTIME_USER=root.
|
|
VOLUME ${RUNTIME_HOME}/.headroom
|
|
|
|
EXPOSE 8787
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD ["curl", "--fail", "--silent", "http://127.0.0.1:8787/readyz"]
|
|
|
|
ENTRYPOINT ["headroom", "proxy"]
|
|
CMD ["--host", "0.0.0.0", "--port", "8787"]
|
|
|
|
FROM ${DISTROLESS_IMAGE} AS runtime-slim
|
|
|
|
ARG RUNTIME_USER=nonroot
|
|
ARG PYTHON_SITE_PACKAGES
|
|
|
|
COPY --from=builder ${PYTHON_SITE_PACKAGES} ${PYTHON_SITE_PACKAGES}
|
|
# Native Rust reverse proxy binary (issue #976).
|
|
COPY --from=builder /usr/local/bin/headroom-proxy /usr/local/bin/headroom-proxy
|
|
|
|
USER ${RUNTIME_USER}
|
|
WORKDIR /app
|
|
|
|
ENV HEADROOM_HOST=0.0.0.0 \
|
|
PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
PYTHONPATH=${PYTHON_SITE_PACKAGES}
|
|
|
|
EXPOSE 8787
|
|
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
|
CMD ["python3", "-c", "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8787/readyz', timeout=5)"]
|
|
|
|
ENTRYPOINT ["python3", "-m", "headroom.cli", "proxy"]
|
|
CMD ["--host", "0.0.0.0", "--port", "8787"]
|
|
|
|
# Default published image remains python-slim runtime
|
|
FROM runtime-slim-base AS runtime
|