diff --git a/docker/Dockerfile b/docker/Dockerfile index dbe078f5..2b5ee74f 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,4 +1,5 @@ -# ------------------------------ ffmpeg builder ------------------------------ # +# ------------------------------ Builder ffmpeg ------------------------------ # + # FFMPEG comes with a ton of dependencies (e.g. llvm) # the full install is over 400mb... # we use a static version which is only 50mb @@ -18,53 +19,18 @@ ARG TARGETARCH RUN curl -L https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-${TARGETARCH}-static.tar.xz \ | tar -xJ -C /tmp/ffmpeg --strip-components=1 -# -------------------------------- Base image -------------------------------- # -FROM python:3.12-slim-trixie AS base - -ENV HOSTNAME="beets-container" -ENV EDITOR="vi" -# need to set some cli editor so `beet edit` works, vi comes with slim -ENV BEETSDIR="/config/beets" -ENV BEETSFLASKDIR="/config/beets-flask" -ENV BEETSFLASKLOG="/logs/beets-flask.log" - -# Create user and group -RUN groupadd -g 1000 beetle && \ - useradd -m -u 1000 -g beetle beetle - -# map beets directory and our configs to /config -RUN mkdir -p /config/beets /config/beets-flask /logs && \ - chown -R beetle:beetle /config /logs - -# our default folders they should not be used in production -RUN mkdir -p /music/inbox /music/imported && \ - chown -R beetle:beetle /music - -# Install dependencies: -RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \ - apt-get update && \ - apt-get install -y --no-install-recommends \ - redis \ - tmux \ - imagemagick && \ - rm -rf /var/lib/apt/lists/* - -# Copy only the binaries from builder -COPY --from=builder_ffmpeg /tmp/ffmpeg/ffmpeg /usr/local/bin/ffmpeg -COPY --from=builder_ffmpeg /tmp/ffmpeg/ffprobe /usr/local/bin/ffprobe -RUN ffmpeg -version - # ------------------------------ Builder python ------------------------------ # + FROM ghcr.io/astral-sh/uv:python3.12-trixie-slim AS builder_py COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ WORKDIR /repo/backend -ENV PYTHONUNBUFFERED=1 \ - UV_COMPILE_BYTECODE=1 \ - UV_LINK_MODE=copy \ - UV_NO_DEV=1 \ - UV_PYTHON_DOWNLOADS=0 +ENV PYTHONUNBUFFERED=1 +ENV UV_COMPILE_BYTECODE=1 +ENV UV_LINK_MODE=copy +ENV UV_NO_DEV=1 +ENV UV_PYTHON_DOWNLOADS=0 # Install backend dependencies COPY ./backend/pyproject.toml ./backend/uv.lock /repo/backend/ @@ -85,6 +51,7 @@ RUN mkdir -p /version RUN python -c "import tomllib; print(tomllib.load(open('/repo/backend/pyproject.toml', 'rb'))['project']['version'])" > /version/backend.txt # ------------------------------- Builder node ------------------------------- # + FROM node:22-slim AS builder_node # Install pnpm @@ -105,9 +72,62 @@ RUN node -p "require('/repo/frontend/package.json').version" > /version/frontend RUN pnpm run build +# ---------------------------------------------------------------------------- # +# Base # +# ---------------------------------------------------------------------------- # + +FROM python:3.12-slim-trixie AS base +COPY --from=builder_py /bin/uv /bin/uvx /bin/ + +ENV HOSTNAME="beets-container" +ENV EDITOR="vi" +# need to set some cli editor so `beet edit` works, vi comes with slim +ENV BEETSDIR="/config/beets" +ENV BEETSFLASKDIR="/config/beets-flask" +ENV BEETSFLASKLOG="/logs/beets-flask.log" +ENV PATH="/repo/backend/.venv/bin:$PATH" + +# Create user and group +RUN groupadd -g 1000 beetle && \ + useradd -m -u 1000 -g beetle beetle + +# map beets directory and our configs to /config +RUN mkdir -p /config/beets /config/beets-flask /logs && \ + chown -R beetle:beetle /config /logs + +# our default folders they should not be used in production +RUN mkdir -p /music/inbox /music/imported && \ + chown -R beetle:beetle /music + +# Install dependencies: +RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + redis \ + tmux \ + imagemagick && \ + rm -rf /var/lib/apt/lists/* + +# Copy only the binaries from builder +COPY --from=builder_ffmpeg /tmp/ffmpeg/ffmpeg /usr/local/bin/ffmpeg +COPY --from=builder_ffmpeg /tmp/ffmpeg/ffprobe /usr/local/bin/ffprobe +RUN ffmpeg -version + +# Remove pip to avoid confusion (force users to use `uv pip install`) +RUN rm -f /usr/local/bin/pip /usr/local/bin/pip3 +RUN rm -rf /root/.cache/pip +RUN echo '#!/bin/sh\n\ + echo "Beets-Flask relies on uv for package management. Please avoid pip and use:"\n\ + echo "uv pip install "\n\ + exit 1' > /usr/local/bin/pip +RUN chmod 755 /usr/local/bin/pip +RUN chown root:root /usr/local/bin/pip +RUN ln -sf /usr/local/bin/pip /usr/local/bin/pip3 + # ------------------------------------------------------------------------------------ # # Production # # ------------------------------------------------------------------------------------ # + FROM base AS prod ENV IB_SERVER_CONFIG="prod" @@ -122,7 +142,6 @@ COPY --chown=beetle:beetle ./docker/entrypoints/*.sh /repo/ RUN chmod +x /repo/*.sh USER root -ENV PATH="/repo/backend/.venv/bin:$PATH" ENTRYPOINT [ \ "/bin/bash", "-c", \ @@ -137,20 +156,21 @@ ENTRYPOINT [ \ # ------------------------------------------------------------------------------------ # FROM base AS dev -COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/ -ENV UV_LINK_MODE=copy +ENV IB_SERVER_CONFIG="dev_docker" +ENV UV_LINK_MODE=copy RUN --mount=type=cache,sharing=locked,target=/var/cache/apt \ apt-get update && \ apt-get install -y --no-install-recommends \ curl \ - build-essential && \ + build-essential && \ rm -rf /var/lib/apt/lists/* # Install nodejs RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - RUN apt-get install -y nodejs + # Install pnpm RUN npm install --global corepack@latest RUN corepack enable pnpm @@ -160,21 +180,19 @@ RUN corepack use pnpm@latest-10 WORKDIR /repo COPY ./frontend/package.json ./frontend/pnpm-lock.yaml /repo/frontend/ COPY ./backend/pyproject.toml ./backend/uv.lock /repo/backend/ -WORKDIR /repo/frontend # Extract version from package.json +WORKDIR /repo/frontend RUN mkdir -p /version RUN node -p "require('/repo/frontend/package.json').version" > /version/frontend.txt RUN python -c "import tomllib; print(tomllib.load(open('/repo/backend/pyproject.toml', 'rb'))['project']['version'])" > /version/backend.txt -ENV IB_SERVER_CONFIG="dev_docker" - # relies on mounting this volume WORKDIR /repo USER root ENTRYPOINT [ \ "/bin/bash", "-c", \ - "./docker/entrypoints/entrypoint_fix_permissions.sh && \ + "./docker/entrypoints/entrypoint_fix_permissions.sh && \ ./docker/entrypoints/entrypoint_user_scripts.sh && \ su beetle -c ./docker/entrypoints/entrypoint_dev.sh" \ ] diff --git a/docker/entrypoints/entrypoint_dev.sh b/docker/entrypoints/entrypoint_dev.sh index cb1ffea5..85e14b91 100755 --- a/docker/entrypoints/entrypoint_dev.sh +++ b/docker/entrypoints/entrypoint_dev.sh @@ -43,7 +43,7 @@ export FLASK_DEBUG=1 cd /repo/backend uv sync --locked -source .venv/bin/activate +# No need to activate, we have this in PATH redis-server --daemonize yes diff --git a/docker/entrypoints/entrypoint_user_scripts.sh b/docker/entrypoints/entrypoint_user_scripts.sh index aa863c20..0ceeca06 100755 --- a/docker/entrypoints/entrypoint_user_scripts.sh +++ b/docker/entrypoints/entrypoint_user_scripts.sh @@ -19,9 +19,9 @@ fi # check for requirements.txt if [ -f /config/requirements.txt ]; then log "Installing pip requirements from /config/requirements.txt" - pip install -r /config/requirements.txt + uv pip install -r /config/requirements.txt fi if [ -f /config/beets-flask/requirements.txt ]; then log "Installing pip requirements from /config/beets-flask/requirements.txt" - pip install -r /config/beets-flask/requirements.txt + uv pip install -r /config/beets-flask/requirements.txt fi diff --git a/docs/plugins.md b/docs/plugins.md index 9d67218c..a6e1f317 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -8,9 +8,16 @@ Plugin support is experimental. Installing beets plugins varies depending on the particular plugin. [See the official docs](https://docs.beets.io/en/latest/plugins/index.html). -We might automate this in the future, but for now you can place a `requirements.txt` and/or `startup.sh` in either the `/config` folder or `/config/beets-flask` folder. The `requirements.txt` may include [python dependencies](https://pip.pypa.io/en/stable/reference/requirements-file-format/), and the `startup.sh` file may be an executable shell script that is compatible with the container's alpine linux base. +We might automate this in the future, but for now you can place a `requirements.txt` and/or `startup.sh` in either the `/config` folder or `/config/beets-flask` folder. The `requirements.txt` may include [python dependencies](https://pip.pypa.io/en/stable/reference/requirements-file-format/), and the `startup.sh` file may be an executable shell script that is compatible with the container's debian linux base. -On startup, the container will run the startup script if it exists, and afterwards install the requirements from the `requirements.txt` file using pip. +On startup, the container will run the startup script if it exists, and afterwards install the requirements from the `requirements.txt` file using [uv](https://docs.astral.sh/uv/pip/). + +```{note} +We use uv to manage python dependecies in a virtual environment at `/repo/backend/.venv`. +This should by default be activated already (`which python`), but note that, to install +more dependencies you need to use `uv pip install`. A normal `pip install` will not place +packages at the right location. +``` ## Example startup.sh: keyfinder