# Stage 1: Essentia builder # Essentia doesn't have wheels for Python 3.12 yet; we use the official image # and copy the bindings into our final stage via a bind mount. FROM docker.io/mtgupf/essentia:latest AS essentia-builder FROM python:3.12-slim AS base WORKDIR /app # System dependencies for audio processing RUN apt-get update && apt-get install -y --no-install-recommends \ ffmpeg \ libsndfile1 \ && rm -rf /var/lib/apt/lists/* # Copy Essentia Python bindings from builder (best-effort: no-op if the library # wasn't built for this Python version or the path doesn't exist). # COPY does not support shell redirections, so we use RUN --mount instead. RUN --mount=type=bind,from=essentia-builder,source=/usr/local/lib,target=/essentia_lib \ find /essentia_lib -maxdepth 4 -name "essentia*" \ -exec cp -r {} /usr/local/lib/python3.12/site-packages/ \; \ 2>/dev/null || true RUN pip install uv FROM base AS development COPY pyproject.toml . RUN uv sync --all-extras --no-install-project --frozen || uv sync --all-extras --no-install-project ENV PYTHONPATH=/app/src ENV PYTHONUNBUFFERED=1 ENV LOG_LEVEL=DEBUG CMD ["/bin/sh", "-c", "PYTHONPATH=/app/src exec /app/.venv/bin/watchfiles --ignore-permission-denied '/app/.venv/bin/python -m worker.main' src"] FROM base AS production COPY pyproject.toml . RUN uv sync --no-dev --frozen || uv sync --no-dev COPY . . ENV PYTHONPATH=/app/src # Pre-warm librosa/numba JIT cache and pooch downloads so they happen at build # time and are baked into the image rather than downloaded on every cold start. RUN uv run python -c "\ import numpy as np; \ import librosa; \ _dummy = np.zeros(22050, dtype=np.float32); \ librosa.beat.beat_track(y=_dummy, sr=22050); \ librosa.feature.chroma_stft(y=_dummy, sr=22050); \ print('librosa warmup done') \ " CMD ["uv", "run", "python", "-m", "worker.main"]