diff --git a/Dockerfile b/Dockerfile index 0dcb96f..76a9fd1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,57 +1,100 @@ # ---- Build Rust Extension for Multi-Arch ---- -FROM rust:latest AS builder - -WORKDIR /app - -# Install Python 3.13 and pip (needed for maturin) -RUN apt-get update && apt-get install -y python3.11 python3-pip python3-venv gcc musl-tools libssl-dev - -# Install maturin for building Rust-Python bindings -RUN cargo install maturin +FROM rust:1.83.0-alpine3.21 AS builder +COPY --from=ghcr.io/astral-sh/uv:0.6.2 /uv /uvx /bin/ + +RUN \ + apk add --no-cache \ + build-base \ + musl-dev \ + gcc \ + ca-certificates \ + python3 \ + python3-dev \ + libressl-dev \ + pkgconf \ + gcompat \ + && rm -rf /var/cache/apk/* + +# - Copy from the cache instead of linking since it's a mounted volume, +# - tell uv to byte-compile packages for faster application startups, +# - prevent uv from accidentally downloading isolated Python builds, +# - pick a Python version, +# - and finally declare `/app` as the target for `uv sync` (venv directory). +ENV UV_LINK_MODE=copy \ + UV_COMPILE_BYTECODE=1 \ + UV_PYTHON_DOWNLOADS=never \ + UV_PYTHON=python3.12 \ + UV_PROJECT_ENVIRONMENT=/app + +# Synchronize DEPENDENCIES without the application itself. +# This layer is cached until uv.lock or pyproject.toml change, which are +# only temporarily mounted into the build container since we don't need +# them in the production one. +# You can create `/app` using `uv venv` in a separate `RUN` +# step to have it cached, but with uv it's so fast, it's not worth +# it, so we let `uv sync` create it for us automagically. +RUN --mount=type=cache,target=/root/.cache \ + --mount=type=bind,source=uv.lock,target=uv.lock \ + --mount=type=bind,source=pyproject.toml,target=pyproject.toml \ + uv sync \ + --locked \ + --no-install-project # Detect target architecture and set appropriate Rust target ARG TARGETPLATFORM RUN echo "Building for platform: $TARGETPLATFORM" -# Copy actual Rust source code -COPY . . +# Copy the rest of the project source code and install it +ADD . /app +WORKDIR /app # Build the Rust Python package with maturin for the correct architecture RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ - maturin build --release --out dist --target aarch64-unknown-linux-gnu -i python3.11; \ + uv run maturin develop --release --target aarch64-unknown-linux-musl; \ else \ - maturin build --release --out dist --target x86_64-unknown-linux-gnu -i python3.11; \ + uv run maturin develop --release --target x86_64-unknown-linux-musl; \ fi -RUN ls -la dist +RUN cargo clean # ---- Setup Python Service ---- -FROM python:3.11-slim AS runtime +FROM python:3.12-alpine3.21 AS runtime -WORKDIR /app/ark_resolver +# Install necessary dependencies +RUN apk add --no-cache \ + shadow \ + libgcc \ + libstdc++ -# Install required dependencies -RUN apt-get update && apt-get install -y libssl-dev && rm -rf /var/lib/apt/lists/* -# Create a virtual environment -RUN python -m venv /venv -ENV PATH="/venv/bin:$PATH" +# Don't run your app as root. +RUN \ + groupadd -r app \ + && useradd -r -d /app -g app -N app -# Install Python dependencies -COPY requirements.txt . -RUN pip install --no-cache-dir --upgrade pip && pip install --no-cache-dir -r requirements.txt +# Copy the pre-built `/app` directory to the runtime container +# and change the ownership to user app and group app in one step. +COPY --from=builder --chown=app:app /app /app -# Copy the Rust-built Python package and install it -COPY --from=builder /app/dist/*.whl . -RUN pip install *.whl && rm -f *.whl - -# Verify the installation of the ark_resolver module -RUN python -c "import _rust; print('_rust module installed successfully')" - -# Copy Python service code -COPY python/src/ark_resolver /app/ark_resolver +USER app +WORKDIR /app -ENV PYTHONPATH="/app" +# Optional: add the application virtualenv to search path. +# We set the venv in the previous stage to `/app`, so we add it to the PATH. +ENV PATH=/app/bin:$PATH +ENV PYTHONPATH=/app/python/src:/app/lib/python3.12/site-packages +ENV VIRTUAL_ENV=/app -ENTRYPOINT ["python3", "/app/ark_resolver/ark.py"] -CMD ["-s"] \ No newline at end of file +# Verify the installation of the ark_resolver module +# Strictly optional, but I like it for introspection of what I've built +# and run a smoke test that the application can, in fact, be imported. +RUN \ + python3 -V \ + && python3 -m site \ + && python3 -c "import sys; print(sys.path)" \ + && python3 -c 'import ark_resolver; print(ark_resolver)' \ + && python3 -c 'import _rust; print(_rust)' + +COPY --chmod=0755 entrypoint.sh /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] +CMD ["-s", "-c", "/app/python/src/ark_resolver/ark-config.ini"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 0215e89..4a9c571 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,6 +1,6 @@ services: ark-resolver: - build: . + image: daschswiss/ark-resolver:latest ports: - "3336:3336" environment: @@ -10,4 +10,4 @@ services: ARK_INTERNAL_PORT: "3336" ARK_NAAN: "99999" ARK_HTTPS_PROXY: false - ARK_REGISTRY: "app/python/src/ark_resolver/ark-registry.ini" + ARK_REGISTRY: "/app/python/src/ark_resolver/ark-registry.ini" diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100644 index 0000000..a9b6b48 --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,3 @@ +#!/bin/sh +set -e # Exit immediately if a command exits with a non-zero status +exec python3 -m ark_resolver.ark "$@" \ No newline at end of file