Files
safeclaude/Dockerfile.base
2026-06-20 17:05:09 -04:00

62 lines
3.0 KiB
Docker

# safeclaude-base — the shared base every project builds on. Keep this generic:
# project-specific packages and language versions go in each project's own
# .safeclaude/Dockerfile (which starts with `FROM safeclaude-base`), not here.
#
# This image runs as root so project Dockerfiles can install system packages
# freely. Don't worry — the launcher locks things down at run time (normal user,
# no special privileges), so nothing Claude does actually runs as root.
#
# Debian (slim) as the base: small, stable, and its apt packages behave
# predictably in a container — notably `chromium` is a real package here, unlike
# on Ubuntu where it's a snap stub that won't run in a container.
FROM debian:bookworm-slim
# Just the basics every project needs:
# - curl/ca-certificates: for downloads (the Claude installer, git-spice)
# - git + ripgrep: required by Claude Code
# We leave apt's package lists in place (no cleanup) so a project Dockerfile can
# install packages without re-fetching them — though it should still run its own
# `apt-get update` first, since this base image may be days or weeks old.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
git \
ripgrep \
bash
# --- git-spice (a tool for stacked pull requests, handy in the Claude workflow) ---
# Releases are named git-spice.Linux-<arch>.tar.gz, and `uname -m` gives the arch.
RUN ARCH=$(uname -m) && \
GS_VERSION=$(curl -fsSL https://api.github.com/repos/abhinav/git-spice/releases/latest \
| grep '"tag_name"' | sed 's/.*"v\([^"]*\)".*/\1/') && \
mkdir -p /tmp/gs-install && \
curl -fsSL "https://github.com/abhinav/git-spice/releases/download/v${GS_VERSION}/git-spice.Linux-${ARCH}.tar.gz" \
| tar -xz -C /tmp/gs-install && \
find /tmp/gs-install -maxdepth 1 -type f -executable -exec cp {} /usr/local/bin/gs \; && \
chmod +x /usr/local/bin/gs && \
rm -rf /tmp/gs-install
# --- the user Claude runs as ---
# We create 'coder' with the *host* user's UID/GID (passed in by the launcher).
# This is what makes the bind-mounted project at /code writable: the files there
# keep the host's ownership, so the container can only write them if it runs as
# that same ID. It also means files Claude creates come out owned by you on the
# host — not root or some stray ID. The launcher rebuilds this image if the host
# ID ever changes (it reads the labels below to notice), so the default here is
# just a placeholder for a from-scratch build.
ARG HOST_UID=1000
ARG HOST_GID=1000
RUN if ! getent group "${HOST_GID}" >/dev/null; then groupadd -g "${HOST_GID}" coder; fi && \
useradd -m -s /bin/bash -u "${HOST_UID}" -g "${HOST_GID}" coder
LABEL safeclaude.uid="${HOST_UID}" safeclaude.gid="${HOST_GID}"
# Claude installs itself into one of these folders, so add them to PATH.
ENV PATH="/home/coder/.local/bin:/home/coder/.claude/bin:$PATH"
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh
WORKDIR /code
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
CMD ["bash"]