chore: import upstream snapshot with attribution
CI / lint (push) Has been cancelled
CI / js-syntax (push) Successful in 10m24s
CI / deps-audit (push) Has been cancelled
CI / test (push) Failing after 24m55s
CI / sast-bandit (push) Failing after 11m13s
CI / trivy (push) Failing after 9m32s
Docker Publish / build-and-push (push) Failing after 34m3s
CI / lint (push) Has been cancelled
CI / js-syntax (push) Successful in 10m24s
CI / deps-audit (push) Has been cancelled
CI / test (push) Failing after 24m55s
CI / sast-bandit (push) Failing after 11m13s
CI / trivy (push) Failing after 9m32s
Docker Publish / build-and-push (push) Failing after 34m3s
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
# Two-stage build:
|
||||
# * builder -- has gcc/build-essential/curl/unzip/pip etc.; produces
|
||||
# an isolated /opt/venv with all Python deps installed
|
||||
# and a fetched deno binary.
|
||||
# * runner -- only the runtime needs: Python (slim), ffmpeg,
|
||||
# ca-certificates, the venv copied from the builder,
|
||||
# and our app code. No compilers, no pip cache, no
|
||||
# download tooling -- shaves ~150-300 MB off the
|
||||
# single-stage image and reduces attack surface.
|
||||
|
||||
# ─── Stage 1: builder ────────────────────────────────────────────
|
||||
FROM python:3.12-slim AS builder
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PIP_NO_CACHE_DIR=1 \
|
||||
PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
|
||||
# Build deps for pip wheels that fall back to source compilation
|
||||
# (numpy/scipy ship manylinux wheels, but smaller deps occasionally
|
||||
# need a compiler). Plus curl/unzip to fetch deno.
|
||||
RUN apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
curl \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# deno -- yt-dlp uses it as the JS runtime for YouTube format
|
||||
# extraction. Staged here so the runner doesn't need curl/unzip.
|
||||
ARG TARGETARCH
|
||||
RUN case "${TARGETARCH:-$(dpkg --print-architecture)}" in \
|
||||
amd64) DENO_ARCH=x86_64-unknown-linux-gnu ;; \
|
||||
arm64) DENO_ARCH=aarch64-unknown-linux-gnu ;; \
|
||||
*) echo "Unsupported arch: ${TARGETARCH}" && exit 1 ;; \
|
||||
esac \
|
||||
&& curl -fsSL -o /tmp/deno.zip "https://github.com/denoland/deno/releases/latest/download/deno-${DENO_ARCH}.zip" \
|
||||
&& unzip /tmp/deno.zip -d /usr/local/bin \
|
||||
&& rm /tmp/deno.zip \
|
||||
&& /usr/local/bin/deno --version
|
||||
|
||||
# Isolated venv -- the runner stage copies /opt/venv wholesale,
|
||||
# inheriting all installed deps without dragging in pip caches,
|
||||
# /var/lib/apt state, or build tools.
|
||||
RUN python -m venv /opt/venv
|
||||
ENV PATH="/opt/venv/bin:$PATH"
|
||||
|
||||
WORKDIR /app
|
||||
COPY pyproject.toml ./
|
||||
COPY app/ ./app/
|
||||
COPY static/ ./static/
|
||||
# The version is git-derived (hatch-vcs), but the build context has no .git, so
|
||||
# pin it explicitly: `docker build --build-arg VERSION=0.7.0-alpha.4 ...`
|
||||
# (defaults to 0.0.0 for ad-hoc local builds). #169
|
||||
ARG VERSION=0.0.0
|
||||
RUN SETUPTOOLS_SCM_PRETEND_VERSION="${VERSION#v}" pip install . --timeout 300
|
||||
|
||||
# ─── Stage 2: runner ─────────────────────────────────────────────
|
||||
FROM python:3.12-slim AS runner
|
||||
|
||||
ENV PYTHONUNBUFFERED=1 \
|
||||
PYTHONDONTWRITEBYTECODE=1 \
|
||||
PATH="/opt/venv/bin:$PATH" \
|
||||
TORCH_HOME=/cache/torch \
|
||||
XDG_CACHE_HOME=/cache
|
||||
|
||||
# Runtime-only OS deps. ffmpeg has many shared libs (libav*, libsw*,
|
||||
# libpostproc), so we install via apt rather than copy-pasting binaries
|
||||
# and chasing transitive .so dependencies. ca-certificates is needed
|
||||
# for HTTPS to YouTube.
|
||||
RUN apt-get update \
|
||||
&& apt-get upgrade -y \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
ffmpeg \
|
||||
ca-certificates \
|
||||
gosu \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Deno binary fetched in stage 1 (single self-contained file).
|
||||
COPY --from=builder /usr/local/bin/deno /usr/local/bin/deno
|
||||
|
||||
# Pre-built Python virtualenv with all our deps. The PATH env above
|
||||
# means `python`, `uvicorn`, `pip`, etc. resolve into the venv with
|
||||
# no further configuration.
|
||||
COPY --from=builder /opt/venv /opt/venv
|
||||
|
||||
# Application code -- copied to /app rather than only relied-on via
|
||||
# site-packages because app/core/config.py derives STATIC_DIR from
|
||||
# __file__'s parent chain, and the cwd-rooted /app/app version sits
|
||||
# earlier on sys.path than the venv install of `app`. Without the
|
||||
# COPY, STATIC_DIR would resolve into site-packages where static/
|
||||
# wasn't included by hatchling's wheel target.
|
||||
WORKDIR /app
|
||||
COPY app/ ./app/
|
||||
COPY static/ ./static/
|
||||
|
||||
# Create the app user and own all non-bind-mounted paths. /app/jobs is a
|
||||
# bind mount at runtime -- the entrypoint script re-chowns it to app:app
|
||||
# before dropping privileges, handling the case where Docker creates the
|
||||
# host directory as root on first run.
|
||||
RUN groupadd --system --gid 1001 app \
|
||||
&& useradd --system --uid 1001 --gid app --home /app --no-create-home app \
|
||||
&& mkdir -p /cache /jobs \
|
||||
&& chown -R app:app /app /cache /jobs
|
||||
|
||||
COPY build/docker-entrypoint.sh /entrypoint.sh
|
||||
RUN chmod +x /entrypoint.sh
|
||||
|
||||
EXPOSE 8000
|
||||
ENTRYPOINT ["/entrypoint.sh"]
|
||||
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||
@@ -0,0 +1,26 @@
|
||||
# Explicit name so compose doesn't derive it from the parent directory
|
||||
# (which is now `build/`, giving misleading object names like
|
||||
# `build_default` for the network).
|
||||
name: stemdeck
|
||||
|
||||
services:
|
||||
stemdeck:
|
||||
# Compose file lives in build/, so the build context is the parent
|
||||
# (project root) and the Dockerfile sits next to this compose file.
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: build/Dockerfile
|
||||
image: stemdeck
|
||||
container_name: stemdeck
|
||||
ports:
|
||||
- "8000:8000"
|
||||
volumes:
|
||||
# Stems and downloaded audio land in <project root>/jobs on the
|
||||
# host so you can grab them without going through the container.
|
||||
- ../jobs:/app/jobs
|
||||
# Demucs model weights (~170 MB) survive container rebuilds.
|
||||
- stemdeck-cache:/cache
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
stemdeck-cache:
|
||||
@@ -0,0 +1,22 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
# Run as the requested UID/GID so files written to the mounted appdata paths are
|
||||
# owned consistently. This matches the Unraid/NAS convention (defaults there are
|
||||
# nobody:users = 99:100). When PUID/PGID are unset, fall back to the image's
|
||||
# original non-root app user (1001), preserving prior behaviour.
|
||||
PUID="${PUID:-1001}"
|
||||
PGID="${PGID:-1001}"
|
||||
|
||||
# Chown the only paths the app writes to before dropping privileges:
|
||||
# /app/jobs registry.json, downloaded audio, and stems
|
||||
# /cache torch/Demucs model weights (TORCH_HOME, XDG_CACHE_HOME)
|
||||
# /app/settings.json best-effort settings persistence (created on demand)
|
||||
# App code and the venv under /app stay world-readable, so a different UID can
|
||||
# still import and run them. Re-chowning is also what fixes a bind mount that
|
||||
# Docker created as root on first run.
|
||||
chown -R "${PUID}:${PGID}" /app/jobs /cache 2>/dev/null || true
|
||||
touch /app/settings.json 2>/dev/null && chown "${PUID}:${PGID}" /app/settings.json 2>/dev/null || true
|
||||
|
||||
# Drop to the target user and exec the CMD. gosu accepts a numeric UID:GID even
|
||||
# when no matching named user exists.
|
||||
exec gosu "${PUID}:${PGID}" "$@"
|
||||
Reference in New Issue
Block a user