Changes to Docker image

This commit is contained in:
Diego Ripley
2026-09-26 10:11:13 -04:00
parent ddec949b03
commit 6dd4c9413f
7 changed files with 2876 additions and 45 deletions
+36 -14
View File
@@ -1,18 +1,26 @@
# syntax=docker/dockerfile:1
#
# Stage 2: Python environment (uv-managed venv with rioxarray/rasterio/xarray/
# dask/numpy) on top of the stage-1 GDAL + ECW + MrSID image.
# Stage 2: Python environment (uv-managed venv holding the repo's
# pyproject.toml + uv.lock dependency set — rioxarray, rasterio, xarray, dask,
# numpy, portolan-cli) on top of the stage-1 GDAL + ECW + MrSID image.
#
# To bump GDAL, change GDAL_VERSION (the base image tag) and rebuild. Nothing
# else here encodes the GDAL version: rasterio is compiled against whatever
# `gdal-config` the base image ships, and the build asserts that it matches.
# To change Python packages, edit ../../pyproject.toml (via `uv add`) and commit
# the regenerated ../../uv.lock; `uv sync --locked` below will pick them up.
#
# The build context is the REPO ROOT, not this directory, because pyproject.toml
# and uv.lock live there. .dockerignore keeps that context tiny — data/ and
# scripts/ hold hundreds of GB of imagery and are excluded.
#
# Build (from the repo root):
# docker/python/build.sh
# # or by hand:
# docker buildx build --platform linux/amd64 \
# --build-arg GDAL_VERSION=3.13.3 \
# -t dataforcanada/gdal-ecw-mrsid-python:3.13.3 docker/python
# --file docker/python/Dockerfile \
# -t dataforcanada/gdal-ecw-mrsid-python:3.13.3 .
ARG GDAL_VERSION=3.13.3
ARG GDAL_IMAGE=dataforcanada/gdal-ecw-mrsid
@@ -62,7 +70,7 @@ RUN test "$(gdal-config --version)" = "${GDAL_VERSION}" \
COPY --from=uv /uv /uvx /usr/local/bin/
# Image-wide default: never install wheels that bundle libgdal (see file).
COPY uv.toml /etc/uv/uv.toml
COPY docker/python/uv.toml /etc/uv/uv.toml
# ---- non-root runtime user ---------------------------------------------------
# Ubuntu 26.04 ships a stock "ubuntu" user at uid 1000; drop it so USER_UID can
@@ -77,18 +85,24 @@ RUN if id ubuntu >/dev/null 2>&1; then userdel -r ubuntu; fi \
&& chown "${USER_UID}:${USER_GID}" /opt/venv \
&& echo "source /usr/share/bash-completion/bash_completion" >> "/home/${USERNAME}/.bashrc"
COPY --chmod=0755 verify_gdal_drivers.py /usr/local/bin/verify-gdal-drivers
COPY --chmod=0755 docker/python/verify_gdal_drivers.py /usr/local/bin/verify-gdal-drivers
# VIRTUAL_ENV/PATH: the venv is the default python for everything downstream.
# UV_PROJECT_ENVIRONMENT: point uv's *project* interface at that same venv, so
# `uv sync`/`uv add`/`uv run` in the repo update /opt/venv instead of creating
# a ./.venv next to pyproject.toml — both in this build and, more importantly,
# in the dev container, where the workspace is bind-mounted.
# UV_PYTHON_DOWNLOADS=never: always use the system interpreter, never a
# uv-managed one.
# GDAL_CONFIG: what rasterio's setup.py consults; explicit rather than PATH luck.
# D4C_GDAL_VERSION: what verify-gdal-drivers checks against at runtime.
# (Not named GDAL_VERSION: rasterio's setup.py treats that as an override.)
# (No UV_NO_BINARY_PACKAGE here on purpose: it does not apply to
# `uv pip install`; /etc/uv/uv.toml covers both interfaces.)
# `uv pip install`. /etc/uv/uv.toml covers both interfaces image-wide, and
# pyproject.toml's [tool.uv] covers the project interface for this repo.)
ENV VIRTUAL_ENV=/opt/venv \
PATH=/opt/venv/bin:${PATH} \
UV_PROJECT_ENVIRONMENT=/opt/venv \
UV_PYTHON_DOWNLOADS=never \
UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
@@ -99,21 +113,29 @@ USER ${USERNAME}
WORKDIR /home/${USERNAME}
# ---- venv + packages ---------------------------------------------------------
# --no-binary rasterio is the critical bit: rasterio's PyPI wheels bundle their
# own libgdal (built without ECW/MrSID). Building the sdist makes it link
# against this image's libgdal via gdal-config. (`uv pip install` takes the
# pip-style `--no-binary <pkg>`; `--no-binary-package` is the `uv sync`/`uv add`
# spelling. /etc/uv/uv.toml already says the same; the flag is belt-and-braces.)
COPY --chown=${USER_UID}:${USER_GID} requirements.txt /tmp/requirements.txt
# The repo's pyproject.toml + uv.lock are the single source of truth for this
# environment; `--locked` fails the build if they have drifted apart, so the
# image can never be built from an unlocked (i.e. unreproducible) dependency
# set. The lock pins the whole transitive tree, not just the direct deps.
#
# --no-binary-package is the critical bit: the PyPI wheels for rasterio and
# pyogrio (the latter pulled in by portolan-cli) bundle their own libgdal, built
# without ECW/MrSID. Building the sdists makes them link against this image's
# libgdal via gdal-config. Both pyproject.toml's [tool.uv] and /etc/uv/uv.toml
# already say this; the flags are belt-and-braces. (`uv sync`/`uv add` take
# `--no-binary-package <pkg>`; `uv pip install` takes pip-style `--no-binary`.)
COPY --chown=${USER_UID}:${USER_GID} pyproject.toml uv.lock /tmp/project/
RUN --mount=type=cache,target=/home/${USERNAME}/.cache/uv,uid=${USER_UID},gid=${USER_GID} \
uv venv --python /usr/bin/python3 "${VIRTUAL_ENV}" \
&& uv pip install --no-binary rasterio --requirements /tmp/requirements.txt \
&& rm /tmp/requirements.txt
&& uv sync --project /tmp/project --locked --no-dev \
--no-binary-package rasterio --no-binary-package pyogrio \
&& rm -rf /tmp/project
# ---- verification: the build fails unless all of these hold ------------------
# gdalinfo --formats lists ECW and MrSID
# rasterio.__gdal_version__ == GDAL_VERSION
# rasterio.Env().drivers() includes ECW and MrSID
# pyogrio.__gdal_version_string__ == GDAL_VERSION
# (+ exactly one libgdal, the system one, is loaded; rioxarray round-trips)
RUN verify-gdal-drivers --expect-gdal "${GDAL_VERSION}"