-
Notifications
You must be signed in to change notification settings - Fork 16
Expand file tree
/
Copy pathDockerfile
More file actions
214 lines (188 loc) · 10.4 KB
/
Copy pathDockerfile
File metadata and controls
214 lines (188 loc) · 10.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
# syntax=docker/dockerfile:1
# theDAW single-container release image.
#
# Stage "ui" builds the React frontend with Vite. Stage "runtime" installs the
# locked Python dependency set with uv, copies the backend plus the built UI,
# and serves everything from one uvicorn process on port 8600. The backend is
# single-worker BY DESIGN: generation jobs, model locks, and the JOBS registry
# live in process memory, so this image must never run with multiple workers
# or replicas.
#
# The linux x86_64 resolution in uv.lock pins torch 2.7.1+cu126 from the
# PyTorch cu126 index. Those wheels bundle the CUDA userspace libraries, so
# the base image needs no CUDA toolkit; GPU access only requires the NVIDIA
# driver plus nvidia-container-toolkit on the host (see docs/DOCKER.md).
########################################################################
# Stage 1: frontend build.
########################################################################
FROM node:22.23.1-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 AS ui
WORKDIR /build/frontend
# Dependency install is layered before the source copy so code edits do not
# invalidate the npm cache layer.
COPY frontend/package.json frontend/package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY frontend/ ./
RUN npm run build
########################################################################
# Stage 1b: VJ (gantasmo/VJ-9000) static build.
#
# The VJ app is a separate repo, so it is cloned and built here rather than
# copied from the build context. Its vite base is '/vj-app/' (for the build
# command), so the backend can mount the compiled dist at /vj-app and serve it
# with no Node.js at runtime. Override the source with VJ_REPO / VJ_REF.
########################################################################
FROM node:22.23.1-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 AS vj
WORKDIR /build/vj
# ca-certificates is required alongside git: the slim base ships no CA bundle,
# so the HTTPS clone of VJ-9000 below fails certificate verification without it.
RUN apt-get update \
&& apt-get install -y --no-install-recommends git ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ARG VJ_REPO=https://github.com/gantasmo/VJ-9000.git
# VJ branch carrying the '/vj-app/' build base, PINNED to a commit so image
# builds are reproducible and survive the branch being merged/deleted.
# KEEP IN SYNC with VJ_COMMIT in electron-ui/scripts/fetch-vj-build.mjs.
ARG VJ_REF=feat/vj-redesign-vfx
ARG VJ_COMMIT=ff7430b1bf66524cc30e509b56f1e743443798fb
RUN --mount=type=cache,target=/root/.npm \
git clone --branch "${VJ_REF}" "${VJ_REPO}" . \
&& git checkout --detach "${VJ_COMMIT}" \
&& npm ci \
&& npm run build
########################################################################
# Stage 1c: VST Foundry fullstack build.
#
# Foundry is an in-repo Node/Express app. Build it from the context, then prune
# to production deps so the runtime stage ships only what `node dist/server.cjs`
# needs. Package files are copied first so source edits don't bust the npm cache.
########################################################################
FROM node:22.23.1-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 AS foundry
WORKDIR /build/foundry
COPY VST-Foundry-UI/VST-UI-FOUNDRY/package.json VST-Foundry-UI/VST-UI-FOUNDRY/package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY VST-Foundry-UI/VST-UI-FOUNDRY/ ./
RUN npm run build && npm prune --omit=dev
########################################################################
# Stage 2: Python runtime.
########################################################################
FROM python:3.10-slim-bookworm@sha256:89cef4d55961e885def21b86e34e102e65b7eab8cd281e806a66ff1709c9a455 AS runtime
# Node.js runtime for the VST Foundry sidecar (it runs `node dist/server.cjs`).
# The single binary is copied from the official node image; libstdc++6 is the
# one shared lib the slim python base lacks that node links against.
COPY --from=node:22.23.1-slim@sha256:813a7480f28fdadac1f7f5c824bcdad435b5bc1322a5968bbbdef8d058f9dff4 /usr/local/bin/node /usr/local/bin/node
# The uv binary is copied from the official distroless image. The tag is
# pinned; bump it deliberately, never float on :latest.
COPY --from=ghcr.io/astral-sh/uv:0.11.26 /uv /uvx /bin/
# ffmpeg backs yt-dlp, loudness metering, and delivery encodes.
# build-essential is required because aubio 0.4.9 ships as an sdist and
# compiles at install time. git supports optional VCS installs such as the
# py-aup3 Audacity parser. libglib2.0-0 satisfies opencv-python-headless on
# slim images.
# apt-get upgrade patches fixable Debian CVEs in the digest-pinned base at
# build time (the base is immutable via @sha256, but its repos still serve
# current security updates). Kept in the same layer as install + cleanup.
RUN apt-get update \
&& apt-get upgrade -y --no-install-recommends \
&& apt-get install -y --no-install-recommends \
ffmpeg \
build-essential \
git \
libglib2.0-0 \
libstdc++6 \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
ENV PYTHONUNBUFFERED=1 \
UV_LINK_MODE=copy \
UV_COMPILE_BYTECODE=1 \
UV_PYTHON_DOWNLOADS=never
# All backend runtime dependencies live in [project.dependencies]; the dev
# group holds only pytest and ruff, so --no-dev is a verified reduction, not
# a guess. theDAW.bat runs "uv sync --group dev" on developer machines only
# to add those tools.
#
# pyk4a-bundle (the Azure Kinect point-cloud backend for the akvj module) is
# skipped in the container: its only Linux wheel is manylinux_2_38 (glibc
# >= 2.38) and this digest-pinned bookworm base ships glibc 2.36, so uv cannot
# install it here. The container has no Kinect device anyway, and the akvj
# sidecar imports pyk4a lazily (inside functions / via a subprocess probe), so
# the backend boots fine without it. The dependency stays in pyproject/uv.lock
# with its win32-or-linux-x86_64 marker so bare-metal Linux installs keep it.
COPY pyproject.toml uv.lock .python-version ./
# Prebuilt aubio wheels referenced by uv.lock's [tool.uv.sources] path entries.
# On linux x86_64 the marker-matched manylinux wheel is what this build installs,
# so it must be present in the context or `uv sync --frozen` fails on the path.
COPY wheels/ ./wheels/
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-project --no-install-package pyk4a-bundle
# README.md is required because pyproject.toml declares it as the project
# readme and the project install builds metadata from it.
COPY README.md CLAUDE.md ./
COPY stable_audio_3/ ./stable_audio_3/
COPY backend/ ./backend/
# backend/rag.py DOC_PATHS reads these markdown files from the source tree at
# runtime; a missing file only logs a warning, but shipping them keeps the
# in-app assistant's RAG index complete.
COPY docs/ ./docs/
COPY frontend/public/USER_GUIDE.md ./frontend/public/USER_GUIDE.md
# The second sync installs the project itself against the already-cached
# dependency set. pyk4a-bundle is skipped for the same reason as the first sync
# (glibc 2.38 wheel vs the base's glibc 2.36; no Kinect device in the container).
RUN --mount=type=cache,target=/root/.cache/uv \
uv sync --frozen --no-dev --no-install-package pyk4a-bundle
# The built SPA lands where backend/server.py expects it
# (PROJECT_ROOT/frontend/dist) for the theDAW_SERVE_UI mount.
COPY --from=ui /build/frontend/dist ./frontend/dist
# The VJ static build lands at /app/vj-dist, which the backend's VJ sidecar
# resolves (_REPO_ROOT/"vj-dist") and mounts at /vj-app. This removes the VJ
# tab's Node.js requirement, so it works in the container like anywhere else.
COPY --from=vj /build/vj/dist ./vj-dist
# The VST Foundry production bundle lands where the foundry sidecar resolves it
# (_REPO_ROOT/"VST-Foundry-UI"/"VST-UI-FOUNDRY"). It runs `node dist/server.cjs`
# with the node binary copied above — a fullstack Node app, so it ships dist/ +
# production node_modules + package.json.
COPY --from=foundry /build/foundry/dist ./VST-Foundry-UI/VST-UI-FOUNDRY/dist
COPY --from=foundry /build/foundry/node_modules ./VST-Foundry-UI/VST-UI-FOUNDRY/node_modules
COPY --from=foundry /build/foundry/package.json ./VST-Foundry-UI/VST-UI-FOUNDRY/package.json
# The RAG embedder (all-MiniLM-L6-v2) is baked into the image at a path that
# is INSIDE the image and is never shadowed by a volume mount. backend/rag.py
# sets HF_HUB_OFFLINE=1 before loading the embedder, so it can never be
# downloaded at runtime; the entrypoint seeds it into the mounted cache on
# first start instead.
ENV HF_HOME=/opt/hf-base
RUN /app/.venv/bin/python -c "from sentence_transformers import SentenceTransformer; SentenceTransformer('all-MiniLM-L6-v2')"
# The runtime Hugging Face cache lives on a volume so SA3 checkpoints and the
# T5Gemma conditioner survive container recreation. Mounting an empty volume
# here would hide anything baked at this path, which is exactly why the
# embedder above is baked to /opt/hf-base and copied in by the entrypoint.
ENV HF_HOME=/data/hf-cache
# The entrypoint seeds the mounted HF cache with the baked embedder when the
# cache does not contain it yet, then hands off to the CMD.
COPY <<'EOF' /usr/local/bin/thedaw-entrypoint.sh
#!/bin/sh
# This script seeds the mounted Hugging Face cache with the RAG embedder that
# was baked into the image at /opt/hf-base. backend/rag.py sets
# HF_HUB_OFFLINE=1 before loading all-MiniLM-L6-v2, so the model must already
# exist under HF_HOME when the first assistant query arrives. A freshly
# created volume mounted at HF_HOME starts empty and would otherwise hide the
# baked copy.
set -e
SEED_SRC="/opt/hf-base/hub"
SEED_DST="${HF_HOME:-/data/hf-cache}/hub"
MODEL_DIR="models--sentence-transformers--all-MiniLM-L6-v2"
if [ -d "${SEED_SRC}/${MODEL_DIR}" ] && [ ! -d "${SEED_DST}/${MODEL_DIR}" ]; then
mkdir -p "${SEED_DST}"
cp -r "${SEED_SRC}/${MODEL_DIR}" "${SEED_DST}/${MODEL_DIR}"
fi
exec "$@"
EOF
RUN chmod +x /usr/local/bin/thedaw-entrypoint.sh
# The image default enables the gated SPA mount in backend/server.py so a
# plain "docker run" serves the UI; local dev without this env var is
# unchanged.
ENV theDAW_SERVE_UI=1 \
PATH="/app/.venv/bin:${PATH}"
EXPOSE 8600
# Process init (PID 1 signal handling) is provided by "init: true" in
# docker-compose.yml, or by "docker run --init".
ENTRYPOINT ["/usr/local/bin/thedaw-entrypoint.sh"]
CMD ["python", "-m", "backend.run"]