# Full Unsloth image: base training stack + Studio + JupyterLab + sshd. # # This is the image published as docker.io/unsloth/unsloth:latest. It layers # Unsloth Studio on top of the lean base image (Dockerfile, published under # the `base` tags) and runs the same service trio as the previous production # image: Studio on 8000, JupyterLab on 8888, key-only sshd on 22. # # Build (local): # docker buildx build \ # --build-arg BASE_IMAGE=unsloth-blackwell:test \ # -f docker/Dockerfile.studio \ # -t unsloth-blackwell:studio docker/ # # Run: # docker run --rm --gpus all -p 8000:8000 -p 8888:8888 \ # -v $HOME/.cache/huggingface:/workspace/.cache/huggingface \ # unsloth-blackwell:studio # # Open http://localhost:8000 for Studio (first-boot admin password is printed # in the container logs and persisted under /opt/unsloth-studio/auth/) and # http://localhost:8888 for JupyterLab (password: JUPYTER_PASSWORD env, # default `unsloth`). On hosts without GPU passthrough (Docker Desktop on # macOS, Windows without WSL2 GPU) add -e UNSLOTH_ALLOW_CPU=1: training is # unavailable but Studio chat / Data Recipes / GGUF tooling / Jupyter work. # # CI pins BASE_IMAGE to the just-published multi-arch base digest so the two # images always ship the same stack. ARG BASE_IMAGE=unsloth-blackwell:test FROM ${BASE_IMAGE} # Studio source ref to clone. Defaults to `main`, but a CI publish pipeline # that pins BASE_IMAGE to a digest should pin this too (same UNSLOTH_REF as # the base) so the published image is reproducible against a known ref. ARG UNSLOTH_STUDIO_REF=main ARG TARGETARCH USER root ENV UNSLOTH_STUDIO_HOME=/opt/unsloth-studio \ DEBIAN_FRONTEND=noninteractive # install.sh needs curl + git; supervisor + openssh-server run the service # trio. The base image already has python + uv + pip. RUN apt-get update \ && apt-get install -y --no-install-recommends \ curl git ca-certificates supervisor openssh-server \ && rm -rf /var/lib/apt/lists/* # Clone + install Studio into a dedicated venv under $UNSLOTH_STUDIO_HOME. # --local makes install.sh use the just-cloned source tree (editable # install), so the source dir MUST persist for the venv's `unsloth_cli` # entrypoint to keep resolving. Move it under $UNSLOTH_STUDIO_HOME/src # (already inside the persistent layer) instead of deleting it. Strip # .git to save ~120MB. # # The llama.cpp symlink BEFORE install.sh points Studio's prebuilt dir at # the bundle already baked into the base image (validated, sha256-checked, # UNSLOTH_PREBUILT_INFO.json present), so the installer's prebuilt step # recognises it and skips a second ~400MB download. The # .unsloth-studio-owned marker satisfies setup.sh's ownership assertion for # custom STUDIO_HOMEs -- the dir IS provisioned exclusively for Studio. # # UNSLOTH_TORCH_INDEX_FAMILY pins the torch wheel index for the Studio # venv: at build time there is no GPU and no nvidia-smi, so install.sh's # probing would land on cpu or cu126 wheels depending on which host built # the image. The image targets CUDA: cu128 on amd64 (Turing..Blackwell, # same line as the base venv), cu130 on arm64 (DGX Spark / Grace, the # aarch64 CUDA wheel line). # # fetch+checkout FETCH_HEAD instead of `clone --branch` because the CI # pipeline passes a commit SHA as the ref (clone --branch only accepts # branch/tag names). RUN set -eux \ && case "${TARGETARCH:-amd64}" in \ amd64) TORCH_FAMILY="cu128" ;; \ arm64) TORCH_FAMILY="cu130" ;; \ *) echo "ERROR: unsupported TARGETARCH=${TARGETARCH}" >&2; exit 1 ;; \ esac \ && mkdir -p "${UNSLOTH_STUDIO_HOME}" \ && ln -s /opt/unsloth/llama.cpp "${UNSLOTH_STUDIO_HOME}/llama.cpp" \ && touch /opt/unsloth/llama.cpp/.unsloth-studio-owned \ && git init -q "${UNSLOTH_STUDIO_HOME}/src" \ && cd "${UNSLOTH_STUDIO_HOME}/src" \ && git remote add origin https://github.com/unslothai/unsloth \ && git fetch -q --depth 1 origin "${UNSLOTH_STUDIO_REF}" \ && git checkout -q FETCH_HEAD \ && UNSLOTH_STUDIO_HOME="${UNSLOTH_STUDIO_HOME}" \ UNSLOTH_TORCH_INDEX_FAMILY="${TORCH_FAMILY}" \ bash install.sh --local \ && rm -rf "${UNSLOTH_STUDIO_HOME}/src/.git" /root/.cache COPY supervisord.conf /etc/supervisor/supervisord.conf COPY studio_launch.sh /usr/local/bin/unsloth-studio-launch RUN chmod +x /usr/local/bin/unsloth-studio-launch # Studio web UI, JupyterLab, sshd. All bind 0.0.0.0 inside the container's # network namespace; the operator publishes them explicitly with -p. EXPOSE 8000 8888 22 # The base ENTRYPOINT (unsloth-entrypoint) still runs its GPU pre-flight # first, then hands off to the service launcher. CMD ["/usr/local/bin/unsloth-studio-launch"]