# Runtime image for the whole app: the editor (admin), the static site server, # the project homepage and umtool, plus the tools they shell out to (yt-dlp, # ffmpeg, and a transcription backend). # # This is NOT one of the other two Dockerfiles and does not replace either: # Dockerfile.build — one hermetic container per SITE for the export build fan-out. # Dockerfile.test — the sharded playwright e2e route. # Both keep working exactly as they did; nothing here touches them. # # Three published targets, all built from the same app code: # runtime CPU whisper.cpp. The default. (docker-compose.yml) # runtime-vulkan parakeet.cpp on Vulkan. Any GPU with a Vulkan driver — # AMD/RADV, Intel, NVIDIA. (docker-compose.vulkan.yml) # runtime-cuda whisper.cpp on CUDA. NVIDIA only. (docker-compose.gpu.yml) # # Used by docker-compose.yml. See RUNNING_IN_DOCKER.md. # # --------------------------------------------------------------------------- # Why the whole repo ships instead of `output: "standalone"` # --------------------------------------------------------------------------- # editor/next.config.ts (and umtool/next.config.ts) explain it: the server does # runtime-dynamic fs reads the file tracer cannot statically bound — readdir over # a video dir, per-job logs, settings.json — so the .nft.json traces Next emits # are deliberately never consumed. A standalone bundle built from them would be # missing files nobody can enumerate ahead of time. So we ship the repo plus # node_modules, exactly as Dockerfile.test already does. Read those comments # before "optimising" this. # --------------------------------------------------------------------------- # Base images, and the one rule that ties them together # --------------------------------------------------------------------------- # GLIBC ONLY GOES FORWARD. A binary built against an older glibc runs on a newer # one; the reverse fails at load time with "GLIBC_2.xx not found". The workspace # (including native modules — lmdb, msgpackr-extract) is compiled ONCE in the # `build` stage and copied into every runtime, so: # # NODE_IMAGE must have the OLDEST glibc of anything it is copied into. # # bookworm is 2.36, trixie 2.41, ubuntu 24.04 2.39 — so building on bookworm and # running on any of them is safe, and moving NODE_IMAGE to trixie would silently # break the CUDA target. If you bump one of these, check that direction first. # # NODE'S MAJOR IS THE SECOND RULE, and it must be the same everywhere: the # native modules are compiled against the build stage's Node ABI, so every # runtime (NODE_IMAGE, RUNTIME_IMAGE here and in docker-compose.vulkan.yml, # NODE_MAJOR in runtime-cuda) carries the same major. 22, not 20: the wrangler # pinned in common/package.json refuses to start on anything older (its # engines floor), and every deploy runs it from this image. # common/publish/buildImage.test.ts holds all four to one major and that floor. ARG NODE_IMAGE=node:22-bookworm-slim # The runtime base, overridable per target: docker-compose.vulkan.yml builds with # trixie because the Vulkan stack needs it (see the parakeet stage below). ARG RUNTIME_IMAGE=node:22-bookworm-slim # ubuntu24.04, not 22.04: 22.04 is glibc 2.35, OLDER than the bookworm the # workspace is built on, and the native modules would not load. ARG CUDA_DEVEL_IMAGE=nvidia/cuda:12.6.3-devel-ubuntu24.04 ARG CUDA_RUNTIME_IMAGE=nvidia/cuda:12.6.3-runtime-ubuntu24.04 # whisper.cpp release to build. Pinned: this is a compiled dependency, and # "whatever master was that day" is not a thing you can reproduce later. ARG WHISPER_REF=v1.7.6 # parakeet.cpp, same reasoning. https://github.com/mudler/parakeet.cpp ARG PARAKEET_REF=v0.5.0 # Compile parallelism for the transcription backends. Defaults to every core; lower it when the # machine is shared or short on RAM (the CUDA build in particular runs one nvcc # per job and each is hungry). `--build-arg WHISPER_BUILD_JOBS=4`. ARG WHISPER_BUILD_JOBS= # Which CUDA architectures the GPU build targets. Turing through Hopper, plus # Pascal, so the image runs on more than the card it was built next to — at a # real cost in build time, since every CUDA translation unit is compiled once per # architecture. Narrow it (e.g. "86") when you are building for one known GPU. # # NOT "all-major", which is the obvious thing to write here and does not work: # CMake only understands that value from 3.23, the CUDA base image ships CMake # 3.22, and the failure is not a cmake error but # nvcc fatal : Unsupported gpu architecture 'compute_' # i.e. the literal string reaching nvcc as if it were an architecture. ARG CUDA_ARCHITECTURES="61;70;75;80;86;89;90" # ggml compiles the CPU backend with -march=native by default, which bakes THIS # machine's instruction set into the image. That is right for the normal case — # you build on the box you run on, and it is measurably faster — and wrong the # moment the image moves: an older CPU dies with SIGILL on the first tensor op. # Set GGML_NATIVE=OFF when building an image you intend to publish or run # elsewhere. ARG GGML_NATIVE=ON # --------------------------------------------------------------------------- # whisper-cpu — build whisper-cli, the default transcription backend. # # Statically linked (BUILD_SHARED_LIBS=OFF) so the runtime stage copies ONE file # and inherits no libwhisper/libggml search-path problems. Models are NOT baked: # they are 142 MB (base.en) to 3 GB (large-v3), they change independently of the # code, and they belong in the `models` volume. docker/entrypoint.sh fetches one # on first boot. # --------------------------------------------------------------------------- FROM debian:bookworm-slim AS whisper-cpu ARG WHISPER_REF ARG WHISPER_BUILD_JOBS ARG GGML_NATIVE RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential cmake git ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 --branch "${WHISPER_REF}" \ https://github.com/ggml-org/whisper.cpp /src/whisper.cpp RUN cmake -S /src/whisper.cpp -B /src/whisper.cpp/build \ -DCMAKE_BUILD_TYPE=Release \ -DBUILD_SHARED_LIBS=OFF \ -DGGML_NATIVE="${GGML_NATIVE}" \ -DWHISPER_BUILD_TESTS=OFF \ -DWHISPER_BUILD_SERVER=OFF \ && cmake --build /src/whisper.cpp/build --config Release \ -j "${WHISPER_BUILD_JOBS:-$(nproc)}" --target whisper-cli \ && cp /src/whisper.cpp/build/bin/whisper-cli /usr/local/bin/whisper-cli \ && strip /usr/local/bin/whisper-cli # --------------------------------------------------------------------------- # parakeet-vulkan — build parakeet-cli with the ggml Vulkan backend. # # On TRIXIE, and not by preference: ggml-vulkan uses vk::LayerSettingEXT, which # arrived in the Vulkan headers around 1.3.272. Bookworm ships 1.3.239, so the # build dies in ggml-vulkan.cpp with "'LayerSettingEXT' is not a member of 'vk'". # Trixie has 1.4.309 and the matching glslc/glslangValidator. That is also why # the Vulkan RUNTIME is trixie — this binary links trixie's libstdc++/glibc. # # Vulkan rather than a vendor SDK on purpose: one build runs on AMD (RADV), # Intel and NVIDIA alike, and needs no proprietary runtime in the image — just # the driver's ICD, which comes from mesa-vulkan-drivers in the runtime stage. # # The app already knows how to drive this and none of it is new app code: # common/lib/transcriptionApps.ts registers a `parakeet` app whose binary is # scripts/parakeet-stitch.mjs (the overlapping-window wrapper), which shells out # to PARAKEET_CLI and passes the worker's device through as PARAKEET_DEVICE. # # Submodules are NOT optional — ggml lives in third_party/ggml, and a plain # --depth 1 clone produces a tree that fails to configure. # --------------------------------------------------------------------------- FROM debian:trixie-slim AS parakeet-vulkan ARG PARAKEET_REF ARG WHISPER_BUILD_JOBS ARG GGML_NATIVE # The Vulkan backend needs more than the loader: ggml-vulkan compiles its # compute shaders at BUILD time, so it wants glslc AND glslangValidator # (glslang-tools), and it find_package()s SPIRV-Headers — without which cmake # stops at "Could not find a package configuration file provided by # SPIRV-Headers", several lines below a cheerful "Found Vulkan". RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential cmake git ca-certificates \ libvulkan-dev glslc glslang-tools spirv-headers spirv-tools \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 --branch "${PARAKEET_REF}" --recurse-submodules \ https://github.com/mudler/parakeet.cpp /src/parakeet.cpp # PARAKEET_GGML_VULKAN, *not* GGML_VULKAN — and the difference is silent. # parakeet.cpp's CMakeLists does # set(GGML_VULKAN ${PARAKEET_GGML_VULKAN} CACHE BOOL "" FORCE) # so passing GGML_VULKAN=ON is not ignored, it is OVERWRITTEN with OFF. The build # then succeeds, prints only "Including CPU backend", ships no # libggml-vulkan.so, and every transcription runs on the CPU with nothing # anywhere saying why. Measured: that is exactly what the first build of this # image did. # Three steps, not one `&&` chain: chaining lets a cmake CONFIGURE error fall # through to the guard's message, which then reports the wrong problem. RUN set -eux; \ cmake -S /src/parakeet.cpp -B /src/parakeet.cpp/build \ -DCMAKE_BUILD_TYPE=Release \ -DPARAKEET_GGML_VULKAN=ON \ -DGGML_NATIVE="${GGML_NATIVE}" \ -DPARAKEET_BUILD_TESTS=OFF \ -DPARAKEET_BUILD_SERVER=OFF; \ cmake --build /src/parakeet.cpp/build --config Release \ -j "${WHISPER_BUILD_JOBS:-$(nproc)}"; \ if [ -z "$(find /src/parakeet.cpp/build -name 'libggml-vulkan.so*' -print -quit)" ]; then \ echo "FATAL: this build produced no Vulkan backend — see the comment above" >&2; \ exit 1; \ fi # ggml builds each backend as its own shared object, nested a level deeper than # the rest (third_party/ggml/src/ggml-vulkan/). A COPY glob does not recurse, so # collect everything into one directory here — a missing libggml-vulkan.so is # what silently turns a "GPU image" into a CPU one. RUN set -eux; \ mkdir -p /out/lib; \ cp "$(find /src/parakeet.cpp/build -name parakeet-cli -type f -perm -u+x | head -1)" \ /out/parakeet-cli; \ find /src/parakeet.cpp/build -name '*.so*' -type f -exec cp -a {} /out/lib/ \;; \ ls -1 /out/lib # --------------------------------------------------------------------------- # whisper-cuda — the same binary with CUDA offload, for the GPU overlay. # # Only ever built when something asks for the runtime-cuda target (BuildKit # builds a target's graph, not the file), so an unreachable or wrong CUDA base # never blocks the default `docker compose build`. # --------------------------------------------------------------------------- FROM ${CUDA_DEVEL_IMAGE} AS whisper-cuda ARG WHISPER_REF ARG WHISPER_BUILD_JOBS ARG CUDA_ARCHITECTURES ARG GGML_NATIVE RUN apt-get update \ && apt-get install -y --no-install-recommends \ build-essential cmake git ca-certificates \ && rm -rf /var/lib/apt/lists/* RUN git clone --depth 1 --branch "${WHISPER_REF}" \ https://github.com/ggml-org/whisper.cpp /src/whisper.cpp # Shared libs here: the CUDA backend links against the driver stack anyway, so a # static build buys nothing, and the CUDA_ARCHITECTURES list keeps the image # usable on more than the card it was built next to. # The stubs directory is not optional. ggml's CUDA backend calls the DRIVER API # (cuMemCreate, cuMemMap, … for its virtual-memory allocator), which lives in # libcuda.so — shipped by the NVIDIA DRIVER, not by the toolkit, and therefore # absent from a build container. The toolkit provides a link-time stub for # exactly this case; without it the compile succeeds and the final link dies with # a wall of "undefined reference to `cuMemCreate'". The real libcuda.so.1 is # injected at run time by the NVIDIA container runtime. # # BOTH halves are needed: -L points at the stub directory, -lcuda actually links # it. Adding only the -L changes nothing and fails identically — measured. RUN cmake -S /src/whisper.cpp -B /src/whisper.cpp/build \ -DCMAKE_BUILD_TYPE=Release \ -DGGML_CUDA=ON \ -DGGML_NATIVE="${GGML_NATIVE}" \ -DCMAKE_CUDA_ARCHITECTURES="${CUDA_ARCHITECTURES}" \ -DCMAKE_EXE_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs -lcuda" \ -DCMAKE_SHARED_LINKER_FLAGS="-L/usr/local/cuda/lib64/stubs -lcuda" \ -DWHISPER_BUILD_TESTS=OFF \ -DWHISPER_BUILD_SERVER=OFF \ && cmake --build /src/whisper.cpp/build --config Release \ -j "${WHISPER_BUILD_JOBS:-$(nproc)}" --target whisper-cli # Collect the binary and EVERY shared object the build produced into one dir. # A COPY glob does not recurse, and the CUDA backend's library is nested a level # deeper than the rest (ggml/src/ggml-cuda/) — copying "the .so files in # ggml/src" silently misses the one that makes this variant a GPU build at all. RUN mkdir -p /out/lib \ && cp /src/whisper.cpp/build/bin/whisper-cli /out/whisper-cli \ && find /src/whisper.cpp/build -name '*.so*' -type f -exec cp -a {} /out/lib/ \; # --------------------------------------------------------------------------- # deps — install the workspace. # --------------------------------------------------------------------------- FROM ${NODE_IMAGE} AS deps # pnpm as a plain global binary, NOT corepack — same reason Dockerfile.build # gives: there is no `packageManager` pin in package.json, so corepack would try # to fetch pnpm from the registry at run time. RUN npm install -g pnpm@9.15.4 WORKDIR /repo # Manifests first, for layer caching: this layer rebuilds only when a package.json # or the lockfile moves. ALL SEVEN workspace packages listed in pnpm-workspace.yaml # must be copied — a --frozen-lockfile install of a workspace with a missing member # fails outright. (Dockerfile.build copies two because it only ever builds the # export; that shortcut does not transfer here.) COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ COPY common/package.json common/package.json COPY editor/package.json editor/package.json COPY export/package.json export/package.json COPY homepage/package.json homepage/package.json COPY mcp/package.json mcp/package.json COPY umtool/report-to-video/package.json umtool/report-to-video/package.json COPY umtool/package.json umtool/package.json # `allowBuilds` / `onlyBuiltDependencies` in pnpm-workspace.yaml rebuild the # native modules (lmdb, msgpackr-extract, esbuild) for THIS image's arch. RUN pnpm install --frozen-lockfile # --------------------------------------------------------------------------- # build — compile the two server apps and the homepage. # --------------------------------------------------------------------------- FROM deps AS build COPY . . # There is no corpus at image-build time and there must not be: it is hundreds of # GB, it is a separate git repo, and .dockerignore excludes it. Point the build # at an empty one so anything that stats a corpus path finds an empty archive # rather than a path that cannot exist. ENV TRANSCRIPTS_DIR=/tmp/empty-corpus \ NEXT_TELEMETRY_DISABLED=1 RUN mkdir -p /tmp/empty-corpus/channels /tmp/empty-corpus/sites RUN pnpm --filter editor exec next build RUN pnpm --filter umtool exec next build # The homepage is the project's own marketing/docs site and is corpus-independent # — build:nodata skips the compose+index data phase that needs one. The EXPORT # site is deliberately NOT built here: it is a static render OF a corpus, so # there is nothing to render until the operator has one. docker/publish-site.sh # builds it at run time and publishes it into the shared volume the `site` # service serves. RUN pnpm --filter homepage run build:nodata # --------------------------------------------------------------------------- # runtime-base — everything the app needs EXCEPT a transcription backend. # # Shared by `runtime` and `runtime-vulkan` — the two differ only in RUNTIME_IMAGE # (bookworm vs trixie) and which transcription backend they copy in. # runtime-cuda cannot derive from it (it starts from an NVIDIA base image, not # the node one) and so repeats it — that duplication is the price of a different # base, not an oversight. # --------------------------------------------------------------------------- FROM ${RUNTIME_IMAGE} AS runtime-base # ffmpeg/ffprobe: audio extraction and the download-time duration guard. # zip/tar/xz/gzip: the export build's archive formats. # rsync: the saved-video backup mirror. # curl: model fetch + the compose healthcheck. # git: the "cut release" flow commits, and yt-dlp resolves some extractors better # with it present. # procps: `ps`, so "is the transcriber actually running in there?" is answerable # from a `docker compose exec` shell. debian:slim ships without it. # aria2: archive.org files over BitTorrent (archive.org as the web seed), then # seeded for a while; without it they are downloaded directly. # python3 + yt-dlp's optional modules (certifi, brotli, websockets, mutagen, # pycryptodome, requests): NOT for the yt-dlp below, which is a standalone # binary with its own python — for /usr/local/bin/yt-dlp-from-source, which # runs a yt-dlp SOURCE tree mounted at run time (docker-compose.ytdlp.yml). # pipx (+ python3-venv, which it needs to make a venv): installs git-filter-repo # below, which the homepage's source mirror runs. RUN apt-get update \ && apt-get install -y --no-install-recommends \ ffmpeg aria2 zip unzip tar xz-utils gzip rsync curl ca-certificates git procps \ python3 python3-venv pipx python3-certifi python3-brotli python3-websockets \ python3-mutagen python3-pycryptodome python3-requests \ && rm -rf /var/lib/apt/lists/* # git-filter-repo, PINNED, for `archilyzer source publish` (the homepage's # /source mirror). Installed, not left to `pipx run`, so a container publishes # with no network fetch at build time. The version is the one # common/publish/source.ts names in FILTER_REPO_PIPX_SPEC — a drift test # (common/publish/buildImage.test.ts) holds every `pipx install` here to it. # Into /opt + /usr/local/bin, not root's home: on PATH for every process. RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install git-filter-repo==2.47.0 \ && git filter-repo --version # yt-dlp as the standalone release binary (it bundles its own python), installed # writable so `yt-dlp -U` works — see YTDLP_AUTO_UPDATE in docker/entrypoint.sh. # A pinned yt-dlp goes stale fast, and a stale yt-dlp is the single most common # reason downloads start failing. It is THE IMAGE'S yt-dlp # (ARCHILYZER_IMAGE_YTDLP below); YTDLP_BIN may name another one at run time — # RUNNING_IN_DOCKER.md, "Substituting yt-dlp". ARG TARGETARCH=amd64 RUN set -eux; \ case "${TARGETARCH}" in \ amd64) asset=yt-dlp_linux ;; \ arm64) asset=yt-dlp_linux_aarch64 ;; \ *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac; \ curl -fsSL "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${asset}" \ -o /usr/local/bin/yt-dlp; \ chmod 0755 /usr/local/bin/yt-dlp; \ /usr/local/bin/yt-dlp --version # A JavaScript runtime for yt-dlp. # # Not optional any more, and easy to miss because it degrades rather than fails: # without one, yt-dlp prints "No supported JavaScript runtime could be found ... # some formats may be missing" and carries on with a reduced format list. deno is # the one it enables by default. Measured inside this image before it was added — # every YouTube extraction warned. ARG DENO_VERSION=v2.9.5 RUN set -eux; \ case "${TARGETARCH}" in \ amd64) deno_asset=deno-x86_64-unknown-linux-gnu.zip ;; \ arm64) deno_asset=deno-aarch64-unknown-linux-gnu.zip ;; \ *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac; \ curl -fsSL "https://github.com/denoland/deno/releases/download/${DENO_VERSION}/${deno_asset}" \ -o /tmp/deno.zip; \ unzip -q /tmp/deno.zip -d /usr/local/bin; \ rm /tmp/deno.zip; \ chmod 0755 /usr/local/bin/deno; \ /usr/local/bin/deno --version RUN npm install -g pnpm@9.15.4 # WORKDIR matters: findMonorepoRoot() (common/lib/paths.ts) walks UP from cwd # looking for pnpm-workspace.yaml, and everything under it — the export staging # dirs, the changelog paths, chart-templates.json — hangs off what it finds. WORKDIR /repo COPY --from=build /repo /repo # The yt-dlp substitution hook: a wrapper that runs a yt-dlp SOURCE tree # mounted at YTDLP_SOURCE_DIR (default /opt/yt-dlp-src) with the python above. # Selected by YTDLP_BIN=/usr/local/bin/yt-dlp-from-source; see # docker-compose.ytdlp.yml. RUN ln -s /repo/docker/yt-dlp-from-source.sh /usr/local/bin/yt-dlp-from-source # Data lives in volumes, never in the image. See docker-compose.yml. # YTDLP_BIN is the one the app runs; ARCHILYZER_IMAGE_YTDLP is the one this # image ships, so the entrypoint and `archilyzer doctor` can say "override". ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ TRANSCRIPTS_DIR=/data/transcripts \ SETTINGS_FILE=/data/config/settings.json \ YTDLP_BIN=/usr/local/bin/yt-dlp \ ARCHILYZER_IMAGE_YTDLP=/usr/local/bin/yt-dlp \ EXPORT_INDEX_DIR=/data/builds/.export-index \ EXPORT_BUILDS_DIR=/data/builds/.export-builds \ ARCHILYZER_SITE_OUT=/data/builds/site RUN mkdir -p /data/transcripts /data/config /data/models /data/builds ENTRYPOINT ["/repo/docker/entrypoint.sh"] CMD ["editor"] # --------------------------------------------------------------------------- # runtime — the published default target. CPU whisper.cpp. # --------------------------------------------------------------------------- FROM runtime-base AS runtime COPY --from=whisper-cpu /usr/local/bin/whisper-cli /usr/local/bin/whisper-cli ENV ARCHILYZER_TRANSCRIBER=whisper-cpp \ WHISPER_BIN=/usr/local/bin/whisper-cli \ WHISPER_MODEL=/data/models/ggml-base.en.bin # Which commit and branch this image was built from — the publish stamps' # `commit`/`branch` where there is no .git (.dockerignore keeps it out). Last, # so a new commit re-runs one ENV layer and nothing else. Passed by compose from # the shell: ARCHILYZER_COMMIT=$(git rev-parse HEAD) docker compose build. ARG ARCHILYZER_COMMIT= ARG ARCHILYZER_BRANCH= ENV ARCHILYZER_COMMIT=${ARCHILYZER_COMMIT} \ ARCHILYZER_BRANCH=${ARCHILYZER_BRANCH} # --------------------------------------------------------------------------- # runtime-vulkan — parakeet.cpp on any Vulkan GPU. docker-compose.vulkan.yml. # # Ships whisper-cli too: the GPU is for parakeet, but a CPU whisper worker is a # useful thing to have in the same image — to compare against, or for when the # GPU is busy. # --------------------------------------------------------------------------- FROM runtime-base AS runtime-vulkan # libvulkan1 is the loader; mesa-vulkan-drivers supplies the ICDs that actually # talk to the hardware (RADV for AMD, ANV for Intel). vulkan-tools is here so # `vulkaninfo` can answer "does the container see the GPU at all?", which is the # first question every time this does not work. RUN apt-get update \ && apt-get install -y --no-install-recommends \ libvulkan1 mesa-vulkan-drivers vulkan-tools \ && rm -rf /var/lib/apt/lists/* COPY --from=parakeet-vulkan /out/parakeet-cli /usr/local/bin/parakeet-cli COPY --from=parakeet-vulkan /out/lib/ /usr/local/lib/ COPY --from=whisper-cpu /usr/local/bin/whisper-cli /usr/local/bin/whisper-cli RUN ldconfig ENV ARCHILYZER_TRANSCRIBER=parakeet \ PARAKEET_CLI=/usr/local/bin/parakeet-cli \ PARAKEET_MODEL=/data/models/tdt_ctc-110m-q8_0.gguf \ WHISPER_BIN=/usr/local/bin/whisper-cli \ WHISPER_MODEL=/data/models/ggml-base.en.bin # Which commit and branch this image was built from — the publish stamps' # `commit`/`branch` where there is no .git (.dockerignore keeps it out). Last, # so a new commit re-runs one ENV layer and nothing else. Passed by compose from # the shell: ARCHILYZER_COMMIT=$(git rev-parse HEAD) docker compose build. ARG ARCHILYZER_COMMIT= ARG ARCHILYZER_BRANCH= ENV ARCHILYZER_COMMIT=${ARCHILYZER_COMMIT} \ ARCHILYZER_BRANCH=${ARCHILYZER_BRANCH} # --------------------------------------------------------------------------- # runtime-cuda — whisper.cpp on CUDA. NVIDIA only. docker-compose.gpu.yml. # --------------------------------------------------------------------------- FROM ${CUDA_RUNTIME_IMAGE} AS runtime-cuda ARG NODE_MAJOR=22 # The same python + pipx + git-filter-repo as runtime-base (read its comments). RUN apt-get update \ && apt-get install -y --no-install-recommends \ ffmpeg aria2 zip unzip tar xz-utils gzip rsync curl ca-certificates git gnupg procps \ python3 python3-venv pipx python3-certifi python3-brotli python3-websockets \ python3-mutagen python3-pycryptodome python3-requests \ && curl -fsSL "https://deb.nodesource.com/setup_${NODE_MAJOR}.x" | bash - \ && apt-get install -y --no-install-recommends nodejs \ && rm -rf /var/lib/apt/lists/* RUN PIPX_HOME=/opt/pipx PIPX_BIN_DIR=/usr/local/bin pipx install git-filter-repo==2.47.0 \ && git filter-repo --version ARG TARGETARCH=amd64 RUN set -eux; \ case "${TARGETARCH}" in \ amd64) asset=yt-dlp_linux ;; \ arm64) asset=yt-dlp_linux_aarch64 ;; \ *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac; \ curl -fsSL "https://github.com/yt-dlp/yt-dlp/releases/latest/download/${asset}" \ -o /usr/local/bin/yt-dlp; \ chmod 0755 /usr/local/bin/yt-dlp; \ /usr/local/bin/yt-dlp --version # A JavaScript runtime for yt-dlp. # # Not optional any more, and easy to miss because it degrades rather than fails: # without one, yt-dlp prints "No supported JavaScript runtime could be found ... # some formats may be missing" and carries on with a reduced format list. deno is # the one it enables by default. Measured inside this image before it was added — # every YouTube extraction warned. ARG DENO_VERSION=v2.9.5 RUN set -eux; \ case "${TARGETARCH}" in \ amd64) deno_asset=deno-x86_64-unknown-linux-gnu.zip ;; \ arm64) deno_asset=deno-aarch64-unknown-linux-gnu.zip ;; \ *) echo "unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac; \ curl -fsSL "https://github.com/denoland/deno/releases/download/${DENO_VERSION}/${deno_asset}" \ -o /tmp/deno.zip; \ unzip -q /tmp/deno.zip -d /usr/local/bin; \ rm /tmp/deno.zip; \ chmod 0755 /usr/local/bin/deno; \ /usr/local/bin/deno --version RUN npm install -g pnpm@9.15.4 # The CUDA whisper build is dynamically linked, so its ggml/whisper libraries # come along with it. COPY --from=whisper-cuda /out/whisper-cli /usr/local/bin/whisper-cli COPY --from=whisper-cuda /out/lib/ /usr/local/lib/ RUN ldconfig WORKDIR /repo COPY --from=build /repo /repo RUN ln -s /repo/docker/yt-dlp-from-source.sh /usr/local/bin/yt-dlp-from-source ENV NODE_ENV=production \ NEXT_TELEMETRY_DISABLED=1 \ TRANSCRIPTS_DIR=/data/transcripts \ SETTINGS_FILE=/data/config/settings.json \ ARCHILYZER_TRANSCRIBER=whisper-cpp \ WHISPER_BIN=/usr/local/bin/whisper-cli \ WHISPER_MODEL=/data/models/ggml-base.en.bin \ YTDLP_BIN=/usr/local/bin/yt-dlp \ ARCHILYZER_IMAGE_YTDLP=/usr/local/bin/yt-dlp \ EXPORT_INDEX_DIR=/data/builds/.export-index \ EXPORT_BUILDS_DIR=/data/builds/.export-builds \ ARCHILYZER_SITE_OUT=/data/builds/site RUN mkdir -p /data/transcripts /data/config /data/models /data/builds ENTRYPOINT ["/repo/docker/entrypoint.sh"] CMD ["editor"] # Which commit and branch this image was built from — the publish stamps' # `commit`/`branch` where there is no .git (.dockerignore keeps it out). Last, # so a new commit re-runs one ENV layer and nothing else. Passed by compose from # the shell: ARCHILYZER_COMMIT=$(git rev-parse HEAD) docker compose build. ARG ARCHILYZER_COMMIT= ARG ARCHILYZER_BRANCH= ENV ARCHILYZER_COMMIT=${ARCHILYZER_COMMIT} \ ARCHILYZER_BRANCH=${ARCHILYZER_BRANCH}