|
| 1 | +# syntax=docker/dockerfile:1@sha256:ecfaec9ed6d810b56388c508f4121597bfbba70d41a6dfeee4d8cad5f295fc32 |
| 2 | +# The git commits uv.lock pins (git_source_downloads in the recipe), fetched by |
| 3 | +# the caller on its own machine, with its own credentials, before any image |
| 4 | +# build, and supplied here as the build context of this name: one bare mirror |
| 5 | +# per repository. Without a caller supplying it, the stage is empty and uv |
| 6 | +# fetches them from the network as it always did. |
| 7 | +FROM scratch AS uvsources |
| 8 | + |
| 9 | +# The builder image — installs the venv via uv, then we copy it into the runtime. |
| 10 | +FROM BUILDER_IMAGE as builder |
| 11 | + |
| 12 | +# uv is part of what the recipe has to state. A base image carries whichever uv |
| 13 | +# it happened to be built with, so a project written against a newer `[tool.uv]` |
| 14 | +# key resolves on the developer's machine and fails inside the builder stage |
| 15 | +# with a TOML parse error naming a field that uv there does not know. Copying a |
| 16 | +# pinned uv in makes the tool that reads pyproject.toml and uv.lock a declared |
| 17 | +# input of the recipe instead of a property of the base image. |
| 18 | +COPY --from=UV_IMAGE /uv /usr/local/bin/uv |
| 19 | + |
| 20 | +WORKDIR /app |
| 21 | + |
| 22 | +COPY code/pyproject.toml code/uv.lock ./ |
| 23 | + |
| 24 | +# Install deps to a project-local .venv. --frozen installs the locked resolution |
| 25 | +# as it stands instead of re-resolving it at build time. --no-editable installs |
| 26 | +# workspace members and path sources as packages in the venv rather than links |
| 27 | +# to builder-stage sources, which the runtime stage does not carry. |
| 28 | +# |
| 29 | +# A project whose requires-python the base image's Python does not satisfy gets |
| 30 | +# the interpreter uv installs for it, as on the host. It is installed where the |
| 31 | +# runtime stage copies it from, so the venv's interpreter exists there too. |
| 32 | +ENV UV_PROJECT_ENVIRONMENT=/app/.venv \ |
| 33 | + UV_LINK_MODE=copy \ |
| 34 | + UV_PYTHON_INSTALL_DIR=/opt/uv/python |
| 35 | +# |
| 36 | +# With the caller's mirrors, git reads each locked repository from its mirror |
| 37 | +# instead of the URL the lock records (uv hands git that URL unchanged), so no |
| 38 | +# credential is needed, and a commit missing from the mirror fails here rather |
| 39 | +# than prompting for one. The mappings are appended after the git configuration |
| 40 | +# the environment already carries, when its GIT_CONFIG_COUNT is a plain count; |
| 41 | +# otherwise the configuration is left alone and the sources are fetched as |
| 42 | +# without a mirror. An image whose own git configuration (system, global or |
| 43 | +# environment) already rewrites a declared repository's URL keeps its rewrite: |
| 44 | +# git prefers it, and the build fetches that source as it does without a mirror. |
| 45 | +RUN --mount=type=bind,from=uvsources,target=/uvsources \ |
| 46 | + if [ -n "$(ls -A /uvsources)" ]; then \ |
| 47 | + n=${GIT_CONFIG_COUNT:-0}; n=${n#+}; \ |
| 48 | + case $n in ''|*[!0-9]*) n= ;; *) while [ "${n#0}" != "$n" ] && [ -n "${n#0}" ]; do n=${n#0}; done; [ ${#n} -le 4 ] || n= ;; esac; \ |
| 49 | + if [ -z "$n" ]; then echo "inherited GIT_CONFIG_COUNT is not a plain count; git sources are fetched without the mirror"; \ |
| 50 | + else export GIT_TERMINAL_PROMPT=0 \ |
| 51 | + "GIT_CONFIG_KEY_$((n+0))=url.file:///uvsources/c9fa8363b851c2643d648edad1c8c7892904f717385511fe4771422d0817a55e.git.insteadOf" "GIT_CONFIG_VALUE_$((n+0))="'https://git.example.invalid/acme/mono' \ |
| 52 | + "GIT_CONFIG_KEY_$((n+1))=url.file:///uvsources/d2b987abe743521016c33e82dba28324380629b9d2544f9e5213ee4299a3af6c.git.insteadOf" "GIT_CONFIG_VALUE_$((n+1))="'ssh://git@git.example.invalid:2222/acme/tools.git' \ |
| 53 | + GIT_CONFIG_COUNT=$((n+2)); fi; \ |
| 54 | + fi && \ |
| 55 | + mkdir -p /opt/uv/python && uv sync --frozen --no-dev --no-install-project --no-editable |
| 56 | + |
| 57 | + |
| 58 | +# Runtime |
| 59 | +FROM BUILDER_IMAGE as runtime |
| 60 | + |
| 61 | +WORKDIR /app |
| 62 | + |
| 63 | +# UID pinned to match runAsUser in the kustomize Deployment: Kubernetes' |
| 64 | +# runAsNonRoot check rejects a container whose image user is non-numeric. |
| 65 | +RUN adduser -D -u 1000 appuser |
| 66 | +USER appuser |
| 67 | + |
| 68 | +ENV VIRTUAL_ENV=/app/.venv \ |
| 69 | + PATH="/app/.venv/bin:$PATH" |
| 70 | + |
| 71 | +COPY --from=builder /opt/uv/python /opt/uv/python |
| 72 | +COPY --from=builder ${VIRTUAL_ENV} ${VIRTUAL_ENV} |
| 73 | + |
| 74 | +COPY --chown=appuser service.codefly.yaml . |
| 75 | + |
| 76 | + |
| 77 | +COPY --chown=appuser code/src code/src |
| 78 | + |
| 79 | + |
| 80 | + |
| 81 | + |
| 82 | +WORKDIR /app/code |
| 83 | + |
| 84 | +# uvicorn's logging configuration and the filter it names (uvicorn_logging.go): |
| 85 | +# health probes are dropped from the access log, everything else is as uvicorn |
| 86 | +# writes it. The builder writes both into the project before the build. |
| 87 | +COPY --chown=appuser code/uvicorn-log-config.json code/codefly_uvicorn_logging.py ./ |
| 88 | + |
| 89 | +# Default REST port (overridden by codefly at runtime). |
| 90 | +CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8080", "--log-config", "uvicorn-log-config.json"] |
0 commit comments