# 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.