Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 62 additions & 32 deletions dockerfiles/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,65 +1,95 @@
FROM cgr.dev/chainguard/wolfi-base:latest@sha256:3754b6da0e1ccdab0fe46abfdd7bbbba994b593c28149f6659fa6597f2261aeb AS base

ARG PYTHON_VERSION=3.14

FROM python:${PYTHON_VERSION} AS builder
ENV PYTHONUNBUFFERED=1

# Set build labels
LABEL stage=builder
LABEL org.opencontainers.image.source="https://github.com/developmentseed/titiler"
LABEL org.opencontainers.image.description="TiTiler"
LABEL org.opencontainers.image.licenses="MIT"
# Install runtime dependencies. Versions deliberately unpinned: Wolfi rolls
# forward and garbage-collects old versions, so an "=<version>" pin breaks the
# build within days. The guardrails are the version-scoped names plus the
# digest above. No pip: uv (below) installs everything.
# libstdc++ NOT optional: the rasterio/pyproj/duckdb manylinux wheels list
# libstdc++.so.6 in DT_NEEDED and auditwheel does not vendor it.
# bash, tzdata, curl parity with the old Debian base: /bin/sh is busybox,
# Wolfi ships no /usr/share/zoneinfo, curl is for in-pod debugging.
RUN apk add --no-cache \
python-${PYTHON_VERSION} \
libstdc++ bash tzdata curl libexpat1

# uv as a pass-through stage rather than a direct COPY --from=<image>:
# Dependabot only parses FROM lines (dependabot/dependabot-core#5103), so this
# keeps the version pinned *and* auto-updated by the docker ecosystem.
FROM ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv

# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1
# Build stage
FROM base AS builder

