|
1 | 1 | # =============================== |
2 | | -# logo + scripts stages |
| 2 | +# Build args |
| 3 | +# =============================== |
| 4 | +ARG PYTHON_VERSION="3.11.14" |
| 5 | + |
| 6 | +# =============================== |
| 7 | +# logo + scripts + proxy stages |
3 | 8 | # =============================== |
4 | 9 | FROM scratch AS logo |
5 | 10 | COPY container-template/yotta.txt yotta.txt |
6 | 11 |
|
7 | 12 | FROM scratch AS scripts |
8 | | -COPY container-template/start.sh start.sh |
| 13 | +COPY container-template/start1.sh start1.sh |
| 14 | + |
| 15 | +FROM scratch AS proxy |
| 16 | +COPY container-template/proxy/nginx.conf nginx.conf |
| 17 | +COPY container-template/proxy/readme.html readme.html |
9 | 18 |
|
10 | 19 | # =============================== |
11 | 20 | # main stage |
12 | 21 | # =============================== |
13 | 22 | FROM nvidia/cuda:12.8.1-cudnn-devel-ubuntu22.04 |
14 | 23 |
|
15 | | -ARG DEBIAN_FRONTEND=noninteractive |
16 | | -ENV TZ=UTC \ |
| 24 | +# Re-declare ARGs after FROM |
| 25 | +ARG PYTHON_VERSION="3.11.14" |
| 26 | + |
| 27 | +SHELL ["/bin/bash", "-o", "pipefail", "-c"] |
| 28 | + |
| 29 | +ENV DEBIAN_FRONTEND=noninteractive \ |
| 30 | + SHELL=/bin/bash \ |
| 31 | + TZ=UTC \ |
17 | 32 | LANG=C.UTF-8 \ |
18 | 33 | LC_ALL=C.UTF-8 \ |
19 | 34 | PYTHONUNBUFFERED=1 \ |
20 | | - VENV_PATH=/opt/venv |
| 35 | + PATH=/usr/local/nvidia/bin:/usr/local/cuda/bin:/usr/local/bin:$PATH \ |
| 36 | + LD_LIBRARY_PATH=/usr/local/nvidia/lib64:$LD_LIBRARY_PATH \ |
| 37 | + JUPYTER_PASSWORD=yotta |
21 | 38 |
|
22 | | -# ------------------------------- |
| 39 | +# =============================== |
| 40 | +# Workspace |
| 41 | +# =============================== |
| 42 | +WORKDIR / |
| 43 | +RUN mkdir -p /workspace && chmod 777 /workspace /root |
| 44 | + |
| 45 | +# =============================== |
23 | 46 | # System packages |
24 | | -# ------------------------------- |
| 47 | +# =============================== |
25 | 48 | RUN set -eux; \ |
26 | | - apt-get update; \ |
| 49 | + apt-get update -y; \ |
27 | 50 | apt-get install -y --no-install-recommends --allow-change-held-packages \ |
28 | 51 | ca-certificates curl wget git \ |
29 | | - sudo \ |
30 | | - openssh-server \ |
31 | | - nginx \ |
32 | | - tini \ |
33 | | - build-essential make g++ \ |
| 52 | + build-essential pkg-config \ |
34 | 53 | software-properties-common \ |
| 54 | + locales tzdata \ |
| 55 | + openssh-server nginx sudo \ |
35 | 56 | vim jq tree htop tmux rsync \ |
36 | | - pciutils iproute2 net-tools lsof procps; \ |
| 57 | + zip unzip less procps net-tools lsof \ |
| 58 | + pciutils iproute2; \ |
| 59 | + echo "en_US.UTF-8 UTF-8" > /etc/locale.gen; \ |
| 60 | + locale-gen; \ |
| 61 | + mkdir -p /var/run/sshd /var/log/supervisor; \ |
| 62 | + chmod 700 /var/run/sshd /var/log/supervisor; \ |
| 63 | + chmod 755 /var/log; \ |
| 64 | + apt-get clean; \ |
37 | 65 | rm -rf /var/lib/apt/lists/* |
38 | 66 |
|
39 | | -# ------------------------------- |
40 | | -# sshd wrapper (unchanged) |
41 | | -# ------------------------------- |
42 | | -RUN <<'EOF' |
43 | | -set -eux |
44 | | -if [ -x /usr/sbin/sshd ] && [ ! -x /usr/sbin/sshd.real ]; then |
45 | | - mv /usr/sbin/sshd /usr/sbin/sshd.real |
46 | | - cat > /usr/sbin/sshd <<'EOSSHD' |
47 | | -#!/usr/bin/env bash |
48 | | -set -euo pipefail |
49 | | -REAL="/usr/sbin/sshd.real" |
50 | | -if [[ " $* " == *" -D "* ]]; then |
51 | | - args=() |
52 | | - for a in "$@"; do |
53 | | - [[ "$a" == "-D" ]] && continue |
54 | | - args+=("$a") |
55 | | - done |
56 | | - exec "$REAL" "${args[@]}" |
57 | | -fi |
58 | | -exec "$REAL" "$@" |
59 | | -EOSSHD |
60 | | - chmod 755 /usr/sbin/sshd |
61 | | -fi |
62 | | -EOF |
63 | | - |
64 | | -# ------------------------------- |
65 | | -# Python 3.11 + venv |
66 | | -# ------------------------------- |
| 67 | +# =============================== |
| 68 | +# Remove ubuntu user (security: prevent unauthorized SSH access) |
| 69 | +# =============================== |
| 70 | +RUN userdel -r ubuntu || true |
| 71 | + |
| 72 | +# =============================== |
| 73 | +# Python (build from source, with ensurepip) |
| 74 | +# =============================== |
67 | 75 | RUN set -eux; \ |
68 | | - add-apt-repository -y ppa:deadsnakes/ppa; \ |
69 | | - apt-get update; \ |
| 76 | + PY_MM="$(echo "${PYTHON_VERSION}" | awk -F. '{print $1"."$2}')"; \ |
| 77 | + apt-get update -y; \ |
70 | 78 | apt-get install -y --no-install-recommends \ |
71 | | - python3.11 python3.11-venv python3.11-dev; \ |
72 | | - rm -rf /var/lib/apt/lists/*; \ |
73 | | - python3.11 -m venv "${VENV_PATH}"; \ |
74 | | - "${VENV_PATH}/bin/pip" install -U pip setuptools wheel |
75 | | - |
76 | | -RUN ln -sf "${VENV_PATH}/bin/python3.11" /usr/local/bin/python3.11 && \ |
77 | | - ln -sf "${VENV_PATH}/bin/python" /usr/local/bin/python || true && \ |
78 | | - ln -sf "${VENV_PATH}/bin/pip" /usr/local/bin/pip || true |
79 | | - |
80 | | -RUN echo 'export PATH=/opt/venv/bin:$PATH' >/etc/profile.d/venv.sh && \ |
81 | | - chmod 644 /etc/profile.d/venv.sh && \ |
82 | | - echo 'Defaults secure_path="/opt/venv/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"' \ |
83 | | - >/etc/sudoers.d/99-secure-path |
84 | | - |
85 | | -# ------------------------------- |
86 | | -# Python packages (Unsloth) |
87 | | -# ------------------------------- |
88 | | -RUN set -eux; \ |
89 | | - pip install --no-cache-dir \ |
| 79 | + libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev \ |
| 80 | + libffi-dev libncursesw5-dev xz-utils tk-dev uuid-dev liblzma-dev; \ |
| 81 | + curl -fSL --retry 10 --retry-delay 2 --retry-all-errors \ |
| 82 | + "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" \ |
| 83 | + -o /tmp/Python.tgz; \ |
| 84 | + mkdir -p /tmp/python-src; \ |
| 85 | + tar -xzf /tmp/Python.tgz -C /tmp/python-src --strip-components=1; \ |
| 86 | + rm -f /tmp/Python.tgz; \ |
| 87 | + cd /tmp/python-src; \ |
| 88 | + ./configure --with-ensurepip=install; \ |
| 89 | + make -j"$(nproc)"; \ |
| 90 | + make altinstall; \ |
| 91 | + cd /; \ |
| 92 | + rm -rf /tmp/python-src; \ |
| 93 | + ln -sf "/usr/local/bin/python${PY_MM}" /usr/bin/python; \ |
| 94 | + ln -sf "/usr/local/bin/python${PY_MM}" /usr/bin/python3; \ |
| 95 | + python -m pip install --no-cache-dir --upgrade pip setuptools wheel; \ |
| 96 | + apt-get clean; \ |
| 97 | + rm -rf /var/lib/apt/lists/* |
| 98 | + |
| 99 | +# =============================== |
| 100 | +# PyTorch (cu128) |
| 101 | +# =============================== |
| 102 | +RUN python -m pip install --no-cache-dir \ |
90 | 103 | --index-url https://download.pytorch.org/whl/cu128 \ |
91 | | - torch torchvision torchaudio; \ |
92 | | - pip install --no-cache-dir \ |
93 | | - unsloth \ |
94 | | - jupyterlab jupyter_server ipykernel \ |
| 104 | + torch torchvision torchaudio |
| 105 | + |
| 106 | +# =============================== |
| 107 | +# Unsloth + dependencies |
| 108 | +# =============================== |
| 109 | +RUN python -m pip install --no-cache-dir \ |
| 110 | + unsloth vllm \ |
95 | 111 | huggingface_hub datasets; \ |
96 | | - pip install --no-cache-dir xformers --no-deps; \ |
97 | | - pip install --no-cache-dir triton |
| 112 | + python -m pip install --no-cache-dir xformers --no-deps; \ |
| 113 | + python -m pip install --no-cache-dir triton |
98 | 114 |
|
99 | | -RUN ln -sf "${VENV_PATH}/bin/jupyter" /usr/local/bin/jupyter || true |
| 115 | +# =============================== |
| 116 | +# Jupyter + common tools |
| 117 | +# =============================== |
| 118 | +RUN python -m pip install --no-cache-dir \ |
| 119 | + jupyterlab ipywidgets jupyter-archive notebook==7.3.3 ipykernel |
100 | 120 |
|
101 | | -# ------------------------------- |
102 | | -# Users |
103 | | -# ------------------------------- |
104 | | -RUN set -eux; \ |
105 | | - useradd -m -s /bin/bash -u 1000 unsloth || true; \ |
106 | | - useradd -m -s /bin/bash -u 1001 ubuntu || true; \ |
107 | | - usermod -aG sudo unsloth; \ |
108 | | - usermod -aG sudo ubuntu; \ |
109 | | - echo "unsloth ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/90-unsloth; \ |
110 | | - echo "ubuntu ALL=(ALL) NOPASSWD:ALL" >/etc/sudoers.d/90-ubuntu; \ |
111 | | - chmod 0440 /etc/sudoers.d/90-unsloth /etc/sudoers.d/90-ubuntu; \ |
112 | | - mkdir -p /workspace /workspace/work /workspace/unsloth-notebooks; \ |
113 | | - chown -R unsloth:unsloth /workspace |
114 | | - |
115 | | -# ========================================================= |
116 | | -# Download Unsloth notebooks templates and flatten structure |
117 | | -# ========================================================= |
| 121 | +# =============================== |
| 122 | +# Build-time assertion: verify key packages |
| 123 | +# (unsloth requires GPU at import time, so only check it's installed) |
| 124 | +# =============================== |
| 125 | +RUN python -c "import torch; import jupyterlab; from importlib.metadata import version; print(f'unsloth=={version(\"unsloth\")} torch=={torch.__version__} ok')" |
| 126 | + |
| 127 | +# =============================== |
| 128 | +# Configure JupyterLab |
| 129 | +# =============================== |
| 130 | +RUN mkdir -p /root/.jupyter && printf '%s\n' \ |
| 131 | + 'c.ServerApp.token = "yotta"' \ |
| 132 | + 'c.ServerApp.password = ""' \ |
| 133 | + 'c.ServerApp.allow_remote_access = True' \ |
| 134 | + 'c.ServerApp.allow_origin = "*"' \ |
| 135 | + 'c.NotebookApp.token = "yotta"' \ |
| 136 | + 'c.NotebookApp.password = ""' \ |
| 137 | + 'c.NotebookApp.allow_remote_access = True' \ |
| 138 | + > /root/.jupyter/jupyter_lab_config.py && \ |
| 139 | + chmod 600 /root/.jupyter/jupyter_lab_config.py |
| 140 | + |
| 141 | +# =============================== |
| 142 | +# Download Unsloth notebook templates |
| 143 | +# =============================== |
118 | 144 | RUN set -eux; \ |
119 | 145 | cd /tmp; \ |
120 | 146 | git clone --depth=1 https://github.com/unslothai/notebooks.git unsloth-notebooks-src; \ |
121 | | - \ |
122 | | - # Ensure target directory exists |
123 | 147 | mkdir -p /workspace/unsloth-notebooks; \ |
124 | | - \ |
125 | | - # Copy all scripts from original_template to the target directory |
126 | 148 | cp -a unsloth-notebooks-src/original_template/. /workspace/unsloth-notebooks/; \ |
127 | | - \ |
128 | | - # Also copy template.ipynb (if needed) |
129 | 149 | cp -a unsloth-notebooks-src/template.ipynb /workspace/unsloth-notebooks/ || true; \ |
130 | | - \ |
131 | | - # Fix permissions (Jupyter runs as unsloth user) |
132 | | - chown -R unsloth:unsloth /workspace/unsloth-notebooks; \ |
133 | | - \ |
134 | | - # Cleanup |
135 | 150 | rm -rf /tmp/unsloth-notebooks-src |
136 | 151 |
|
| 152 | +# =============================== |
| 153 | +# CUDA bin convenience |
| 154 | +# =============================== |
| 155 | +RUN ln -sf /usr/local/cuda/bin/* /usr/bin/ || true |
137 | 156 |
|
138 | | -# ========================================================= |
139 | | -# Key fix: do not COPY unsloth-notebooks directly |
140 | | -# instead COPY the entire build context, then copy if present, skip if absent |
141 | | -# ========================================================= |
142 | | -COPY . /__build_context__ |
| 157 | +# =============================== |
| 158 | +# Supervisor dirs |
| 159 | +# =============================== |
| 160 | +RUN mkdir -p /var/log/supervisor /usr/local/bin && \ |
| 161 | + chmod 777 /var/log/supervisor /workspace /var/run /var/lib/nginx && \ |
| 162 | + mkdir -p /run/sshd && \ |
| 163 | + chmod 700 /run/sshd |
| 164 | + |
| 165 | +# =============================== |
| 166 | +# start1.sh |
| 167 | +# =============================== |
| 168 | +COPY --from=scripts start1.sh /start1.sh |
| 169 | +RUN chmod 755 /start1.sh && \ |
| 170 | + sed -i 's/\r$//' /start1.sh |
| 171 | + |
| 172 | +# =============================== |
| 173 | +# nginx / branding |
| 174 | +# =============================== |
| 175 | +COPY --from=proxy nginx.conf /etc/nginx/nginx.conf |
| 176 | +COPY --from=proxy readme.html /usr/share/nginx/html/readme.html |
| 177 | +COPY README.md /usr/share/nginx/html/README.md |
143 | 178 |
|
144 | | -RUN set -eux; \ |
145 | | - if [ -d /__build_context__/unsloth-notebooks ]; then \ |
146 | | - cp -a /__build_context__/unsloth-notebooks/. /workspace/unsloth-notebooks/; \ |
147 | | - chown -R unsloth:unsloth /workspace/unsloth-notebooks; \ |
148 | | - fi; \ |
149 | | - rm -rf /__build_context__ |
150 | | - |
151 | | -# ------------------------------- |
152 | | -# sshd dirs |
153 | | -# ------------------------------- |
154 | | -RUN mkdir -p /var/run/sshd |
155 | | - |
156 | | -# ------------------------------- |
157 | | -# Branding |
158 | | -# ------------------------------- |
159 | 179 | COPY --from=logo yotta.txt /etc/yotta.txt |
160 | 180 | RUN echo 'cat /etc/yotta.txt' >> /root/.bashrc |
161 | 181 |
|
162 | | -# ------------------------------- |
163 | | -# start.sh (must not change) |
164 | | -# ------------------------------- |
165 | | -COPY --from=scripts start.sh /start.sh |
166 | | -RUN chmod 755 /start.sh |
167 | | - |
168 | | -# ------------------------------- |
169 | | -# entry.sh |
170 | | -# ------------------------------- |
171 | | -RUN <<'EOF' |
172 | | -cat > /entry.sh <<'EOENTRY' |
173 | | -#!/usr/bin/env bash |
174 | | -set -euo pipefail |
175 | | -(/usr/sbin/sshd || true) |
176 | | -exec sudo -u unsloth -H bash -lc \ |
177 | | - 'jupyter lab --ip=0.0.0.0 --port=${JUPYTER_PORT:-8888} --no-browser \ |
178 | | - --ServerApp.token="" --ServerApp.password="" \ |
179 | | - --ServerApp.allow_origin="*" \ |
180 | | - --ServerApp.root_dir=/workspace' |
181 | | -EOENTRY |
182 | | -chmod 755 /entry.sh |
183 | | -EOF |
184 | | - |
185 | | -EXPOSE 8888 22 80 |
186 | | - |
187 | | -ENTRYPOINT ["/usr/bin/tini","--"] |
188 | | -CMD ["/start.sh"] |
| 182 | +# =============================== |
| 183 | +# Ports |
| 184 | +# =============================== |
| 185 | +EXPOSE 22 80 8888 |
| 186 | + |
| 187 | +# =============================== |
| 188 | +# Entrypoint: root runs start1.sh |
| 189 | +# =============================== |
| 190 | +USER root |
| 191 | +WORKDIR /root |
| 192 | +CMD ["/bin/bash", "-c", "exec /bin/bash /start1.sh"] |
0 commit comments