Dockerfile 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. # docker/Dockerfile
  2. # IMPORTANT: The docker images do not contain the checkpoints. You need to mount the checkpoints to the container.
  3. # Build the image:
  4. # docker build \
  5. # --platform linux/amd64 \
  6. # -f docker/Dockerfile \
  7. # --build-arg BACKEND=[cuda, cpu] \
  8. # --target [webui, server] \
  9. # -t fish-speech-[webui, server]:[cuda, cpu] .
  10. # e.g. for building the webui:
  11. # docker build \
  12. # --platform linux/amd64 \
  13. # -f docker/Dockerfile \
  14. # --build-arg BACKEND=cuda \
  15. # --target webui \
  16. # -t fish-speech-webui:cuda .
  17. # e.g. for building the server:
  18. # docker build \
  19. # --platform linux/amd64 \
  20. # -f docker/Dockerfile \
  21. # --build-arg BACKEND=cuda \
  22. # --target server \
  23. # -t fish-speech-server:cuda .
  24. # Multi-platform build:
  25. # docker buildx build \
  26. # --platform linux/amd64,linux/arm64 \
  27. # -f docker/Dockerfile \
  28. # --build-arg BACKEND=cpu \
  29. # --target webui \
  30. # -t fish-speech-webui:cpu .
  31. # Running the image interactively:
  32. # docker run \
  33. # --gpus all \
  34. # -v /path/to/fish-speech/checkpoints:/app/checkpoints \
  35. # -e COMPILE=1 \ ... or -e COMPILE=0 \
  36. # -it fish-speech-[webui, server]:[cuda, cpu]
  37. # E.g. running the webui:
  38. # docker run \
  39. # --gpus all \
  40. # -v ./checkpoints:/app/checkpoints \
  41. # -e COMPILE=1 \
  42. # -p 7860:7860 \
  43. # fish-speech-webui:cuda
  44. # E.g. running the server:
  45. # docker run \
  46. # --gpus all \
  47. # -v ./checkpoints:/app/checkpoints \
  48. # -p 8080:8080 \
  49. # -it fish-speech-server:cuda
  50. # Select the specific cuda version (see https://hub.docker.com/r/nvidia/cuda/)
  51. ARG CUDA_VER=12.9.0
  52. # Adapt the uv extra to fit the cuda version (one of [cu126, cu128, cu129])
  53. ARG UV_EXTRA=cu129
  54. ARG BACKEND=cuda
  55. ARG UBUNTU_VER=24.04
  56. ARG PY_VER=3.12
  57. ARG UV_VERSION=0.8.15
  58. # Create non-root user early for security
  59. ARG USERNAME=fish
  60. ARG USER_UID=1000
  61. ARG USER_GID=1000
  62. ##############################################################
  63. # Base stage per backend
  64. ##############################################################
  65. # --- CUDA (x86_64) ---
  66. FROM nvidia/cuda:${CUDA_VER}-cudnn-runtime-ubuntu${UBUNTU_VER} AS base-cuda
  67. ENV DEBIAN_FRONTEND=noninteractive
  68. # Install system dependencies in a single layer with cleanup
  69. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
  70. --mount=type=cache,target=/var/lib/apt,sharing=locked \
  71. set -eux \
  72. && rm -f /etc/apt/apt.conf.d/docker-clean \
  73. && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
  74. && apt-get update \
  75. && apt-get install -y --no-install-recommends \
  76. python3-pip \
  77. python3-dev \
  78. git \
  79. ca-certificates \
  80. curl \
  81. ninja-build \
  82. && apt-get clean \
  83. && rm -rf /var/lib/apt/lists/*
  84. # --- CPU-only (portable x86_64) ---
  85. FROM python:${PY_VER}-slim AS base-cpu
  86. ENV UV_EXTRA=cpu
  87. # Install system dependencies in a single layer with cleanup
  88. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
  89. --mount=type=cache,target=/var/lib/apt,sharing=locked \
  90. set -eux \
  91. && rm -f /etc/apt/apt.conf.d/docker-clean \
  92. && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
  93. && apt-get update \
  94. && apt-get install -y --no-install-recommends \
  95. git \
  96. ca-certificates \
  97. curl \
  98. && apt-get clean \
  99. && rm -rf /var/lib/apt/lists/*
  100. ##############################################################
  101. # UV stage
  102. ##############################################################
  103. ARG UV_VERSION
  104. FROM ghcr.io/astral-sh/uv:${UV_VERSION} AS uv-bin
  105. ##############################################################
  106. # Shared app base stage
  107. ##############################################################
  108. FROM base-${BACKEND} AS app-base
  109. ARG PY_VER
  110. ARG BACKEND
  111. ARG USERNAME
  112. ARG USER_UID
  113. ARG USER_GID
  114. ARG UV_VERSION
  115. ARG UV_EXTRA
  116. ENV BACKEND=${BACKEND} \
  117. DEBIAN_FRONTEND=noninteractive \
  118. PYTHONDONTWRITEBYTECODE=1 \
  119. PYTHONUNBUFFERED=1 \
  120. UV_HTTP_TIMEOUT=3600 \
  121. UV_INDEX_URL=http://mirrors.aliyun.com/pypi/simple/ \
  122. UV_DEFAULT_INDEX=http://mirrors.aliyun.com/pypi/simple/ \
  123. PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True
  124. # System dependencies for audio processing
  125. ARG DEPENDENCIES=" \
  126. libsox-dev \
  127. build-essential \
  128. cmake \
  129. libasound-dev \
  130. portaudio19-dev \
  131. libportaudio2 \
  132. libportaudiocpp0 \
  133. ffmpeg"
  134. # Install system dependencies with caching and cleanup
  135. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
  136. --mount=type=cache,target=/var/lib/apt,sharing=locked \
  137. set -eux \
  138. && rm -f /etc/apt/apt.conf.d/docker-clean \
  139. && echo 'Binary::apt::APT::Keep-Downloaded-Packages "true";' >/etc/apt/apt.conf.d/keep-cache \
  140. && apt-get update \
  141. && apt-get install -y --no-install-recommends ${DEPENDENCIES} \
  142. && apt-get clean \
  143. && rm -rf /var/lib/apt/lists/*
  144. # Install specific uv version
  145. COPY --from=uv-bin /uv /uvx /bin/
  146. # RUN groupadd --gid ${USER_GID} ${USERNAME} \
  147. # && useradd --uid ${USER_UID} --gid ${USER_GID} -m ${USERNAME} \
  148. # && mkdir -p /app /home/${USERNAME}/.cache \
  149. # && chown -R ${USERNAME}:${USERNAME} /app /home/${USERNAME}/.cache
  150. # Create non-root user (or use existing user)
  151. RUN set -eux; \
  152. if getent group ${USER_GID} >/dev/null 2>&1; then \
  153. echo "Group ${USER_GID} already exists"; \
  154. else \
  155. groupadd -g ${USER_GID} ${USERNAME}; \
  156. fi; \
  157. if id -u ${USER_UID} >/dev/null 2>&1; then \
  158. echo "User ${USER_UID} already exists, using existing user"; \
  159. EXISTING_USER=$(id -un ${USER_UID}); \
  160. mkdir -p /app /home/${EXISTING_USER}/.cache; \
  161. chown -R ${USER_UID}:${USER_GID} /app /home/${EXISTING_USER}/.cache; \
  162. else \
  163. useradd -m -u ${USER_UID} -g ${USER_GID} ${USERNAME}; \
  164. mkdir -p /app /home/${USERNAME}/.cache; \
  165. chown -R ${USERNAME}:${USERNAME} /app /home/${USERNAME}/.cache; \
  166. fi
  167. # Create references directory with proper permissions for the non-root user
  168. RUN mkdir -p /app/references \
  169. && chown -R ${USER_UID}:${USER_GID} /app/references \
  170. && chmod 755 /app/references
  171. # Set working directory
  172. WORKDIR /app
  173. # Copy dependency files first for better caching
  174. COPY --chown=${USER_UID}:${USER_GID} pyproject.toml uv.lock README.md ./
  175. # Switch to non-root user for package installation
  176. USER ${USER_UID}:${USER_GID}
  177. # Install Python dependencies (cacheable by lockfiles)
  178. # Use a generic cache path that works regardless of username
  179. RUN --mount=type=cache,target=/tmp/uv-cache,uid=${USER_UID},gid=${USER_GID} \
  180. uv python pin ${PY_VER} \
  181. && uv sync -vv --extra ${UV_EXTRA} --frozen --no-install-project
  182. # Copy application code
  183. COPY --chown=${USER_UID}:${USER_GID} . .
  184. # Install the local package after copying source code
  185. RUN uv sync -vv --extra ${UV_EXTRA} --frozen --no-build-isolation
  186. # Create common entrypoint script
  187. RUN printf '%s\n' \
  188. '#!/bin/bash' \
  189. 'set -euo pipefail' \
  190. '' \
  191. '# Set user info from build args' \
  192. 'USER_UID='${USER_UID} \
  193. 'USER_GID='${USER_GID} \
  194. '' \
  195. '# Logging function' \
  196. 'log() { echo "[$(date +"%Y-%m-%d %H:%M:%S")] $*" >&2; }' \
  197. '' \
  198. '# Validate environment' \
  199. 'validate_env() {' \
  200. ' if [ ! -d "/app/checkpoints" ]; then' \
  201. ' log "WARNING: /app/checkpoints directory not found. Please mount your checkpoints."' \
  202. ' fi' \
  203. ' if [ ! -d "/app/references" ]; then' \
  204. ' log "WARNING: /app/references directory not found. Please mount your references."' \
  205. ' else' \
  206. ' # Check if we can write to references directory' \
  207. ' if [ ! -w "/app/references" ]; then' \
  208. ' log "ERROR: Cannot write to /app/references directory. Please ensure the mounted directory has proper permissions for user with UID ${USER_UID}."' \
  209. ' log "You can fix this by running: sudo chown -R ${USER_UID}:${USER_GID} /path/to/your/references"' \
  210. ' exit 1' \
  211. ' fi' \
  212. ' fi' \
  213. '}' \
  214. '' \
  215. '# Build device arguments' \
  216. 'build_device_args() {' \
  217. ' if [ "${BACKEND:-}" = "cpu" ]; then' \
  218. ' echo "--device cpu"' \
  219. ' fi' \
  220. '}' \
  221. '' \
  222. '# Build compile arguments' \
  223. 'build_compile_args() {' \
  224. ' if [ "${1:-}" = "compile" ] || [ "${COMPILE:-}" = "1" ] || [ "${COMPILE:-}" = "true" ]; then' \
  225. ' echo "--compile"' \
  226. ' shift' \
  227. ' fi' \
  228. ' echo "$@"' \
  229. '}' \
  230. '' \
  231. '# Build half arguments' \
  232. 'build_half_args() {' \
  233. ' if [ "${1:-}" = "half" ] || [ "${HALF:-}" = "1" ] || [ "${HALF:-}" = "true" ]; then' \
  234. ' echo "--half"' \
  235. ' shift' \
  236. ' fi' \
  237. ' echo "$@"' \
  238. '}' \
  239. '' \
  240. '# Health check function' \
  241. 'health_check() {' \
  242. ' local port=${1:-7860}' \
  243. ' local endpoint=${2:-/health}' \
  244. ' curl -f http://localhost:${port}${endpoint} 2>/dev/null || exit 1' \
  245. '}' \
  246. > /app/common.sh && chmod +x /app/common.sh
  247. ##############################################################
  248. # App stages
  249. ##############################################################
  250. # Gradio WebUI
  251. FROM app-base AS webui
  252. ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
  253. ARG GRADIO_SERVER_NAME="0.0.0.0"
  254. ARG GRADIO_SERVER_PORT=7860
  255. ARG LLAMA_CHECKPOINT_PATH="checkpoints/s2-pro"
  256. ARG DECODER_CHECKPOINT_PATH="checkpoints/s2-pro/codec.pth"
  257. ARG DECODER_CONFIG_NAME="modded_dac_vq"
  258. # Expose port
  259. EXPOSE ${GRADIO_SERVER_PORT}
  260. # Set environment variables
  261. ENV GRADIO_SERVER_NAME=${GRADIO_SERVER_NAME}
  262. ENV GRADIO_SERVER_PORT=${GRADIO_SERVER_PORT}
  263. ENV LLAMA_CHECKPOINT_PATH=${LLAMA_CHECKPOINT_PATH}
  264. ENV DECODER_CHECKPOINT_PATH=${DECODER_CHECKPOINT_PATH}
  265. ENV DECODER_CONFIG_NAME=${DECODER_CONFIG_NAME}
  266. # Create webui entrypoint
  267. RUN printf '%s\n' \
  268. '#!/bin/bash' \
  269. 'source /app/common.sh' \
  270. '' \
  271. 'log "Starting Fish Speech WebUI..."' \
  272. 'validate_env' \
  273. '' \
  274. 'DEVICE_ARGS=$(build_device_args)' \
  275. 'COMPILE_ARGS=$(build_compile_args "$@")' \
  276. '' \
  277. 'log "Device args: ${DEVICE_ARGS:-none}"' \
  278. 'log "Compile args: ${COMPILE_ARGS}"' \
  279. 'log "Server: ${GRADIO_SERVER_NAME}:${GRADIO_SERVER_PORT}"' \
  280. '' \
  281. 'exec uv -v run tools/run_webui.py \' \
  282. ' --llama-checkpoint-path "${LLAMA_CHECKPOINT_PATH}" \' \
  283. ' --decoder-checkpoint-path "${DECODER_CHECKPOINT_PATH}" \' \
  284. ' --decoder-config-name "${DECODER_CONFIG_NAME}" \' \
  285. ' ${DEVICE_ARGS} ${COMPILE_ARGS}' \
  286. > /app/start_webui.sh && chmod +x /app/start_webui.sh
  287. # Health check
  288. HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  289. CMD curl -f http://localhost:${GRADIO_SERVER_PORT}/health || exit 1
  290. ENTRYPOINT ["/app/start_webui.sh"]
  291. # API Server
  292. FROM app-base AS server
  293. ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1
  294. ARG API_SERVER_NAME="0.0.0.0"
  295. ARG API_SERVER_PORT=8080
  296. ARG LLAMA_CHECKPOINT_PATH="checkpoints/s2-pro"
  297. ARG DECODER_CHECKPOINT_PATH="checkpoints/s2-pro/codec.pth"
  298. ARG DECODER_CONFIG_NAME="modded_dac_vq"
  299. # Expose port
  300. EXPOSE ${API_SERVER_PORT}
  301. # Set environment variables
  302. ENV API_SERVER_NAME=${API_SERVER_NAME}
  303. ENV API_SERVER_PORT=${API_SERVER_PORT}
  304. ENV LLAMA_CHECKPOINT_PATH=${LLAMA_CHECKPOINT_PATH}
  305. ENV DECODER_CHECKPOINT_PATH=${DECODER_CHECKPOINT_PATH}
  306. ENV DECODER_CONFIG_NAME=${DECODER_CONFIG_NAME}
  307. # Create server entrypoint
  308. RUN printf '%s\n' \
  309. '#!/bin/bash' \
  310. 'source /app/common.sh' \
  311. '' \
  312. 'log "Starting Fish Speech API Server..."' \
  313. 'validate_env' \
  314. '' \
  315. 'DEVICE_ARGS=$(build_device_args)' \
  316. 'COMPILE_ARGS=$(build_compile_args "$@")' \
  317. 'HALF_ARGS=$(build_half_args "$@")' \
  318. '' \
  319. 'log "Device args: ${DEVICE_ARGS:-none}"' \
  320. 'log "Compile args: ${COMPILE_ARGS}"' \
  321. 'log "Half args: ${HALF_ARGS}"' \
  322. 'log "Server: ${API_SERVER_NAME}:${API_SERVER_PORT}"' \
  323. '' \
  324. 'exec uv run tools/api_server.py \' \
  325. ' --listen "${API_SERVER_NAME}:${API_SERVER_PORT}" \' \
  326. ' --llama-checkpoint-path "${LLAMA_CHECKPOINT_PATH}" \' \
  327. ' --decoder-checkpoint-path "${DECODER_CHECKPOINT_PATH}" \' \
  328. ' --decoder-config-name "${DECODER_CONFIG_NAME}" \' \
  329. ' --num-workers 2 \' \
  330. ' ${DEVICE_ARGS} ${COMPILE_ARGS} ${HALF_ARGS}' \
  331. > /app/start_server.sh && chmod +x /app/start_server.sh
  332. # Health check
  333. HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
  334. CMD curl -f http://localhost:${API_SERVER_PORT}/v1/health || exit 1
  335. ENTRYPOINT ["/app/start_server.sh"]
  336. # Development stage
  337. FROM app-base AS dev
  338. USER root
  339. # Install development tools
  340. RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
  341. --mount=type=cache,target=/var/lib/apt,sharing=locked \
  342. apt-get update \
  343. && apt-get install -y --no-install-recommends \
  344. vim \
  345. htop \
  346. strace \
  347. gdb \
  348. && apt-get clean \
  349. && rm -rf /var/lib/apt/lists/*
  350. USER ${USER_UID}:${USER_GID}
  351. # Install development dependencies
  352. RUN uv sync -vv --extra ${UV_EXTRA} --dev --no-build-isolation
  353. # Default to bash for development
  354. ENTRYPOINT ["/bin/bash"]