# Install build dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libexpat1 curl && \
rm -rf /var/lib/apt/lists/*
ARG PYTHON_VERSION

# Install uv
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
COPY --from=uv /uv /uvx /usr/local/bin/

# Configure uv-managed virtual environment
# UV_PYTHON: the apk python's version, but resolved by uv (not the apk
# binary itself: Wolfi's rolling python-3.14 build has repeatedly required
# a newer glibc than the glibc package available in the same repo snapshot,
# making /usr/bin/python3.14 unable to import stdlib extension modules).
# uv instead downloads its own self-contained, statically-linked build.
# UV_PYTHON_INSTALL_DIR: put that managed interpreter next to /opt/venv (both
# get copied into the runtime stage below) — otherwise it lands under
# /root/.local/share/uv, which never reaches the runtime stage, leaving
# /opt/venv/bin/python (and every venv script's shebang, eg uvicorn) a
# dangling symlink: `exec .../uvicorn: no such file or directory`.
# UV_COMPILE_BYTECODE: .pyc at install time, as pip did, so cold starts
# don't pay the compile on first import
ENV UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=/opt/venv \
UV_PYTHON=${PYTHON_VERSION} \
UV_PYTHON_INSTALL_DIR=/opt/uv-python \
UV_COMPILE_BYTECODE=1 \
PATH="/opt/venv/bin:${PATH}"

WORKDIR /tmp
WORKDIR /tmp/app

# Copy project metadata and code
# Copy project metadata and dependencies
COPY pyproject.toml uv.lock README.md LICENSE ./
COPY src/titiler/ ./src/titiler/

# Install project dependencies into the uv-managed virtual environment.
# --no-editable: the runtime stage only copies /opt/venv (and /opt/uv-python),
# not this /tmp/app source tree, so editable installs (.pth files pointing
# back at /tmp/app) would leave `import titiler...` broken at runtime.
RUN uv sync --frozen --no-dev --no-editable --group server
RUN uv pip install --no-deps --no-editable .

# uv skips downloading its own managed interpreter when the apk python already
# satisfies UV_PYTHON, leaving UV_PYTHON_INSTALL_DIR absent. Make sure it
# exists (even empty) so the unconditional COPY below never fails.
RUN mkdir -p /opt/uv-python

# Runtime stage
FROM python:${PYTHON_VERSION}-slim
FROM base

# Set runtime labels
LABEL org.opencontainers.image.source="https://github.com/developmentseed/titiler"
LABEL org.opencontainers.image.description="TiTiler"
LABEL org.opencontainers.image.licenses="MIT"

# Set environment variables
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PATH="/opt/venv/bin:$PATH"
# The chart execs `command: ["uvicorn"]` — a bare binary, resolved against the
# image's PATH.
ENV PATH="/opt/venv/bin:${PATH}"

# Install runtime dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends \
libexpat1 curl && \
rm -rf /var/lib/apt/lists/*

# Copy virtual environment from builder
# Copy virtual environment and its interpreter from builder
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /opt/uv-python /opt/uv-python

RUN groupadd -g 1000 user && \
useradd -u 1000 -g user -s /bin/bash -m user
WORKDIR /tmp

USER user
# Run as the wolfi-base nonroot user (uid 65532). On Kubernetes the chart sets
# the net.ipv4.ip_unprivileged_port_start sysctl per pod.
# Other deployments must allow unprivileged low ports the same way,
# or override PORT to something >=1024.
USER nonroot

###################################################
# For compatibility (might be removed at one point)
Expand All @@ -68,4 +98,4 @@
ENV HOST=0.0.0.0
ENV PORT=80
ENV WEB_CONCURRENCY=1
CMD gunicorn -k uvicorn.workers.UvicornWorker ${MODULE_NAME}:${VARIABLE_NAME} --bind ${HOST}:${PORT} --workers ${WEB_CONCURRENCY}

Check warning on line 101 in dockerfiles/Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-scan (main, amd64)

JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals

JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals More info: https://docs.docker.com/go/dockerfile/rule/json-args-recommended/

Check warning on line 101 in dockerfiles/Dockerfile

View workflow job for this annotation

GitHub Actions / build-and-scan (main, arm64)

JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals

JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals More info: https://docs.docker.com/go/dockerfile/rule/json-args-recommended/
104 changes: 88 additions & 16 deletions dockerfiles/Dockerfile.xarray
Original file line number Diff line number Diff line change
@@ -1,26 +1,98 @@
# WARNING: This image is deprecated and will be removed in 3.0.

FROM cgr.dev/chainguard/wolfi-base:latest@sha256:3754b6da0e1ccdab0fe46abfdd7bbbba994b593c28149f6659fa6597f2261aeb AS base

ARG PYTHON_VERSION=3.14

FROM python:${PYTHON_VERSION}
RUN apt update && apt upgrade -y \
&& apt install curl -y \
&& rm -rf /var/lib/apt/lists/*
ENV PYTHONUNBUFFERED=1

# Install runtime dependencies. Versions deliberately unpinned: Wolfi rolls
# forward and garbage-collects old versions, so an "=<version>" pin breaks the
# build within days. The guardrails are the version-scoped names plus the
# digest above. No pip: uv (below) installs everything.
# libstdc++ NOT optional: the rasterio/pyproj/duckdb manylinux wheels list
# libstdc++.so.6 in DT_NEEDED and auditwheel does not vendor it.
# bash, tzdata, curl parity with the old Debian base: /bin/sh is busybox,
# Wolfi ships no /usr/share/zoneinfo, curl is for in-pod debugging.
RUN apk add --no-cache \
python-${PYTHON_VERSION} \
libstdc++ bash tzdata curl libexpat1

# uv as a pass-through stage rather than a direct COPY --from=<image>:
# Dependabot only parses FROM lines (dependabot/dependabot-core#5103), so this
# keeps the version pinned *and* auto-updated by the docker ecosystem.
FROM ghcr.io/astral-sh/uv:0.12.5@sha256:e85be844203885286c60ffad8a858d48afb6c5a5c237ca0e67f12e74b8f174b1 AS uv

# Build stage
FROM base AS builder

ARG PYTHON_VERSION

# Install uv
COPY --from=uv /uv /uvx /usr/local/bin/

# Ensure root certificates are always updated at evey container build
# and curl is using the latest version of them
# RUN mkdir /usr/local/share/ca-certificates/cacert.org
# RUN cd /usr/local/share/ca-certificates/cacert.org && curl -k -O https://www.cacert.org/certs/root.crt
# RUN cd /usr/local/share/ca-certificates/cacert.org && curl -k -O https://www.cacert.org/certs/class3.crt
# ENV CURL_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
RUN update-ca-certificates
# Configure uv-managed virtual environment
# UV_PYTHON: the apk python's version, but resolved by uv (not the apk
# binary itself: Wolfi's rolling python-3.14 build has repeatedly required
# a newer glibc than the glibc package available in the same repo snapshot,
# making /usr/bin/python3.14 unable to import stdlib extension modules).
# uv instead downloads its own self-contained, statically-linked build.
# UV_PYTHON_INSTALL_DIR: put that managed interpreter next to /opt/venv (both
# get copied into the runtime stage below) — otherwise it lands under
# /root/.local/share/uv, which never reaches the runtime stage, leaving
# /opt/venv/bin/python (and every venv script's shebang, eg uvicorn) a
# dangling symlink: `exec .../uvicorn: no such file or directory`.
# UV_COMPILE_BYTECODE: .pyc at install time, as pip did, so cold starts
# don't pay the compile on first import
ENV UV_LINK_MODE=copy \
UV_PROJECT_ENVIRONMENT=/opt/venv \
UV_PYTHON=${PYTHON_VERSION} \
UV_PYTHON_INSTALL_DIR=/opt/uv-python \
UV_COMPILE_BYTECODE=1 \
PATH="/opt/venv/bin:${PATH}"

RUN python -m pip install -U pip
RUN python -m pip install uvicorn uvicorn-worker gunicorn
WORKDIR /tmp/app

COPY src/titiler/ /tmp/titiler/
RUN python -m pip install /tmp/titiler/core["telemetry"] /tmp/titiler/xarray --no-cache-dir --upgrade
RUN rm -rf /tmp/titiler
# Copy project metadata and dependencies
COPY pyproject.toml uv.lock README.md LICENSE ./
COPY src/titiler/ ./src/titiler/

# Install project dependencies into the uv-managed virtual environment.
# --no-editable: the runtime stage only copies /opt/venv (and /opt/uv-python),
# not this /tmp/app source tree, so editable installs (.pth files pointing
# back at /tmp/app) would leave `import titiler...` broken at runtime.
RUN uv sync --frozen --no-dev --no-editable --group server
RUN uv pip install --no-deps --no-editable /tmp/app/src/titiler/core["telemetry"] /tmp/app/src/titiler/xarray

# uv skips downloading its own managed interpreter when the apk python already
# satisfies UV_PYTHON, leaving UV_PYTHON_INSTALL_DIR absent. Make sure it
# exists (even empty) so the unconditional COPY below never fails.
RUN mkdir -p /opt/uv-python

# Runtime stage
FROM base

# Set runtime labels
LABEL deprecated=TRUE
LABEL org.opencontainers.image.source="https://github.com/developmentseed/titiler"
LABEL org.opencontainers.image.description="TiTiler Xarray"
LABEL org.opencontainers.image.licenses="MIT"

# The chart execs `command: ["uvicorn"]` — a bare binary, resolved against the
# image's PATH.
ENV PATH="/opt/venv/bin:${PATH}"

# Copy virtual environment and its interpreter from builder
COPY --from=builder /opt/venv /opt/venv
COPY --from=builder /opt/uv-python /opt/uv-python

WORKDIR /tmp

# Run as the wolfi-base nonroot user (uid 65532). On Kubernetes the chart sets
# the net.ipv4.ip_unprivileged_port_start sysctl per pod.
# Other deployments must allow unprivileged low ports the same way,
# or override PORT to something >=1024.
USER nonroot

###################################################
# For compatibility (might be removed at one point)
Expand All @@ -29,4 +101,4 @@
ENV HOST=0.0.0.0
ENV PORT=80
ENV WEB_CONCURRENCY=1
CMD gunicorn -k uvicorn.workers.UvicornWorker ${MODULE_NAME}:${VARIABLE_NAME} --bind ${HOST}:${PORT} --workers ${WEB_CONCURRENCY}

Check warning on line 104 in dockerfiles/Dockerfile.xarray

View workflow job for this annotation

GitHub Actions / build-and-scan (xarray, amd64)

JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals

JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals More info: https://docs.docker.com/go/dockerfile/rule/json-args-recommended/

Check warning on line 104 in dockerfiles/Dockerfile.xarray

View workflow job for this annotation

GitHub Actions / build-and-scan (xarray, arm64)

JSON arguments recommended for ENTRYPOINT/CMD to prevent unintended behavior related to OS signals

JSONArgsRecommended: JSON arguments recommended for CMD to prevent unintended behavior related to OS signals More info: https://docs.docker.com/go/dockerfile/rule/json-args-recommended/
24 changes: 4 additions & 20 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ classifiers = [
]
version="2.2.2"
dependencies = [
"titiler-core",
"titiler-xarray",
"titiler-extensions",
"titiler-core[telemetry]",
"titiler-xarray[fs]",
"titiler-extensions[cogeo,stac]",
"titiler-mosaic[mosaicjson]",
"titiler-application",
]
Expand All @@ -42,17 +42,8 @@ dev = [
"pytest",
"pytest-cov",
"pytest-asyncio",
"httpx2",
"obstore",
"zarr>=3,<4.0",
"h5netcdf",
"h5py",
"fsspec",
"s3fs>=2025.2.0",
"aiohttp",
"requests",
"brotlicffi",
"pystac[validation]>=1.0.0,<2.0.0",
"brotlipy",
"boto3",
"owslib",
"pre-commit",
Expand All @@ -61,13 +52,6 @@ dev = [
"matplotlib",
"folium",
]
telemetry = [
"opentelemetry-api",
"opentelemetry-sdk",
"opentelemetry-instrumentation-fastapi",
"opentelemetry-instrumentation-logging",
"opentelemetry-exporter-otlp",
]
docs = [
"black>=23.10.1",
"mkdocs>=1.4.3",
Expand Down
3 changes: 1 addition & 2 deletions src/titiler/application/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ test = [
"pytest",
"pytest-cov",
"pytest-asyncio",
"httpx2",
"brotlipy",
"brotlicffi",
"boto3",
]

Expand Down
3 changes: 1 addition & 2 deletions src/titiler/core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ dependencies = [
"numpy",
"pydantic~=2.0",
"rasterio",
"rio-tiler>=9.0,<10.0",
"rio-tiler>=9.1,<10.0",
"morecantile",
"simplejson",
]
Expand All @@ -56,7 +56,6 @@ test = [
"pytest",
"pytest-cov",
"pytest-asyncio",
"httpx2",
]

[project.urls]
Expand Down
1 change: 0 additions & 1 deletion src/titiler/extensions/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ test = [
"pytest",
"pytest-cov",
"pytest-asyncio",
"httpx2",
"pystac[validation]>=1.0.0,<2.0.0",
"owslib",
]
Expand Down
1 change: 0 additions & 1 deletion src/titiler/mosaic/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ test = [
"pytest",
"pytest-cov",
"pytest-asyncio",
"httpx2",
"owslib",
]

Expand Down
1 change: 0 additions & 1 deletion src/titiler/xarray/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ dependencies = [
"zarr>=3.1,<4.0",
"starlette-cramjam>=0.4,<1.0",
"pydantic-settings~=2.0",
"httpx2",
]

[project.optional-dependencies]
Expand Down
Loading
Loading