diff --git a/Dockerfile b/Dockerfile index dd34a9f..486d793 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,26 @@ -FROM node:20-slim +FROM node:24-bookworm-slim RUN apt-get update && apt-get install -y \ - git \ - curl \ - wget \ - jq \ - netcat-openbsd \ - socat \ - iputils-ping \ - iproute2 \ - dnsutils \ - python3 \ + git curl wget jq netcat-openbsd socat \ + iputils-ping iproute2 dnsutils python3 \ + unzip zstd procps lsof psmisc \ && rm -rf /var/lib/apt/lists/* ARG UID=1000 ARG GID=1000 -RUN npm install -g @anthropic-ai/claude-code +RUN GCS="https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases" \ + && VERSION=$(curl -fsSL "$GCS/latest") \ + && CHECKSUM=$(curl -fsSL "$GCS/$VERSION/manifest.json" | jq -r '.platforms["linux-x64"].checksum') \ + && curl -fsSL "$GCS/$VERSION/linux-x64/claude" -o /tmp/claude \ + && echo "$CHECKSUM /tmp/claude" | sha256sum -c \ + && mv /tmp/claude /usr/local/bin/claude \ + && chmod +x /usr/local/bin/claude -RUN groupmod -g $GID node && usermod -u $UID -g $GID -l claude node && usermod -d /home/claude -m claude +RUN (userdel node 2>/dev/null || true) \ + && (groupdel node 2>/dev/null || true) \ + && groupadd -g $GID claude \ + && useradd -u $UID -g $GID -m -s /bin/bash claude USER claude WORKDIR /workspace diff --git a/dockerfile.md b/dockerfile.md new file mode 100644 index 0000000..c14ffff --- /dev/null +++ b/dockerfile.md @@ -0,0 +1,122 @@ +# Dockerfile Notes + +## Base image + +```dockerfile +FROM node:24-bookworm-slim +``` + +Uses the official Node.js 24 image on Debian Bookworm (slim variant). Node.js is required because `claude` is a Node.js application distributed as a self-contained binary. + +The slim variant strips documentation and locale files to keep the image small, while still including everything needed to run Node apps. + +**Note:** This image ships with a `node` user and `node` group, both at UID/GID 1000. This is a convention in official Docker images — providing a ready-made unprivileged user so containers don't run as root by default. We replace this user with `claude` (see below). + +--- + +## System packages + +```dockerfile +RUN apt-get update && apt-get install -y \ + git curl wget jq netcat-openbsd socat \ + iputils-ping iproute2 dnsutils python3 \ + unzip zstd procps lsof psmisc \ + && rm -rf /var/lib/apt/lists/* +``` + +Tools installed for use inside the container: + +| Package | Purpose | +|---|---| +| `git` | Version control | +| `curl`, `wget` | HTTP requests / file downloads | +| `jq` | JSON processing on the command line | +| `netcat-openbsd` | TCP/UDP connection testing (`nc`) | +| `socat` | Multipurpose socket relay | +| `iputils-ping` | `ping` command | +| `iproute2` | `ip` command, network inspection | +| `dnsutils` | `dig`, `nslookup` | +| `python3` | Scripting | +| `unzip` | Zip archive extraction | +| `zstd` | Zstandard compression/decompression | +| `procps` | `ps`, `top`, process inspection | +| `lsof` | List open files / sockets | +| `psmisc` | `fuser`, `killall`, `pstree` | + +The `rm -rf /var/lib/apt/lists/*` at the end discards the apt package index so it doesn't bloat the image layer. + +--- + +## UID/GID build args + +```dockerfile +ARG UID=1000 +ARG GID=1000 +``` + +Build-time arguments that control which UID and GID the `claude` user gets inside the container. The defaults are 1000/1000 (the most common primary user on Linux desktops). + +The purpose is to match the host system's user, so that files written inside the container (e.g. in a mounted workspace volume) are owned by the correct user on the host — avoiding permission issues. + +Pass these at build time to override: + +```sh +docker build --build-arg UID=$(id -u) --build-arg GID=$(id -g) . +``` + +--- + +## Claude binary installation + +```dockerfile +RUN GCS="https://storage.googleapis.com/..." \ + && VERSION=$(curl -fsSL "$GCS/latest") \ + && CHECKSUM=$(curl -fsSL "$GCS/$VERSION/manifest.json" | jq -r '.platforms["linux-x64"].checksum') \ + && curl -fsSL "$GCS/$VERSION/linux-x64/claude" -o /tmp/claude \ + && echo "$CHECKSUM /tmp/claude" | sha256sum -c \ + && mv /tmp/claude /usr/local/bin/claude \ + && chmod +x /usr/local/bin/claude +``` + +Downloads the latest `claude` release from Google Cloud Storage: + +1. Fetches the current version string from `latest` +2. Fetches the expected SHA-256 checksum from `manifest.json` +3. Downloads the `linux-x64` binary to `/tmp/claude` +4. Verifies the checksum before installing — build fails if it doesn't match +5. Moves the binary to `/usr/local/bin/claude` and makes it executable + +--- + +## User setup + +```dockerfile +RUN userdel node && groupdel node \ + && groupadd -g $GID claude \ + && useradd -u $UID -g $GID -m -s /bin/bash claude +USER claude +``` + +The base image's `node` user and group are removed first to free up UID/GID 1000 (or whatever was passed in). A `claude` user and group are then created in their place. + +- `-m` creates a home directory at `/home/claude` +- `-s /bin/bash` sets bash as the login shell +- `USER claude` switches all subsequent instructions (and the running container) to this unprivileged user + +--- + +## Workspace and entrypoint + +```dockerfile +WORKDIR /workspace + +CMD ["claude"] +``` + +`/workspace` is the default working directory — intended to be bind-mounted from the host: + +```sh +docker run -it -v "$PWD:/workspace" claude-image +``` + +`CMD ["claude"]` launches the Claude Code CLI when the container starts.