TTS-Arena-V2 / Dockerfile
GitHub Actions
Deploy from github.com/TTS-AGI/TTS-Arena@f49eded
374bcb6
Raw
History Blame Contribute Delete
5.53 kB
# syntax=docker/dockerfile:1
#
# Combined image for the Hugging Face Space. One container runs two processes
# under supervisord:
# - router (:8080, internal)
# - web (:7860, the port HF exposes)
#
# The database is Postgres on a VPS (DATABASE_URL secret, over a Cloudflare
# tunnel). Generated audio is logged to /audio (a persistent bucket).
FROM oven/bun:1.1.42-debian AS base
WORKDIR /app
# ── deps ──
FROM base AS deps
# git + ca-certificates for cloning the private providers over HTTPS; build
# toolchain for native modules (better-sqlite3) when no prebuilt binary matches.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
git ca-certificates python3 make g++ \
&& rm -rf /var/lib/apt/lists/*
COPY package.json bun.lock ./
COPY apps/web/package.json apps/web/package.json
COPY apps/router/package.json apps/router/package.json
# Copy every workspace manifest (providers grow over time, so copy the tree's
# package.json files rather than enumerating each).
COPY packages/ packages/
# Pull the private providers straight into the workspace using a build-time
# secret (a read-only GH token Space secret). This keeps the private source out
# of BOTH the Space repo and the build context β€” the Space can be public without
# exposing it. The token is mounted only for this layer and never persisted.
# PRIVATE_PROVIDERS_REPO defaults to the known repo; override via build arg.
# If the secret is absent the build still succeeds (no private providers).
ARG PRIVATE_PROVIDERS_REPO=TTS-AGI/private-providers
# Cache-buster: the `git clone` below is a fixed command, so Docker would reuse
# a stale layer and silently keep an OLD snapshot of the private repo forever
# (new private providers never appear). The deploy passes the private repo's
# current HEAD SHA here; when it changes, this layer's cache key changes and the
# clone re-runs. Defaults to "dev" for local builds.
ARG PRIVATE_PROVIDERS_REF=6ce6680683f6fc856df478b86596389cdcb916d9
RUN --mount=type=secret,id=PRIVATE_PROVIDERS_TOKEN,mode=0444,required=false \
echo "private providers ref: ${PRIVATE_PROVIDERS_REF}"; \
if [ -s /run/secrets/PRIVATE_PROVIDERS_TOKEN ]; then \
tok="$(cat /run/secrets/PRIVATE_PROVIDERS_TOKEN)"; \
git clone --depth 1 \
"https://x-access-token:${tok}@github.com/${PRIVATE_PROVIDERS_REPO}.git" \
packages/providers/_private \
&& rm -rf packages/providers/_private/.git \
&& echo "cloned private providers into workspace" \
|| { echo "ERROR: token present but private provider clone failed" >&2; exit 1; }; \
else \
echo "no PRIVATE_PROVIDERS_TOKEN secret; building without private providers"; \
fi; \
# Ensure the dir always exists so later COPY --from=deps never fails. A stub
# package.json (private, not a workspace match) keeps it inert when empty.
if [ ! -e packages/providers/_private/package.json ]; then \
mkdir -p packages/providers/_private; \
echo '{"name":"@ttsa-private/_placeholder","private":true,"version":"0.0.0"}' \
> packages/providers/_private/package.json; \
fi
# Install after the private package is in place so bun links it as a workspace
# (node_modules/@ttsa-private/providers), resolvable by the router.
RUN bun install
# ── build the web app ──
FROM base AS build
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN cd apps/web && bun run build
# ── runtime ──
FROM base AS runtime
ENV NODE_ENV=production
# System deps: supervisord, ffmpeg (router audio normalization), ca-certificates
# (verify TLS to the Postgres host). The app connects directly to the VPS
# Postgres over a TLS connection (DATABASE_URL) β€” no tunnel client in the Space.
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
supervisor ffmpeg ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# App: built web + full source for the router (run from source via bun).
COPY --from=deps /app/node_modules ./node_modules
COPY --from=build /app/apps/web/.next/standalone ./standalone
COPY --from=build /app/apps/web/.next/static ./standalone/apps/web/.next/static
COPY --from=build /app/apps/web/public ./standalone/apps/web/public
COPY apps/router ./apps/router
COPY apps/web/drizzle ./apps/web/drizzle
COPY apps/web/drizzle.config.ts ./apps/web/drizzle.config.ts
COPY apps/web/src/server/db ./apps/web/src/server/db
# Public packages from the repo, then the private providers cloned in `deps`
# (kept out of the repo + build context). The node_modules copied from `deps`
# already contains bun's workspace symlink node_modules/@ttsa-private/providers
# -> packages/providers/_private, so the router resolves it via PROVIDER_PLUGINS.
COPY packages ./packages
COPY --from=deps /app/packages/providers/_private ./packages/providers/_private
# The prompt corpus (Git LFS). Baked into the image so it ships with the deploy
# (the source URL is not guaranteed to stay up). PROMPTS_FILE points the app at
# it; the loader memory-maps it via a line-offset index, never loading it whole.
COPY apps/web/data/combined_prompts.txt /app/data/combined_prompts.txt
ENV PROMPTS_FILE=/app/data/combined_prompts.txt
# Entrypoint + supervisor config.
COPY deploy/space/entrypoint.sh /entrypoint.sh
COPY deploy/space/supervisord.conf /etc/supervisor/conf.d/tts-arena.conf
RUN chmod +x /entrypoint.sh
# HF mounts /data and /audio as persistent buckets; create mountpoints.
RUN mkdir -p /data /audio
EXPOSE 7860
ENTRYPOINT ["/entrypoint.sh"]