8f10353f0c
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
113 lines
4.5 KiB
Docker
113 lines
4.5 KiB
Docker
# 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"]
|