# ─── OpenBao CLI, copied from upstream image (multi-arch automatic) ─── # Update the tag here to adopt a newer OpenBao. Binary is statically linked, # safe to drop into the Node alpine image as-is. FROM openbao/openbao:2.5.3 AS bao-src FROM node:20-alpine WORKDIR /app # ffmpeg: audio conversion for AWS Transcribe (WebM → PCM) # curl: download Whisper models for browser-based transcription # jq: JSON parsing for the entrypoint's OpenBao secret-fetch step RUN apk add --no-cache ffmpeg curl jq # Pull the bao CLI out of the upstream image — matches host arch because # buildx pulls the right manifest-list variant per build. COPY --from=bao-src /bin/bao /usr/local/bin/bao RUN /usr/local/bin/bao version COPY package.json ./ # argon2 compiles native code via node-gyp — needs python3/make/g++ at build time RUN apk add --no-cache --virtual .build-deps python3 make g++ \ && npm install --omit=dev \ && apk del .build-deps COPY . . # Ensure the entrypoint is executable regardless of host file permissions RUN chmod +x /app/docker-entrypoint.sh RUN mkdir -p /app/data/logs # Download Browser Whisper (COMPLETE self-hosting - zero CDN dependencies) # Library + Models all bundled and served from our server RUN mkdir -p /app/public/models/Xenova/whisper-tiny.en/onnx && \ cd /app/public/models && \ echo "Downloading transformers.js library (worker-compatible build)..." && \ curl -sL -o transformers.min.js https://cdn.jsdelivr.net/npm/@xenova/transformers@2.0.0/dist/transformers.min.js && \ cd Xenova/whisper-tiny.en && \ echo "Downloading Whisper model files..." && \ curl -sL -o config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/config.json && \ curl -sL -o tokenizer.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/tokenizer.json && \ curl -sL -o preprocessor_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/preprocessor_config.json && \ curl -sL -o generation_config.json https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/generation_config.json && \ curl -sL -o onnx/encoder_model_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/encoder_model_quantized.onnx && \ curl -sL -o onnx/decoder_model_merged_quantized.onnx https://huggingface.co/Xenova/whisper-tiny.en/resolve/main/onnx/decoder_model_merged_quantized.onnx && \ echo "✅ Browser Whisper: 100% self-hosted (library: 760KB, models: 42MB)" EXPOSE 3000 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s \ CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1 # Entrypoint wrapper handles optional OpenBao secret fetch before exec'ing CMD. # See docker-entrypoint.sh for the logic — it is a no-op if OPENBAO_ADDR is # unset, so legacy .env-only deployments continue to work unchanged. ENTRYPOINT ["/app/docker-entrypoint.sh"] CMD ["node", "server.js"]