# 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 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 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"]