entrypoint.sh: a container started without a GPU request has no
nvidia-smi at all (the toolkit injects it), so the old check 1 reported
'CUDA runtime in this image is broken, re-pull' for the most common user
error. Fold the missing-binary case into the actionable 'No GPU visible'
message and document the CPU-only option (UNSLOTH_ALLOW_CPU=1).
run.sh / test_locally.sh: guard empty-array expansions with the
${arr[@]+...} form; bash 3.2 (macOS /bin/bash) treats "${empty[@]}"
as unbound under set -u, which broke the documented macOS CPU path.
studio_launch.sh: exclude *_TOKEN, *_API_KEY, *_PASSWORD, *_SECRET,
*_LICENSE from the env snapshot written for SSH sessions; secrets stay
in process env only, never on disk.
supervisord.conf / Dockerfile.studio: pin HOME=/root for the studio and
jupyter programs (jupyter would silently fall back to token auth if HOME
were unset), default JUPYTER_PORT and UNSLOTH_ENABLE_SSHD at the image
level so a direct supervisord invocation cannot hit a bad %(ENV_*)s
expansion, and document the root-services decision (non-root parity with
the previous production image is a tracked follow-up).
docker_confirm.ps1: mirror the bash script's GPU selector translation so
GPUS=0 / 0,1 select devices instead of silently using all GPUs.
docker-publish.yml: studio cache scope moves to mode=min; a mode=max
cache of a ~24GB image would evict everything else in the 10GB GHA
quota for no hit-rate gain.
113 lines
5.1 KiB
Text
113 lines
5.1 KiB
Text
# 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
|
|
|
|
# Services run as root in this revision (the base image is root-only by
|
|
# design); the previous production image ran them as a dedicated uid-1001
|
|
# user. Non-root parity is a tracked follow-up. sshd is key-only and stays
|
|
# disabled unless a PUBLIC_KEY/SSH_KEY is provided, and no secrets are
|
|
# persisted to disk (see studio_launch.sh).
|
|
#
|
|
# The JUPYTER_PORT / UNSLOTH_ENABLE_SSHD defaults exist so supervisord's
|
|
# %(ENV_*)s expansions still resolve when someone bypasses the launcher
|
|
# and runs supervisord directly.
|
|
USER root
|
|
ENV UNSLOTH_STUDIO_HOME=/opt/unsloth-studio \
|
|
JUPYTER_PORT=8888 \
|
|
UNSLOTH_ENABLE_SSHD=false \
|
|
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"]
|