Skip to content
Open
Show file tree
Hide file tree
Changes from 93 commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
c6d9216
Add Docker build for Blackwell that runs on any NVIDIA GPU host
danielhanchen May 24, 2026
a75aef0
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
58693c4
Add entrypoint with GPU pre-flight checks + opinionated run.sh wrapper
danielhanchen May 24, 2026
acbb16c
Add docker/test_locally.sh: one-shot end-to-end Docker validation
danielhanchen May 24, 2026
f7b3479
test_locally.sh: use docker buildx (or DOCKER_BUILDKIT=1) for the build
danielhanchen May 24, 2026
56d2701
test_locally.sh: require docker buildx, no legacy fallback
danielhanchen May 24, 2026
23a5b43
test_locally.sh: pre-flight check for docker daemon connectivity
danielhanchen May 24, 2026
fd55ed0
Dockerfile: drop system-python pip/uv bootstrap (PEP 668)
danielhanchen May 24, 2026
00cbc82
smoke_test.py: import unsloth before unsloth_zoo / transformers / trl…
danielhanchen May 24, 2026
1cdc5f1
Dockerfile: install gcc + g++ + python3-dev in runtime stage
danielhanchen May 24, 2026
dde5170
Expand arch list to every current x86_64 NVIDIA CC per developer.nvid…
danielhanchen May 24, 2026
4bfb4b8
Add docker/hf_{push,pull}.sh: simulate docker push/pull against HF Hub
danielhanchen May 24, 2026
7354642
hf_{push,pull}.sh: use new `hf` CLI, fall back to deprecated `hugging…
danielhanchen May 24, 2026
8344fa0
test_locally.sh: use nbformat directly, drop fragile jupyter nbconver…
danielhanchen May 24, 2026
391532c
test_locally.sh: skip notebook install cells, strip stray jupyter magic
danielhanchen May 24, 2026
e7cfcea
Add linux/arm64 (DGX Spark / Grace) support via QEMU at build time
danielhanchen May 24, 2026
131f1d3
docker-publish.yml: native arm64 runner + per-arch digest merge
danielhanchen May 24, 2026
897e5e7
Dockerfile: tighten arch-flag assertion + correct fat-binary claims
danielhanchen May 24, 2026
1769204
Dockerfile: arm64 DGX Spark NVRTC + ptxas fix (cu13 alongside cu128)
danielhanchen May 24, 2026
e728eed
Dockerfile: switch runtime base cudnn-runtime -> base (~2.7 GB lighter)
danielhanchen May 24, 2026
c463d58
entrypoint.sh: correct driver-floor message (570+ unconditionally on …
danielhanchen May 24, 2026
fa9609d
Dockerfile: arm64 install cu13 nvrtc/nvcc directly without cuda-keyri…
danielhanchen May 24, 2026
34fb65f
Dockerfile: install vLLM nightly on amd64 for GRPO fast_inference
danielhanchen May 24, 2026
215ed9b
Dockerfile: arm64 build aborted by set -e in vLLM auto-gate
danielhanchen May 24, 2026
6d536d8
Dockerfile: re-upgrade numpy after vLLM install (2.2.6 wheel is broken)
danielhanchen May 24, 2026
79e9363
Fix two upstream regressions surfaced by the Blackwell Docker validation
danielhanchen May 24, 2026
a01fa21
docker: add Dockerfile.studio extending the Blackwell image with Unsl…
danielhanchen May 24, 2026
29a6bde
Address reviewer findings on PR #5748: 4 release-path bugs
danielhanchen May 24, 2026
5cb5eb7
Remove async_task_outputs from repo (accidentally committed)
danielhanchen May 24, 2026
6e45c27
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
291e2cf
docker/Dockerfile.studio: keep source for the editable install
danielhanchen May 24, 2026
914f91c
docker: add timm + addict to the base image
danielhanchen May 24, 2026
0d574d8
Address reviewer-2 findings on PR #5748
danielhanchen May 24, 2026
a9b8d68
Remove individual_reviews from repo (accidentally committed)
danielhanchen May 24, 2026
f116f78
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] May 24, 2026
c91fa26
tests/studio: assert losses_per_step matches max_steps, not stale 7
danielhanchen May 24, 2026
cceeeb1
Address 3 MAJOR review findings on the docker PR
danielhanchen May 25, 2026
2faf827
docker: round-3 review fixes (concurrency, lockfile wording)
danielhanchen May 25, 2026
6b91708
Merge branch 'main' into docker-blackwell-build
danielhanchen May 27, 2026
9723d72
docker-publish: pin UNSLOTH_ZOO_REF on tag pushes
danielhanchen May 27, 2026
4d34845
docker/run.sh: translate UNSLOTH_GPUS index selectors to device= form
danielhanchen May 27, 2026
10f0a03
docker/test_locally.sh: fail fast + pin notebook fetch to immutable SHA
danielhanchen May 27, 2026
f34a4cd
Merge branch 'main' into docker-blackwell-build
danielhanchen Jun 12, 2026
6448587
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
f1a63db
docker: ship Jupyter, Studio and prebuilt llama.cpp out of the box
danielhanchen Jun 12, 2026
9e9877e
docker: pin the llama.cpp bake by target arch, add docker_confirm.sh
danielhanchen Jun 12, 2026
38d7b5e
docker: whitelist new build-context files, add docker_confirm.ps1
danielhanchen Jun 12, 2026
e8ac40f
docker/studio: deterministic Studio install inside the image build
danielhanchen Jun 12, 2026
d431f3c
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
6fd1220
docker: mirror the llama.cpp bake into build/bin so Studio setup reus…
danielhanchen Jun 12, 2026
c62bb19
docker_confirm.sh: rename unused poll counter for shellcheck SC2034
danielhanchen Jun 12, 2026
f4e378e
docker: review fixes from the 8-reviewer pass and staging CI
danielhanchen Jun 12, 2026
81b0d1e
docker: second review pass fixes
danielhanchen Jun 12, 2026
3d56379
docker ci: aggressive runner disk reclaim before image builds
danielhanchen Jun 12, 2026
96edc89
gpu_init: satisfy the import-hoist lint in the compile-thread patch
danielhanchen Jun 12, 2026
b2fe9f4
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
25d95c0
docker: zstd + matplotlib for out-of-the-box notebook coverage
danielhanchen Jun 12, 2026
c515aa0
docker: audio decode out of the box (ffmpeg + matched torchcodec bake)
danielhanchen Jun 12, 2026
e06b1fb
docker: split the torchcodec bake across build stages
danielhanchen Jun 12, 2026
3e6d37c
docker: install vLLM on the arm64 leg too and probe it in the confirm…
danielhanchen Jun 12, 2026
cfeb772
Merge remote-tracking branch 'origin/main' into pr-5748-head
danielhanchen Jun 12, 2026
9f9cd41
docker: add wget to the runtime image
danielhanchen Jun 12, 2026
09c9c95
ci: retrigger after bulk-cancelled runs
danielhanchen Jun 12, 2026
11430aa
docker: unbreak standalone vllm serve (ninja-build + flashinfer-jit-c…
danielhanchen Jun 12, 2026
487ea4f
dataprep: detect vllm 0.19 server readiness (renamed log line, stderr)
danielhanchen Jun 12, 2026
9987323
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
9d39aee
studio: honor UNSLOTH_TORCH_INDEX_FAMILY in CUDA repair path, assert …
danielhanchen Jun 12, 2026
6f6b638
Fix import on driverless hosts under UNSLOTH_ALLOW_CPU=1
danielhanchen Jun 12, 2026
8242b73
docker: mirror soname symlinks into llama.cpp build/bin, assert the r…
danielhanchen Jun 12, 2026
5b4eb34
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 12, 2026
eba071f
docker/studio: make the quantizer build assertion content based
danielhanchen Jun 12, 2026
5c5b534
Merge remote-tracking branch 'origin/main' into pr-5748-head
danielhanchen Jun 12, 2026
d01da4c
docker_confirm: accept locally built images when pull fails
danielhanchen Jun 12, 2026
2cd58d5
docker: ship cuda-nvcc and cudart-dev in the runtime image
danielhanchen Jun 12, 2026
5bb47cf
docker: bake soundfile, evaluate, tensorboard for notebook deps
danielhanchen Jun 13, 2026
ea91c7a
docker: add jiwer, langid, easydict, protobuf to baked notebook deps
danielhanchen Jun 13, 2026
dec240a
Merge remote-tracking branch 'origin/main' into pr-5748-head
Jun 14, 2026
aba16af
docker: notebook deps, image size cuts, per-notebook transformers
danielhanchen Jun 15, 2026
448e225
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 15, 2026
d0d5f3c
docker: pre-load unslothai/notebooks into JupyterLab, edit-safe refresh
danielhanchen Jun 15, 2026
338aff5
docker: notebook refresh ignores header/footer-only upstream changes
danielhanchen Jun 16, 2026
7e2e842
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 16, 2026
921ab18
docker: heal deleted notebooks on boot + fix notebooks helper dockeri…
danielhanchen Jun 16, 2026
d5df6c0
docker: track latest llama.cpp release + show its update banner in St…
danielhanchen Jun 24, 2026
b897cf8
docker: add unsloth-studio-update for in-place Studio updates
danielhanchen Jun 24, 2026
7606081
docker: add unsloth-llama-update for in-place llama.cpp prebuilt updates
danielhanchen Jun 24, 2026
08f9b67
docker: optional Cloudflare tunnel for JupyterLab (UNSLOTH_JUPYTER_CL…
danielhanchen Jun 24, 2026
c0abb0a
Merge remote-tracking branch 'origin/main' into dbb-merge-main
danielhanchen Jun 26, 2026
053a4f3
docker-publish: clean build-args + add least-privilege default permis…
danielhanchen Jun 26, 2026
52067fb
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 26, 2026
8f693c6
docker: fix notebook pip-shim drops, first-boot overwrite, base :late…
danielhanchen Jun 26, 2026
f45c455
Merge remote-tracking branch 'origin/main' into dbb-merge-main
danielhanchen Jun 26, 2026
8402dce
docker-publish: pin one llama.cpp prebuilt release across both arch legs
danielhanchen Jun 26, 2026
0ebbdbb
docker: address review follow-ups (pip-shim flags, sync ownership, ta…
danielhanchen Jun 26, 2026
d476c77
docker: address review round 3 (notebook -r filter, studio zoo ref, p…
danielhanchen Jun 26, 2026
7083a2d
docker: pip-shim catches direct-reference protected installs + --opt=…
danielhanchen Jun 26, 2026
2ee7f4b
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 26, 2026
2c31686
docker: address review round 4 (jupyter probe, CPU messaging, llama E…
danielhanchen Jun 27, 2026
f152569
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jun 27, 2026
68f5394
docker_confirm.ps1: probe JupyterLab /login, not /api
danielhanchen Jun 29, 2026
8fc483e
Merge remote-tracking branch 'origin/main' into docker-blackwell-build
danielhanchen Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
538 changes: 538 additions & 0 deletions .github/workflows/docker-publish.yml

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -235,5 +235,7 @@ package-lock.json
!studio/backend/core/data_recipe/oxc-validator/package-lock.json
!studio/package-lock.json
llama.cpp/
async_task_outputs/
individual_reviews/
# Stray "~" dir some tools create from a literal ~ TMPDIR; never part of the repo.
/~/
16 changes: 16 additions & 0 deletions docker/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
**
!Dockerfile
!entrypoint.sh
!smoke_test.py
!fetch_llama_prebuilt.py
!supervisord.conf
!studio_launch.sh
!unsloth_studio_update.sh
!unsloth_llama_update.sh
!unsloth_jupyter_tunnel.sh
!unsloth_nb_compat.py
!unsloth_pip_shim.py
!unsloth_ipython_startup.py
!unsloth_run.py
!unsloth_sync_notebooks.sh
!unsloth_nb_content_sig.py
719 changes: 719 additions & 0 deletions docker/Dockerfile

Large diffs are not rendered by default.

184 changes: 184 additions & 0 deletions docker/Dockerfile.studio
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
# 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; when
# unset a random one is generated and printed in the container logs). 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. cu128 on BOTH arches, mirroring the base venv: cu130 wheels
# would silently lift the arm64 driver floor to 580+ while the base venv
# keeps the documented 570+ floor. DGX Spark / GB10 (sm_121) support comes
# from the same NVRTC cu13 swap the base image applies to its venv --
# repeated below for the Studio venv's own bundled libnvrtc (the base's
# arm64 layer already installed cuda-nvrtc-13-0, so the cu13 .so exists).
#
# UNSLOTH_PYTHON=3.12 pins the Studio venv to the SAME Python minor as the base
# venv (install.sh defaults Linux to 3.13). Matching minors makes the two venvs'
# nvidia-*-cu12 CUDA wheels byte-identical, which lets the dedup RUN further down
# replace the Studio venv's ~3.7GB of CUDA .so with symlinks into the base venv's
# copies (cudnn/cublas/nccl/... are plain C libs, Python-minor independent).
#
# 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|arm64) TORCH_FAMILY="cu128" ;; \
*) 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}" \
UNSLOTH_PYTHON=3.12 \
bash install.sh --local \
Comment on lines +112 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin Studio unsloth-zoo to the release ref

For tag/release builds this path checks out the requested Unsloth ref, but bash install.sh --local then takes the local-install branch in install.sh, which overlays unsloth-zoo @ git+https://github.com/unslothai/unsloth-zoo from the default branch rather than the workflow's matched UNSLOTH_ZOO_REF. Fresh evidence compared with the earlier base-image comment is that the full Studio image runs its backend from this separate Studio venv, so release-tag images can still ship moving unsloth-zoo code in Studio even after the base venv is pinned.

Useful? React with 👍 / 👎.

# Fail loud if the Studio venv torch missed the pinned CUDA family (an
Comment on lines +116 to +117

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Do not continue Studio install into a comment block

This continued bash install.sh --local \ is followed by only shell comments before the next &&; after Docker's line continuations, the comment consumes the rest of that logical line and the shell then sees a new command starting with &&, causing the Studio image build to fail immediately after the installer step. End the command before the comment or move the && continuation above the comment block.

Useful? React with 👍 / 👎.

# install.sh that ignores UNSLOTH_TORCH_INDEX_FAMILY falls back to
# nvidia-smi probing, which cannot work at build time and lands on cu126
# wheels with no sm_100/sm_120 kernels). metadata check only: importing
# torch needs native libs, which QEMU arm64 builds cannot load.
&& "${UNSLOTH_STUDIO_HOME}/unsloth_studio/bin/python" -c "import sys; from importlib.metadata import version; assert sys.version_info[:2] == (3, 12), 'Studio venv python %d.%d is not 3.12 (UNSLOTH_PYTHON pin ignored) -- CUDA dedup below depends on it' % sys.version_info[:2]; v = version('torch'); assert v.endswith('+${TORCH_FAMILY}'), 'Studio venv torch ' + v + ' does not match ${TORCH_FAMILY}'; print('Studio venv python %d.%d' % sys.version_info[:2], 'torch', v)" \

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge End the Studio metadata check before comments

This line also keeps the RUN command open into a block of shell comments, and the next non-comment command begins with && at the llama.cpp probe. Once the installer-step continuation above is corrected, Docker will still join this line to the comment so /bin/sh sees a fresh command starting with &&, causing the Studio image build to fail before the quantizer check runs; end the Python check before the comment or move the continuation onto the next command.

Useful? React with 👍 / 👎.

# setup.sh may relink the root llama-quantize into build/bin; prove the
# relinked quantizer still resolves its libraries, or GGUF export breaks
# at runtime with "No working quantizer found". Content check, not rc:
# llama-quantize exits nonzero on --help, while a loader failure prints
# "error while loading shared libraries" and no usage text.
&& { "${UNSLOTH_STUDIO_HOME}/llama.cpp/llama-quantize" --help 2>&1 || true; } | grep -q "usage" \
&& rm -rf "${UNSLOTH_STUDIO_HOME}/src/.git" \
"${UNSLOTH_STUDIO_HOME}/src/studio/frontend/node_modules" \
/root/.cache \
&& if [ "${TARGETARCH:-amd64}" = "arm64" ]; then \
for NVRTC_DIR in "${UNSLOTH_STUDIO_HOME}"/unsloth_studio/lib/python*/site-packages/nvidia/cuda_nvrtc/lib; do \
if [ -f "${NVRTC_DIR}/libnvrtc.so.12" ]; then \
mv "${NVRTC_DIR}/libnvrtc.so.12" "${NVRTC_DIR}/libnvrtc.so.12.cu128.orig"; \
ln -s /usr/local/cuda-13.0/lib64/libnvrtc.so.13 "${NVRTC_DIR}/libnvrtc.so.12"; \
fi; \
done; \
fi \
&& BASE_NV=/opt/unsloth-venv/lib/python3.12/site-packages/nvidia \
&& STU_NV="${UNSLOTH_STUDIO_HOME}/unsloth_studio/lib/python3.12/site-packages/nvidia" \
&& if [ ! -d "${STU_NV}" ] || [ ! -d "${BASE_NV}" ]; then \
echo ">> nvidia dir missing (STU=${STU_NV} BASE=${BASE_NV}); skipping CUDA dedup"; \
else \
find "${UNSLOTH_STUDIO_HOME}/unsloth_studio" -name '*.a' -delete; \
rm -f "${STU_NV}/nvshmem/lib/libnvshmem_device.bc"; \
for c in cudnn cublas cusparselt nccl cusolver cusparse cufft curand nvjitlink cuda_cupti nvshmem npp; do \
b="${BASE_NV}/${c}/lib"; s="${STU_NV}/${c}/lib"; \
{ [ -d "$b" ] && [ -d "$s" ]; } || { echo ">> skip ${c} (dir missing)"; continue; }; \
if [ "${c}" = "npp" ]; then \
rm -rf "$s" && ln -s "$b" "$s" && readlink -e "$s" >/dev/null; \
echo ">> deduped npp -> base (pruned)"; \
elif [ "$(cd "$s" && ls | sort | tr '\n' ' ')" = "$(cd "$b" && ls | sort | tr '\n' ' ')" ]; then \
rm -rf "$s" && ln -s "$b" "$s" && readlink -e "$s" >/dev/null; \
echo ">> deduped ${c} -> base"; \
else \
echo ">> skip ${c} (file set differs base vs studio)"; \
fi; \
done; \
echo "studio venv size after dedup:"; du -sh "${UNSLOTH_STUDIO_HOME}/unsloth_studio"; \
fi

COPY supervisord.conf /etc/supervisor/supervisord.conf
COPY studio_launch.sh /usr/local/bin/unsloth-studio-launch
# In-place updaters (no image pull):
# unsloth-studio-update refreshes the Studio packages (backend + baked
# frontend) and restarts the service.
# unsloth-llama-update swaps the baked llama.cpp prebuilt to the latest
# release (the same swap the in-app banner performs).
COPY unsloth_studio_update.sh /usr/local/bin/unsloth-studio-update
COPY unsloth_llama_update.sh /usr/local/bin/unsloth-llama-update
# unsloth-llama-update reuses the build-time fetcher (redirect-based, no GitHub
# API, so it is not rate-limited; deterministic portable bundle that runs on CPU
# and every supported GPU) rather than the host-probing installer.
COPY fetch_llama_prebuilt.py /usr/local/lib/unsloth/fetch_llama_prebuilt.py
# Optional public Cloudflare tunnel for JupyterLab (UNSLOTH_JUPYTER_CLOUDFLARE=1,
# or `unsloth-jupyter-tunnel --force`); supervisord runs it as jupyter-cloudflare.
COPY unsloth_jupyter_tunnel.sh /usr/local/bin/unsloth-jupyter-tunnel
RUN chmod +x /usr/local/bin/unsloth-studio-launch \
/usr/local/bin/unsloth-studio-update \
/usr/local/bin/unsloth-llama-update \
/usr/local/bin/unsloth-jupyter-tunnel

# 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"]
67 changes: 67 additions & 0 deletions docker/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Build the unsloth-blackwell image on this B200 host (or any Linux host with Docker).
# The build host's GPU is NOT used -- nvcc cross-compiles for sm_100 + sm_120.
#
# Usage:
# ./build.sh # builds unsloth-blackwell:latest pinned to unsloth main
# TAG=2026.05.1 ./build.sh # custom tag
# UNSLOTH_REF=v2026.5.6 UNSLOTH_ZOO_REF=v2026.5.4 ./build.sh # pin git refs
set -euo pipefail

cd "$(dirname "$0")"

IMAGE_NAME="${IMAGE_NAME:-unsloth-blackwell}"
TAG="${TAG:-latest}"
CUDA_VERSION="${CUDA_VERSION:-12.8.1}"
UBUNTU_VERSION="${UBUNTU_VERSION:-24.04}"
PYTHON_VERSION="${PYTHON_VERSION:-3.12}"
UNSLOTH_REF="${UNSLOTH_REF:-main}"
UNSLOTH_ZOO_REF="${UNSLOTH_ZOO_REF:-main}"

# llama.cpp prebuilt: default to the newest unslothai/llama.cpp release, resolved
# here to a concrete tag so the build-arg changes only when upstream publishes a
# new release (correct Docker layer caching) and the build stays reproducible.
# Pin it explicitly for a frozen build: LLAMA_PREBUILT_TAG=b9596-mix-e6f2453 ./build.sh
resolve_latest_llama_tag() {
curl -fsSL -o /dev/null -w '%{url_effective}' \
"https://github.com/unslothai/llama.cpp/releases/latest" 2>/dev/null \
| sed -n 's#.*/releases/tag/##p'
}
if [ -z "${LLAMA_PREBUILT_TAG:-}" ]; then
LLAMA_PREBUILT_TAG="$(resolve_latest_llama_tag || true)"
if [ -n "$LLAMA_PREBUILT_TAG" ]; then
echo "Resolved latest llama.cpp release: ${LLAMA_PREBUILT_TAG}"
else
LLAMA_PREBUILT_TAG="latest"
echo "Could not resolve latest llama.cpp tag here; passing 'latest' (resolved inside the build)"
fi
fi

echo "Building ${IMAGE_NAME}:${TAG}"
echo " CUDA ${CUDA_VERSION} Ubuntu ${UBUNTU_VERSION} Python ${PYTHON_VERSION}"
echo " unsloth @${UNSLOTH_REF}"
echo " unsloth-zoo @${UNSLOTH_ZOO_REF}"
echo " llama.cpp ${LLAMA_PREBUILT_TAG}"
echo " arch list 8.0;8.6;8.9;9.0;10.0;12.0+PTX"
echo

DOCKER_BUILDKIT=1 docker build \
--progress=plain \
--build-arg CUDA_VERSION="${CUDA_VERSION}" \
--build-arg UBUNTU_VERSION="${UBUNTU_VERSION}" \
--build-arg PYTHON_VERSION="${PYTHON_VERSION}" \
--build-arg UNSLOTH_REF="${UNSLOTH_REF}" \
--build-arg UNSLOTH_ZOO_REF="${UNSLOTH_ZOO_REF}" \
--build-arg LLAMA_PREBUILT_TAG="${LLAMA_PREBUILT_TAG}" \
-t "${IMAGE_NAME}:${TAG}" \
.

echo
echo "Built ${IMAGE_NAME}:${TAG}"
echo
echo "Smoke test on this host (B200, sm_100):"
echo " docker run --rm --gpus all ${IMAGE_NAME}:${TAG} python /workspace/smoke_test.py"
echo
echo "Smoke test on an RTX 5090 host (sm_120):"
echo " docker pull ${IMAGE_NAME}:${TAG} # or load .tar"
echo " docker run --rm --gpus all ${IMAGE_NAME}:${TAG} python /workspace/smoke_test.py"
Loading
Loading