alessandro trinca tornidor commited on
Commit ·
f17138a
1
Parent(s): d81ec6c
build: migrate Docker images to Docker Hardened Images (DHI)
Browse files- Builder: dhi.io/python:3.13-dev (has apt, pip, shell)
- Runtime: dhi.io/python:3.13 (no shell, no pkg manager, nonroot user)
- Remove libGL (not needed: PIL manylinux bundles its own)
- Remove system GDAL (not needed: rasterio manylinux wheels bundle it)
- Keep libexpat (needed by rasterio's bundled GDAL at runtime)
- Use nonroot user (65532) instead of custom python (999)
- HEALTHCHECK exec form (no shell in runtime)
- Clean up debug RUN layers in Dockerfile (57→23 lines)
- Add .cursor to .dockerignore
- Update CLAUDE.md Docker structure docs
Base image: 203MB 25CVEs → 22MB 3CVEs (2L + 1 unspecified)
- .dockerignore +1 -0
- CLAUDE.md +6 -2
- Dockerfile +14 -47
- dockerfiles/dockerfile-samgis-base +32 -61
.dockerignore
CHANGED
|
@@ -2,6 +2,7 @@ venv/
|
|
| 2 |
*.pyc
|
| 3 |
__cache__
|
| 4 |
.idea
|
|
|
|
| 5 |
tmp/
|
| 6 |
.env*
|
| 7 |
__pycache__
|
|
|
|
| 2 |
*.pyc
|
| 3 |
__cache__
|
| 4 |
.idea
|
| 5 |
+
.cursor
|
| 6 |
tmp/
|
| 7 |
.env*
|
| 8 |
__pycache__
|
CLAUDE.md
CHANGED
|
@@ -128,8 +128,12 @@ curl -d@./events/payload_point_colico.json -H 'content-type: application/json' $
|
|
| 128 |
### Docker Structure
|
| 129 |
|
| 130 |
- `Dockerfile`: Main application image (uses pre-built base)
|
| 131 |
-
- `dockerfiles/dockerfile-samgis-base`: Multi-stage base image
|
| 132 |
-
- `
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
### Model Files
|
| 135 |
|
|
|
|
| 128 |
### Docker Structure
|
| 129 |
|
| 130 |
- `Dockerfile`: Main application image (uses pre-built base)
|
| 131 |
+
- `dockerfiles/dockerfile-samgis-base`: Multi-stage base image using Docker Hardened Images (`dhi.io/python:3.13`)
|
| 132 |
+
- Builder: `dhi.io/python:3.13-dev` (has `apt`, `pip`, shell)
|
| 133 |
+
- Runtime: `dhi.io/python:3.13` (no shell, no pkg manager, `nonroot` user 65532)
|
| 134 |
+
- `libexpat` copied from builder (required by rasterio's bundled GDAL)
|
| 135 |
+
- No libGL or system GDAL needed (manylinux wheels bundle everything)
|
| 136 |
+
- `dockerfiles/dockerfile-samgis-base-alpinelinux3.23`: Alpine Linux variant (legacy)
|
| 137 |
|
| 138 |
### Model Files
|
| 139 |
|
Dockerfile
CHANGED
|
@@ -1,56 +1,23 @@
|
|
| 1 |
FROM registry.gitlab.com/aletrn/gis-prediction:1.12.3
|
| 2 |
|
| 3 |
-
# Include global arg in this stage of the build
|
| 4 |
ARG WORKDIR_ROOT="/var/task"
|
| 5 |
-
ENV VIRTUAL_ENV=${WORKDIR_ROOT}/.venv
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
|
| 11 |
-
# Set working directory to function root directory
|
| 12 |
WORKDIR ${WORKDIR_ROOT}
|
| 13 |
-
# workaround for missing /home folder
|
| 14 |
-
RUN ls -ld ${HOME_USER}
|
| 15 |
-
RUN ls -lA ${HOME_USER}
|
| 16 |
|
| 17 |
-
COPY --chown=
|
| 18 |
-
COPY --chown=
|
| 19 |
-
RUN chmod 755 ${WORKDIR_ROOT}/client_health.py
|
| 20 |
|
| 21 |
-
#
|
| 22 |
-
|
| 23 |
-
RUN
|
| 24 |
-
RUN if [ "${WRITE_TMP_ON_DISK}" != "" ]; then ls -l {WRITE_TMP_ON_DISK}; fi
|
| 25 |
|
| 26 |
-
|
| 27 |
-
RUN /usr/bin/which python
|
| 28 |
-
RUN python --version
|
| 29 |
-
RUN pip list
|
| 30 |
-
RUN echo "PATH: ${PATH}."
|
| 31 |
-
RUN echo "WORKDIR_ROOT: ${WORKDIR_ROOT}."
|
| 32 |
-
RUN ls -l ${WORKDIR_ROOT}
|
| 33 |
-
RUN ls -ld ${WORKDIR_ROOT}
|
| 34 |
-
RUN ls -l ${WORKDIR_ROOT}/
|
| 35 |
-
RUN ls -l ${WORKDIR_ROOT}/sam-quantized/
|
| 36 |
-
RUN ls -l ${WORKDIR_ROOT}/sam-quantized/machine_learning_models
|
| 37 |
-
RUN ls -ld ${WORKDIR_ROOT}/sam-quantized/machine_learning_models
|
| 38 |
-
RUN python -c "import sys; print(sys.path)"
|
| 39 |
-
RUN python -c "import fastapi"
|
| 40 |
-
RUN python -c "import geopandas"
|
| 41 |
-
RUN python -c "import onnxruntime"
|
| 42 |
-
RUN python -c "import rasterio"
|
| 43 |
-
RUN python -c "import uvicorn"
|
| 44 |
-
RUN python -c "import jinja2"
|
| 45 |
-
RUN df -h
|
| 46 |
-
RUN ls -l ${WORKDIR_ROOT}/app.py
|
| 47 |
-
RUN ls -l ${WORKDIR_ROOT}/client_health.py
|
| 48 |
-
RUN ls -l ${WORKDIR_ROOT}/static/
|
| 49 |
-
RUN ls -l ${WORKDIR_ROOT}/static/dist
|
| 50 |
-
RUN ls -l ${WORKDIR_ROOT}/static/node_modules
|
| 51 |
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
CMD ["
|
| 55 |
-
# client_health.py default server_url value is http://localhost:7860/health
|
| 56 |
-
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 CMD /var/task/client_health.py || exit 1
|
|
|
|
| 1 |
FROM registry.gitlab.com/aletrn/gis-prediction:1.12.3
|
| 2 |
|
|
|
|
| 3 |
ARG WORKDIR_ROOT="/var/task"
|
| 4 |
+
ENV VIRTUAL_ENV=${WORKDIR_ROOT}/.venv \
|
| 5 |
+
PATH="${WORKDIR_ROOT}/.venv/bin:/opt/python/bin:$PATH" \
|
| 6 |
+
WRITE_TMP_ON_DISK="" \
|
| 7 |
+
VITE__STATIC_INDEX_URL="/static" \
|
| 8 |
+
VITE__INDEX_URL="/"
|
| 9 |
|
|
|
|
| 10 |
WORKDIR ${WORKDIR_ROOT}
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
+
COPY --chown=65532:65532 app.py ${WORKDIR_ROOT}/
|
| 13 |
+
COPY --chown=65532:65532 pyproject.toml poetry.lock README.md ${WORKDIR_ROOT}/
|
|
|
|
| 14 |
|
| 15 |
+
# Smoke tests: verify imports and model files
|
| 16 |
+
RUN ["python3", "-c", "import fastapi; import onnxruntime; import rasterio; import uvicorn; import jinja2; import geopandas"]
|
| 17 |
+
RUN ["python3", "-c", "from pathlib import Path; assert Path('/var/task/sam-quantized/machine_learning_models').is_dir()"]
|
|
|
|
| 18 |
|
| 19 |
+
USER 65532
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
+
CMD ["python3", "-m", "uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
|
| 22 |
+
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
|
| 23 |
+
CMD ["/opt/python/bin/python3", "/var/task/client_health.py"]
|
|
|
|
|
|
dockerfiles/dockerfile-samgis-base
CHANGED
|
@@ -1,84 +1,62 @@
|
|
| 1 |
# Include global ARGs at the dockerfile top
|
| 2 |
-
ARG ARCH="x86_64"
|
| 3 |
ARG WORKDIR_ROOT="/var/task"
|
| 4 |
ARG FASTAPI_STATIC="${WORKDIR_ROOT}/static"
|
| 5 |
-
ARG PYTHONPATH="${WORKDIR_ROOT}:${PYTHONPATH}:/usr/local/lib/python3/dist-packages"
|
| 6 |
ARG POETRY_NO_INTERACTION=1
|
| 7 |
ARG POETRY_VIRTUALENVS_IN_PROJECT=1
|
| 8 |
ARG POETRY_VIRTUALENVS_CREATE=1
|
| 9 |
ARG POETRY_CACHE_DIR=/tmp/poetry_cache
|
| 10 |
-
ARG HOME_USER=/home/python
|
| 11 |
|
| 12 |
|
| 13 |
-
FROM python:3.13-
|
| 14 |
|
| 15 |
-
ARG ARCH
|
| 16 |
ARG WORKDIR_ROOT
|
| 17 |
-
ARG PYTHONPATH
|
| 18 |
ARG POETRY_NO_INTERACTION
|
| 19 |
ARG POETRY_VIRTUALENVS_IN_PROJECT
|
| 20 |
ARG POETRY_VIRTUALENVS_CREATE
|
| 21 |
ARG POETRY_CACHE_DIR
|
| 22 |
|
| 23 |
-
RUN echo "ARCH: $ARCH ..."
|
| 24 |
-
|
| 25 |
-
RUN echo "ARG POETRY_CACHE_DIR: ${POETRY_CACHE_DIR} ..."
|
| 26 |
-
RUN echo "ARG PYTHONPATH: $PYTHONPATH ..."
|
| 27 |
-
RUN echo "arg dep:"
|
| 28 |
-
|
| 29 |
-
RUN groupadd -g 999 python && useradd -r -u 999 -g python python
|
| 30 |
# Set working directory to function root directory
|
| 31 |
-
RUN mkdir
|
| 32 |
WORKDIR ${WORKDIR_ROOT}
|
| 33 |
-
COPY
|
| 34 |
|
| 35 |
-
#
|
| 36 |
-
RUN
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
-
# poetry installation path is NOT within ${WORKDIR_ROOT}: not needed for runtime docker image
|
| 39 |
-
RUN python -m pip install --upgrade pip \
|
| 40 |
-
&& python -m pip install -r ${WORKDIR_ROOT}/requirements_poetry.txt && python -m pip list
|
| 41 |
|
| 42 |
-
|
| 43 |
-
RUN poetry config virtualenvs.path ${WORKDIR_ROOT}
|
| 44 |
-
RUN echo "# poetry config --list #" && poetry config --list
|
| 45 |
-
RUN poetry install --no-cache
|
| 46 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 47 |
|
| 48 |
-
FROM python:3.13-slim-trixie AS runtime
|
| 49 |
|
| 50 |
-
|
| 51 |
-
ARG WORKDIR_ROOT
|
| 52 |
-
ARG HOME_USER
|
| 53 |
|
| 54 |
-
|
| 55 |
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
RUN groupadd -g 999 python && useradd -r -u 999 -g python python
|
| 59 |
-
RUN ls -l /home
|
| 60 |
-
RUN ls -ld /home
|
| 61 |
-
RUN mkdir -p ${HOME_USER} ${HOME_USER}/.config && chown python:python -R ${HOME_USER}
|
| 62 |
-
RUN ls -ld ${HOME_USER}
|
| 63 |
-
RUN ls -lA ${HOME_USER}
|
| 64 |
-
RUN mkdir ${WORKDIR_ROOT} && chown python:python ${WORKDIR_ROOT}
|
| 65 |
|
| 66 |
-
|
| 67 |
-
COPY --from=
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
RUN . ${WORKDIR_ROOT}/.venv && which python && pip list
|
| 72 |
|
| 73 |
-
|
|
|
|
|
|
|
| 74 |
|
| 75 |
-
|
| 76 |
-
RUN
|
| 77 |
-
RUN ls -lA ${WORKDIR_ROOT}/
|
| 78 |
|
| 79 |
-
RUN python -c "import rasterio"
|
| 80 |
|
| 81 |
-
###
|
| 82 |
FROM node:24-alpine AS node_fastapi
|
| 83 |
|
| 84 |
ENV PNPM_HOME="/pnpm"
|
|
@@ -92,7 +70,6 @@ WORKDIR /appnode
|
|
| 92 |
FROM node_fastapi AS node_prod_deps
|
| 93 |
|
| 94 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm install --prod --frozen-lockfile
|
| 95 |
-
# here multiple conditions concatenated to avoid failing on check
|
| 96 |
RUN if [ ! -d /appnode/node_modules ]; then echo "no node_modules folder" && exit 1; fi
|
| 97 |
|
| 98 |
|
|
@@ -101,9 +78,6 @@ FROM node_fastapi AS node_build
|
|
| 101 |
ARG VITE__MAP_DESCRIPTION
|
| 102 |
ARG VITE__SAMGIS_SPACE
|
| 103 |
ENV VITE__INDEX_URL="/"
|
| 104 |
-
RUN echo "VITE__MAP_DESCRIPTION:" ${VITE__MAP_DESCRIPTION}
|
| 105 |
-
RUN echo "VITE__SAMGIS_SPACE:" ${VITE__SAMGIS_SPACE}
|
| 106 |
-
RUN echo "VITE__INDEX_URL:" ${VITE__INDEX_URL}
|
| 107 |
|
| 108 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm install --frozen-lockfile
|
| 109 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm build
|
|
@@ -116,10 +90,7 @@ FROM runtime
|
|
| 116 |
ARG FASTAPI_STATIC
|
| 117 |
ARG WORKDIR_ROOT
|
| 118 |
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
COPY --chown=
|
| 122 |
-
COPY --chown=
|
| 123 |
-
COPY --chown=python:python --from=node_build /appnode/dist* ${FASTAPI_STATIC}/dist
|
| 124 |
-
COPY --chown=python:python static/list_files.html ${FASTAPI_STATIC}/
|
| 125 |
-
RUN ls -l ${FASTAPI_STATIC}/
|
|
|
|
| 1 |
# Include global ARGs at the dockerfile top
|
|
|
|
| 2 |
ARG WORKDIR_ROOT="/var/task"
|
| 3 |
ARG FASTAPI_STATIC="${WORKDIR_ROOT}/static"
|
|
|
|
| 4 |
ARG POETRY_NO_INTERACTION=1
|
| 5 |
ARG POETRY_VIRTUALENVS_IN_PROJECT=1
|
| 6 |
ARG POETRY_VIRTUALENVS_CREATE=1
|
| 7 |
ARG POETRY_CACHE_DIR=/tmp/poetry_cache
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
+
FROM dhi.io/python:3.13-dev AS builder_global
|
| 11 |
|
|
|
|
| 12 |
ARG WORKDIR_ROOT
|
|
|
|
| 13 |
ARG POETRY_NO_INTERACTION
|
| 14 |
ARG POETRY_VIRTUALENVS_IN_PROJECT
|
| 15 |
ARG POETRY_VIRTUALENVS_CREATE
|
| 16 |
ARG POETRY_CACHE_DIR
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
# Set working directory to function root directory
|
| 19 |
+
RUN mkdir -p ${WORKDIR_ROOT}
|
| 20 |
WORKDIR ${WORKDIR_ROOT}
|
| 21 |
+
COPY requirements_poetry.txt pyproject.toml poetry.lock README.md ${WORKDIR_ROOT}/
|
| 22 |
|
| 23 |
+
# Install poetry and project dependencies into a venv
|
| 24 |
+
RUN pip install --upgrade pip \
|
| 25 |
+
&& pip install -r ${WORKDIR_ROOT}/requirements_poetry.txt
|
| 26 |
+
RUN poetry config virtualenvs.path ${WORKDIR_ROOT} \
|
| 27 |
+
&& poetry install --no-cache
|
| 28 |
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
+
FROM dhi.io/python:3.13-dev AS dir_creator
|
|
|
|
|
|
|
|
|
|
| 31 |
|
| 32 |
+
ARG WORKDIR_ROOT
|
| 33 |
+
# Pre-create workdir and copy libexpat (needed by rasterio's bundled GDAL at runtime)
|
| 34 |
+
RUN mkdir -p ${WORKDIR_ROOT} && chown 65532:65532 ${WORKDIR_ROOT}
|
| 35 |
+
RUN apt update && apt install -y --no-install-recommends libexpat1 && apt clean && rm -rf /var/lib/apt/lists/*
|
| 36 |
|
|
|
|
| 37 |
|
| 38 |
+
FROM dhi.io/python:3.13 AS runtime
|
|
|
|
|
|
|
| 39 |
|
| 40 |
+
ARG WORKDIR_ROOT
|
| 41 |
|
| 42 |
+
ENV VIRTUAL_ENV=${WORKDIR_ROOT}/.venv \
|
| 43 |
+
PATH="${WORKDIR_ROOT}/.venv/bin:/opt/python/bin:$PATH"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
# Copy pre-created workdir with correct ownership
|
| 46 |
+
COPY --from=dir_creator --chown=65532:65532 ${WORKDIR_ROOT} ${WORKDIR_ROOT}
|
| 47 |
+
# Copy libexpat (required by rasterio's bundled GDAL)
|
| 48 |
+
COPY --from=dir_creator /usr/lib/*-linux-gnu/libexpat.so* /usr/lib/
|
| 49 |
+
WORKDIR ${WORKDIR_ROOT}
|
|
|
|
| 50 |
|
| 51 |
+
# Copy venv from builder and app files
|
| 52 |
+
COPY --from=builder_global --chown=65532:65532 ${WORKDIR_ROOT}/.venv ${WORKDIR_ROOT}/.venv
|
| 53 |
+
COPY --chown=65532:65532 ./app.py ./scripts/client_health.py ${WORKDIR_ROOT}/
|
| 54 |
|
| 55 |
+
# Smoke test: verify critical imports work without libGL, libexpat, or system GDAL
|
| 56 |
+
RUN ["python3", "-c", "import rasterio; import onnxruntime; import fastapi; import uvicorn"]
|
|
|
|
| 57 |
|
|
|
|
| 58 |
|
| 59 |
+
### Frontend build (unchanged)
|
| 60 |
FROM node:24-alpine AS node_fastapi
|
| 61 |
|
| 62 |
ENV PNPM_HOME="/pnpm"
|
|
|
|
| 70 |
FROM node_fastapi AS node_prod_deps
|
| 71 |
|
| 72 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm install --prod --frozen-lockfile
|
|
|
|
| 73 |
RUN if [ ! -d /appnode/node_modules ]; then echo "no node_modules folder" && exit 1; fi
|
| 74 |
|
| 75 |
|
|
|
|
| 78 |
ARG VITE__MAP_DESCRIPTION
|
| 79 |
ARG VITE__SAMGIS_SPACE
|
| 80 |
ENV VITE__INDEX_URL="/"
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm install --frozen-lockfile
|
| 83 |
RUN --mount=type=cache,id=pnpm,target=/pnpm/store; pnpm build
|
|
|
|
| 90 |
ARG FASTAPI_STATIC
|
| 91 |
ARG WORKDIR_ROOT
|
| 92 |
|
| 93 |
+
COPY --chown=65532:65532 ./sam-quantized ${WORKDIR_ROOT}/sam-quantized
|
| 94 |
+
COPY --chown=65532:65532 --from=node_prod_deps /appnode/node_modules* ${FASTAPI_STATIC}/node_modules
|
| 95 |
+
COPY --chown=65532:65532 --from=node_build /appnode/dist* ${FASTAPI_STATIC}/dist
|
| 96 |
+
COPY --chown=65532:65532 static/list_files.html ${FASTAPI_STATIC}/
|
|
|
|
|
|
|
|
|