@@ -63,28 +63,36 @@ RUN maturin build --release
6363
6464
6565# ═══════════════════════════════════════════════════════════════════════
66- # CUDA stages (--extra cu128)
66+ # Shared deps base — everything before the GPU-specific uv sync
6767# ═══════════════════════════════════════════════════════════════════════
6868
69- # ── Deps (CUDA) ──────────────────────────────────────────────────────
70- FROM python:3.12-slim AS deps
69+ FROM python:3.12-slim AS deps-common
7170
7271RUN apt-get update && apt-get install -y --no-install-recommends \
7372 openssh-server tini \
7473 && rm -rf /var/lib/apt/lists/* \
7574 && mkdir -p /run/sshd
7675
77- COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
78-
7976ENV PYTHONUNBUFFERED=1 \
8077 UV_LINK_MODE=copy \
8178 UV_CACHE_DIR=/tmp/uv-cache
8279
83- COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
84-
8580WORKDIR /opt/pawn
8681COPY pyproject.toml uv.lock ./
8782COPY --from=builder /build/engine/target/wheels/*.whl /tmp/
83+
84+ # External binaries last — they don't depend on our layers, so placing
85+ # them here avoids invalidating the layers above on a caddy/uv release.
86+ COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
87+ COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
88+
89+
90+ # ═══════════════════════════════════════════════════════════════════════
91+ # CUDA stages (--extra cu128)
92+ # ═══════════════════════════════════════════════════════════════════════
93+
94+ # ── Deps (CUDA) ──────────────────────────────────────────────────────
95+ FROM deps-common AS deps
8896RUN uv venv && \
8997 uv sync --extra cu128 --no-dev --frozen --no-install-workspace && \
9098 uv pip install /tmp/*.whl && rm -rf /tmp/*.whl ${UV_CACHE_DIR}
@@ -101,24 +109,53 @@ ENV PAWN_GIT_HASH=${GIT_HASH} \
101109 PYTHONPATH=/opt/pawn \
102110 PATH="/opt/pawn/.venv/bin:${PATH}"
103111
112+ RUN chmod +x deploy/entrypoint.sh
104113EXPOSE 8888
105- COPY deploy/entrypoint.sh /opt/pawn/entrypoint.sh
106- RUN chmod +x /opt/pawn/entrypoint.sh
107114ENTRYPOINT ["tini" , "--" ]
108- CMD ["/opt/pawn/entrypoint.sh" ]
115+ CMD ["/opt/pawn/deploy/ entrypoint.sh" ]
109116
110- # ── Dev (CUDA) ───────────────────────────────────────────────────────
111- # Built independently (not FROM runtime) so all /opt/pawn files are
112- # owned by pawn from the start, avoiding a slow chown -R layer.
113- FROM python:3.12-slim AS dev
114117
115- RUN apt-get update && apt-get install -y --no-install-recommends \
118+ # ═══════════════════════════════════════════════════════════════════════
119+ # ROCm stages (--extra rocm)
120+ # Same python:3.12-slim base — the ROCm torch wheel (~2.8 GB) bundles
121+ # HIP, rocBLAS, MIOpen, etc. inside the wheel itself.
122+ # ═══════════════════════════════════════════════════════════════════════
123+
124+ # ── Deps (ROCm) ──────────────────────────────────────────────────────
125+ FROM deps-common AS deps-rocm
126+ RUN uv venv && \
127+ uv sync --extra rocm --no-dev --frozen --no-install-workspace && \
128+ uv pip install /tmp/*.whl && rm -rf /tmp/*.whl ${UV_CACHE_DIR}
129+
130+ # ── Runtime (ROCm) ───────────────────────────────────────────────────
131+ FROM deps-rocm AS runtime-rocm
132+
133+ COPY . .
134+
135+ ARG GIT_HASH=""
136+ ARG GIT_TAG=""
137+ ENV PAWN_GIT_HASH=${GIT_HASH} \
138+ PAWN_GIT_TAG=${GIT_TAG} \
139+ PYTHONPATH=/opt/pawn \
140+ PATH="/opt/pawn/.venv/bin:${PATH}"
141+
142+ RUN chmod +x deploy/entrypoint.sh
143+ EXPOSE 8888
144+ ENTRYPOINT ["tini" , "--" ]
145+ CMD ["/opt/pawn/deploy/entrypoint.sh" ]
146+
147+
148+ # ═══════════════════════════════════════════════════════════════════════
149+ # Shared dev base — dev tools, non-root user, Claude Code, tmux
150+ # ═══════════════════════════════════════════════════════════════════════
151+
152+ FROM python:3.12-slim AS dev-common
153+
154+ RUN apt-get update && apt-get install -y --no-install-recommends build-essential \
116155 openssh-server tini tmux ripgrep jq curl git \
117156 && rm -rf /var/lib/apt/lists/* \
118157 && mkdir -p /run/sshd
119158
120- COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
121-
122159ENV PYTHONUNBUFFERED=1 \
123160 UV_LINK_MODE=copy \
124161 UV_CACHE_DIR=/tmp/uv-cache
@@ -134,31 +171,18 @@ set -g renumber-windows on
134171set -g set-clipboard on
135172TMUX
136173
137- COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
138-
139- # Create non-root user, then copy installed deps with correct ownership
174+ # Create non-root user
140175RUN useradd -m -s /bin/bash pawn && \
141176 mkdir -p /opt/pawn && chown pawn:pawn /opt/pawn
142- COPY --from=deps --chown=pawn:pawn /opt/pawn /opt/pawn
143177
144- # Source code + entrypoint
178+ # Install Claude Code and Rust toolchain (for building the chess engine)
145179USER pawn
146- WORKDIR /opt/pawn
147- COPY --chown=pawn:pawn . .
148-
149- # Install Claude Code
150180RUN curl -fsSL https://claude.ai/install.sh | bash
181+ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
151182
152- ARG GIT_HASH=""
153- ARG GIT_TAG=""
154- ENV PAWN_GIT_HASH=${GIT_HASH} \
155- PAWN_GIT_TAG=${GIT_TAG} \
156- PYTHONPATH=/opt/pawn \
157- PATH="/home/pawn/.local/bin:/opt/pawn/.venv/bin:${PATH}"
158-
159- # Convenience script: drop into pawn user with claude in a tmux session.
183+ # Convenience script: drop into pawn user with claude in a tmux session
160184USER root
161- COPY <<'CLAUDE_DEV' /usr/local/bin/claude-dev
185+ COPY --chmod=755 <<'CLAUDE_DEV' /usr/local/bin/claude-dev
162186# !/usr/bin/env bash
163187set -euo pipefail
164188SESSION="claude"
@@ -171,127 +195,65 @@ exec su - pawn -c "
171195 exec tmux attach -t $SESSION
172196"
173197CLAUDE_DEV
174- RUN chmod +x /usr/local/bin/claude-dev
175198
176- EXPOSE 8888
177- COPY deploy/entrypoint.sh /opt/pawn/entrypoint.sh
178- RUN chmod +x /opt/pawn/entrypoint.sh
179- ENTRYPOINT ["tini" , "--" ]
180- CMD ["/opt/pawn/entrypoint.sh" ]
199+ # External binaries last (same rationale as deps-common)
200+ COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
201+ COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
181202
182203
183204# ═══════════════════════════════════════════════════════════════════════
184- # ROCm stages (--extra rocm)
185- # Same python:3.12-slim base — the ROCm torch wheel (~2.8 GB) bundles
186- # HIP, rocBLAS, MIOpen, etc. inside the wheel itself.
205+ # Dev images — GPU deps + source code on top of dev-common
206+ # Built independently from runtime/deps so every file in /opt/pawn
207+ # enters via COPY --chown=pawn:pawn, avoiding a slow chown -R layer
208+ # that would duplicate the multi-GB venv.
187209# ═══════════════════════════════════════════════════════════════════════
188210
189- # ── Deps (ROCm) ──────────────────────────────────────────────────────
190- FROM python:3.12-slim AS deps-rocm
191-
192- RUN apt-get update && apt-get install -y --no-install-recommends \
193- openssh-server tini \
194- && rm -rf /var/lib/apt/lists/* \
195- && mkdir -p /run/sshd
196-
197- COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
198-
199- ENV PYTHONUNBUFFERED=1 \
200- UV_LINK_MODE=copy \
201- UV_CACHE_DIR=/tmp/uv-cache
202-
203- COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
211+ # ── Dev (CUDA) ───────────────────────────────────────────────────────
212+ FROM dev-common AS dev
213+ COPY --from=deps --chown=pawn:pawn /opt/pawn /opt/pawn
204214
215+ USER pawn
205216WORKDIR /opt/pawn
206- COPY pyproject.toml uv.lock ./
207- COPY --from=builder /build/engine/target/wheels/*.whl /tmp/
208- RUN uv venv && \
209- uv sync --extra rocm --no-dev --frozen --no-install-workspace && \
210- uv pip install /tmp/*.whl && rm -rf /tmp/*.whl ${UV_CACHE_DIR}
211-
212- # ── Runtime (ROCm) ───────────────────────────────────────────────────
213- FROM deps-rocm AS runtime-rocm
217+ COPY --chown=pawn:pawn . .
214218
215- COPY . .
219+ # Build the engine so uv run doesn't trigger a rebuild on first use
220+ RUN PATH="/home/pawn/.cargo/bin:${PATH}" \
221+ uv sync --extra cu128 --frozen
216222
217223ARG GIT_HASH=""
218224ARG GIT_TAG=""
219225ENV PAWN_GIT_HASH=${GIT_HASH} \
220226 PAWN_GIT_TAG=${GIT_TAG} \
221227 PYTHONPATH=/opt/pawn \
222- PATH="/opt/pawn/.venv/bin:${PATH}"
228+ PATH="/home/pawn/.cargo/bin:/home/pawn/.local/bin:/ opt/pawn/.venv/bin:${PATH}"
223229
230+ USER root
231+ RUN chmod +x /opt/pawn/deploy/entrypoint-dev.sh /opt/pawn/deploy/entrypoint.sh
224232EXPOSE 8888
225- COPY deploy/entrypoint.sh /opt/pawn/entrypoint.sh
226- RUN chmod +x /opt/pawn/entrypoint.sh
227233ENTRYPOINT ["tini" , "--" ]
228- CMD ["/opt/pawn/entrypoint.sh" ]
234+ CMD ["/opt/pawn/deploy/ entrypoint-dev .sh" ]
229235
230236# ── Dev (ROCm) ───────────────────────────────────────────────────────
231- FROM python:3.12-slim AS dev-rocm
232-
233- RUN apt-get update && apt-get install -y --no-install-recommends \
234- openssh-server tini tmux ripgrep jq curl git \
235- && rm -rf /var/lib/apt/lists/* \
236- && mkdir -p /run/sshd
237-
238- COPY --from=caddy /usr/local/bin/caddy /usr/local/bin/caddy
239-
240- ENV PYTHONUNBUFFERED=1 \
241- UV_LINK_MODE=copy \
242- UV_CACHE_DIR=/tmp/uv-cache
243-
244- # Developer-friendly tmux defaults
245- RUN cat <<'TMUX' > /etc/tmux.conf
246- set -g mouse on
247- set -g history-limit 50000
248- set -g default-terminal "tmux-256color"
249- set -g base-index 1
250- setw -g pane-base-index 1
251- set -g renumber-windows on
252- set -g set-clipboard on
253- TMUX
254-
255- COPY --from=ghcr.io/astral-sh/uv:0.10 /uv /uvx /bin/
256-
257- # Create non-root user, then copy installed deps with correct ownership
258- RUN useradd -m -s /bin/bash pawn && \
259- mkdir -p /opt/pawn && chown pawn:pawn /opt/pawn
237+ FROM dev-common AS dev-rocm
260238COPY --from=deps-rocm --chown=pawn:pawn /opt/pawn /opt/pawn
261239
262- # Source code + entrypoint
263240USER pawn
264241WORKDIR /opt/pawn
265242COPY --chown=pawn:pawn . .
266243
267- # Install Claude Code
268- RUN curl -fsSL https://claude.ai/install.sh | bash
244+ # Build the engine so uv run doesn't trigger a rebuild on first use
245+ RUN PATH="/home/pawn/.cargo/bin:${PATH}" \
246+ uv sync --extra rocm --frozen
269247
270248ARG GIT_HASH=""
271249ARG GIT_TAG=""
272250ENV PAWN_GIT_HASH=${GIT_HASH} \
273251 PAWN_GIT_TAG=${GIT_TAG} \
274252 PYTHONPATH=/opt/pawn \
275- PATH="/home/pawn/.local/bin:/opt/pawn/.venv/bin:${PATH}"
253+ PATH="/home/pawn/.cargo/bin:/home/pawn/. local/bin:/opt/pawn/.venv/bin:${PATH}"
276254
277255USER root
278- COPY <<'CLAUDE_DEV' /usr/local/bin/claude-dev
279- # !/usr/bin/env bash
280- set -euo pipefail
281- SESSION="claude"
282- exec su - pawn -c "
283- if tmux has-session -t $SESSION 2>/dev/null; then
284- exec tmux attach -t $SESSION
285- fi
286- tmux new-session -d -s $SESSION -c /opt/pawn
287- tmux send-keys -t $SESSION 'cd /opt/pawn && claude --dangerously-skip-permissions' Enter
288- exec tmux attach -t $SESSION
289- "
290- CLAUDE_DEV
291- RUN chmod +x /usr/local/bin/claude-dev
292-
256+ RUN chmod +x /opt/pawn/deploy/entrypoint-dev.sh /opt/pawn/deploy/entrypoint.sh
293257EXPOSE 8888
294- COPY deploy/entrypoint.sh /opt/pawn/entrypoint.sh
295- RUN chmod +x /opt/pawn/entrypoint.sh
296258ENTRYPOINT ["tini" , "--" ]
297- CMD ["/opt/pawn/entrypoint.sh" ]
259+ CMD ["/opt/pawn/deploy/ entrypoint-dev .sh" ]
0 commit comments