08a529fa63
ci / lint (push) Successful in 24s
ci / types (push) Successful in 34s
ci / unit (push) Successful in 26s
ci / security (push) Successful in 37s
ci / dockerfile (push) Successful in 6s
ci / chart (push) Successful in 7s
ci / integration (push) Successful in 41s
ci / image (api) (push) Successful in 2m9s
ci / image (reconciler) (push) Successful in 2m1s
ci / image (worker) (push) Successful in 2m7s
ci / bump (push) Successful in 13s
Docker Hub rate-limits anonymous pulls per source IP and every node here shares one NAT address, so a busy afternoon fails an unrelated build with `toomanyrequests`. Nothing in this repo needs to be there. Every base image now comes from mirror.gcr.io (python, alpine/helm, postgres) or ghcr.io (uv, trivy). Verified digest-for-digest against Docker Hub before switching, including the superseded postgres digest this repo still pins, so every existing pin stays valid — same bytes, different transport. catalog.yaml: the three bitnami entries named `bitnamilegacy/<chart>`, a repo alias nothing in the worker image configures, so they could never resolve at provision time. All five entries are now `oci://` refs, which need no `helm repo add`, and all are on latest stable: elasticsearch 21.3.15 -> 22.1.6 redis 20.6.2 -> 27.0.15 postgresql 16.4.5 -> 18.8.0 podinfo 6.7.1 -> 6.14.0 Moving the chart pull is only half of it, though: a bitnami chart defaults its own images to registry-1.docker.io. CatalogEntry gains a `values:` dict, merged under the size's replicas and resources, so an entry can set `global.imageRegistry` and move the image pull too. Size wins on conflict — otherwise an entry setting replicaCount would make every size deploy the same shape. Deep merge, because a shallow one drops sibling keys of a shared nested map. Bitnami charts reject a substituted registry unless `global.security.allowInsecureImages` is set. That check is about provenance, and the mirror serves byte-identical manifests, so it is set deliberately and only for entries whose digests were verified. The dind prune had `--filter until=168h` on both prunes, and it got both cases exactly backwards. `until` reads an image's CREATED time, so it deleted trivy every leg (a released tool image is always older than any window) while protecting the dangling build layers it existed to remove. Measured on node0: 21 dangling images / 5.96GB, and exactly 1 of them older than 168h. Trivy is protected by a tag now, so the image prune drops the filter; buildx keeps it, where age genuinely matters. Tests: +10 unit (deep merge, precedence, no-mutation, and a guard that fails if any catalog entry points at Docker Hub). Both new guards were control-tested by breaking the code and watching them fail. The API test that hardcoded `21.3.15` now reads the catalog — its subject is where the value comes from, not what it is.
63 lines
3.1 KiB
Docker
63 lines
3.1 KiB
Docker
# syntax=docker/dockerfile:1.10
|
|
#
|
|
# svcforge api. Build from the REPO ROOT:
|
|
# docker buildx build -f services/api/Dockerfile -t svcforge/api:dev .
|
|
# `COPY ../..` is illegal, so the context must be the root. There is no other option.
|
|
#
|
|
# Two syncs, not one: deps change rarely and our own code changes every commit, so the
|
|
# expensive layer (third-party wheels) must land before the cheap one (our source).
|
|
|
|
FROM mirror.gcr.io/library/python:3.14-slim@sha256:cea0e6040540fb2b965b6e7fb5ffa00871e632eef63719f0ea54bca189ce14a6 AS builder
|
|
|
|
COPY --from=ghcr.io/astral-sh/uv:0.11.29@sha256:eb2843a1e56fd9e30c7276ce1a52cba86e64c7b385f5e3279a0e08e02dd058fc /uv /usr/local/bin/uv
|
|
|
|
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy UV_PYTHON_DOWNLOADS=never
|
|
WORKDIR /app
|
|
|
|
# --- layer 1: third-party dependencies only -------------------------------------------
|
|
# --no-install-project skips the root; --no-install-package skips our path dependency.
|
|
# Without the latter, uv would try to build svcforge-core here, where its source is not
|
|
# yet in the context, and the build would fail.
|
|
COPY pyproject.toml uv.lock ./
|
|
COPY libs/svcforge_core/pyproject.toml libs/svcforge_core/
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
uv sync --frozen --no-dev --no-editable \
|
|
--no-install-project --no-install-package svcforge-core
|
|
|
|
# --- layer 2: our code ----------------------------------------------------------------
|
|
COPY libs/ libs/
|
|
COPY services/api/ services/api/
|
|
COPY catalog.yaml ./
|
|
# The migrate Job runs from THIS image (migrate-job.yaml pins the api digest), so the SQL
|
|
# has to be in it. svcforge_core.migrate's fallback resolves MIGRATIONS_DIR relative to
|
|
# its own __file__, which lands under site-packages here — a directory that does not and
|
|
# should not contain SQL — so main() returned 1 and the pre-install/pre-upgrade hook
|
|
# failed every sync. SVCFORGE_MIGRATIONS_DIR below points it at this copy instead.
|
|
COPY migrations/ migrations/
|
|
# --no-editable is what turns svcforge-core into a real wheel in site-packages.
|
|
# pyproject.toml declares it `editable = true` for local dev; an editable install in an
|
|
# image points at /app/libs, which is a source tree that need not survive the final stage.
|
|
RUN --mount=type=cache,target=/root/.cache/uv \
|
|
uv sync --frozen --no-dev --no-editable && \
|
|
/app/.venv/bin/python -c 'import svcforge_core, sys; \
|
|
p = svcforge_core.__file__; \
|
|
sys.exit(0) if "site-packages" in p else sys.exit("not a wheel install: " + p)'
|
|
|
|
# --- runtime --------------------------------------------------------------------------
|
|
FROM mirror.gcr.io/library/python:3.14-slim@sha256:cea0e6040540fb2b965b6e7fb5ffa00871e632eef63719f0ea54bca189ce14a6
|
|
|
|
ARG BUILD_SHA=unknown
|
|
LABEL org.opencontainers.image.title="svcforge-api" \
|
|
org.opencontainers.image.source="https://gitea.oci-oci.duckdns.org/gitea_admin/svcforge" \
|
|
org.opencontainers.image.revision="${BUILD_SHA}"
|
|
|
|
RUN useradd -u 10001 -m -s /usr/sbin/nologin svcforge
|
|
WORKDIR /app
|
|
COPY --from=builder --chown=10001:10001 /app /app
|
|
ENV PATH="/app/.venv/bin:$PATH" \
|
|
PYTHONUNBUFFERED=1 \
|
|
PYTHONDONTWRITEBYTECODE=1 \
|
|
SVCFORGE_MIGRATIONS_DIR=/app/migrations
|
|
USER 10001
|
|
ENTRYPOINT ["python", "-m", "services.api"]
|