config / pids / validate / claude_accounts / launch / fork / routes_runs /
routes_accounts, mounted by a thin sidecar.py (same `sidecar:app`, same 24
routes, same startup hook). Mutable state keeps one owner module
(pids._procs, claude_accounts._account_status_cache). Includes the
_claude_args builder from 1de9426 and its test; the Dockerfile's explicit
COPY list gains the new modules.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
76 lines
4.2 KiB
Docker
76 lines
4.2 KiB
Docker
# syntax=docker/dockerfile:1
|
|
# BuildKit `--mount=type=cache` steps below persist the npm/pip/vite/apt caches
|
|
# across builds (survive layer-cache misses), so a dependency bump or a pruned
|
|
# builder re-installs from the local cache instead of re-downloading everything.
|
|
# Requires BuildKit (default with `docker compose build` / buildx here).
|
|
|
|
# ── stage 1: build the React PWA frontend ───────────────────────────────────
|
|
FROM node:20-alpine AS frontend
|
|
WORKDIR /build
|
|
COPY frontend/package.json frontend/package-lock.json* frontend/.npmrc ./
|
|
# @gabvdl/ui now comes from the homelab private registry (verdaccio) instead of a
|
|
# vendored tarball. The .npmrc scopes @gabvdl → http://localhost:4873; this build
|
|
# stage runs with `network: host` (see docker-compose.yml) so that localhost is
|
|
# the host-bound verdaccio. `npm ci` is deterministic (installs exactly the
|
|
# lockfile) and faster than `npm install`; the cache mount keeps the download
|
|
# cache warm across builds so a lockfile change re-links from cache instead of
|
|
# re-fetching all ~250 MB of deps.
|
|
RUN --mount=type=cache,target=/root/.npm npm ci
|
|
COPY frontend/ ./
|
|
# Persist Vite's esbuild dep pre-bundle cache across builds. (The app bundle
|
|
# itself is re-rollup'd on every source change — that ~17s is inherent to a
|
|
# production build and no cache avoids it; this mount only saves the dep
|
|
# optimize pass on cold builds.)
|
|
RUN --mount=type=cache,target=/build/node_modules/.vite npm run build
|
|
|
|
# ── stage 2: FastAPI backend serving the API + the built frontend ───────────
|
|
FROM python:3.12-slim
|
|
WORKDIR /app
|
|
|
|
# git: read each project's last-commit date / recent commits from the mounted repo.
|
|
# curl/ca-certificates: fetch the Claude Code CLI below. ripgrep: the CLI's search.
|
|
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
|
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
|
apt-get update \
|
|
&& apt-get install -y --no-install-recommends git curl ca-certificates ripgrep
|
|
|
|
# ── the in-image runner: the Claude Code CLI itself ─────────────────────────
|
|
# GOAL.md's first gap is folding the host sidecar into the container. Claude Code
|
|
# ships as a self-contained native binary, so the image can just carry it: with
|
|
# RUNNER_IN_CONTAINER=1 the entrypoint runs sidecar/sidecar.py against this binary
|
|
# on the container's loopback, and the image can spawn sessions with no host
|
|
# process at all. The homelab keeps using its host sidecar (nothing changes there).
|
|
#
|
|
# The installer drops a versioned binary under $HOME/.local/share/claude and
|
|
# symlinks it; we resolve the symlink into /usr/local/bin so the CLI is on PATH
|
|
# for whatever uid the container is later told to run as.
|
|
ARG CLAUDE_VERSION=stable
|
|
RUN --mount=type=cache,target=/root/.cache/claude-install \
|
|
HOME=/tmp/claude-install sh -c \
|
|
"curl -fsSL https://claude.ai/install.sh | bash -s -- ${CLAUDE_VERSION}" \
|
|
&& cp -L /tmp/claude-install/.local/bin/claude /usr/local/bin/claude \
|
|
&& chmod 0755 /usr/local/bin/claude \
|
|
&& rm -rf /tmp/claude-install \
|
|
&& claude --version
|
|
|
|
# Deps first, keyed on requirements.txt only, so editing backend code below never
|
|
# re-resolves pip. Cache mount keeps wheels warm across dep bumps / pruned builds.
|
|
COPY backend/requirements.txt /app/requirements.txt
|
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
|
pip install -r /app/requirements.txt
|
|
|
|
COPY backend/ /app/
|
|
# The runner: the same FastAPI wrapper the homelab runs on the host, launched
|
|
# in-container by the entrypoint when RUNNER_IN_CONTAINER=1 (dead weight otherwise
|
|
# — it shares the backend's fastapi/uvicorn, so it costs no extra deps).
|
|
COPY sidecar/sidecar.py sidecar/config.py sidecar/pids.py sidecar/validate.py \
|
|
sidecar/claude_accounts.py sidecar/launch.py sidecar/fork.py \
|
|
sidecar/routes_runs.py sidecar/routes_accounts.py \
|
|
sidecar/claude_cli.py sidecar/feed.py sidecar/pairing.py /app/sidecar/
|
|
COPY --chmod=0755 docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
|
# Built PWA assets served as static files at the web root.
|
|
COPY --from=frontend /build/dist /app/static
|
|
|
|
EXPOSE 8080
|
|
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|