muscriptor-chords / Dockerfile
jerryzhou196
Initial chord service
e319a7b unverified
Raw History Blame Contribute Delete
4.14 kB
# Image for the "muscriptor-chords" Hugging Face Docker Space (see the README
# next to this file). The build context is the *repository root*, not this
# directory: the image installs the real `muscriptor` package from the checkout
# so the Space runs the same chord recognition code as the main server rather
# than a copy of it. Build it locally with:
#
# docker build -f deploy/huggingface/Dockerfile -t muscriptor-chords .
#
# and the sync workflow lays the Space repo out the same way (this file copied
# to the Space root, the rest of the tree kept where it is).
FROM python:3.11-slim
# ffmpeg and libsndfile1 are what soundfile/librosa call out to for anything
# that is not a PCM WAV β€” mp3, m4a, ogg, flac. Everything else the service
# needs is a wheel.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ffmpeg \
libsndfile1 \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# A Space runs as a non-root user, so every path the process writes to has to
# belong to it: HOME (where `muscriptor.utils.download` caches the BTC
# checkpoint), the HF hub cache (where beat_this's checkpoint lands β€” the
# default /root/.cache is not writable here) and numba's cache, which librosa
# fills on first use.
ENV HOME=/home/app \
HF_HOME=/home/app/.cache/huggingface \
XDG_CACHE_HOME=/home/app/.cache \
NUMBA_CACHE_DIR=/home/app/.cache/numba \
PYTHONUNBUFFERED=1 \
PIP_NO_CACHE_DIR=1 \
PIP_DISABLE_PIP_VERSION_CHECK=1
RUN useradd --uid 10001 --create-home --home-dir /home/app --shell /usr/sbin/nologin app \
&& mkdir -p /home/app/.cache /app \
&& chown -R 10001:10001 /home/app /app
WORKDIR /app
# torch and torchaudio come from PyTorch's CPU index, pinned to the versions
# uv.lock resolves. The default PyPI wheels bundle CUDA β€” over 2 GB of it β€” for
# a Space that will only ever run `torch.device("cpu")`. Bump these together
# when the lockfile moves; they must stay a matched pair.
ARG TORCH_VERSION=2.13.0
ARG TORCHAUDIO_VERSION=2.11.0
RUN pip install --no-cache-dir --index-url https://download.pytorch.org/whl/cpu \
"torch==${TORCH_VERSION}" "torchaudio==${TORCHAUDIO_VERSION}"
# uv is the project's package manager; here it is used only as a fast pip that
# can install a pyproject's dependencies without its source, so the dependency
# layer survives every change to muscriptor's own code. Nothing resolves torch
# again: the CPU build above already satisfies the `torch>=2.3` floor.
COPY --from=ghcr.io/astral-sh/uv:0.12.3 /uv /usr/local/bin/uv
COPY pyproject.toml uv.lock ./
RUN uv pip install --system --no-cache -r pyproject.toml
# README.md and LICENSE are not documentation here β€” hatchling reads both while
# building the wheel (`readme` and `license-files` in pyproject.toml), and the
# build fails without them.
COPY README.md LICENSE ./
COPY muscriptor/ ./muscriptor/
COPY deploy/huggingface/app.py ./app.py
RUN uv pip install --system --no-cache --no-deps .
USER 10001
# Bake the model weights into the image, the way the root Dockerfile pre-warms
# the soundfonts: a Space that has gone to sleep must not spend its first
# request downloading 12 MB of checkpoint and verifying it. `_checkpoint_path`
# is the same download-and-check-the-digest path the service uses at runtime,
# so a tampered or truncated download fails the build rather than the request.
# The beat tracker's checkpoint is best-effort β€” it is a separate host, and
# chords without a beat grid are still chords.
RUN python -c "from muscriptor.utils.chords import _checkpoint_path; print('BTC checkpoint at', _checkpoint_path())" \
&& (python -c "from beat_this.inference import Audio2Beats; Audio2Beats(checkpoint_path='final0', device='cpu')" \
|| echo 'warning: beat tracker checkpoint not pre-fetched; it downloads on first use')
# 7860 is the port Hugging Face routes a Docker Space to (app_port in the
# README frontmatter). One worker on purpose: the concurrency cap, the rate
# limiter and the loaded model are all per-process state.
EXPOSE 7860
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]