moved project local resources to an example
This commit is contained in:
69
example/Dockerfile
Normal file
69
example/Dockerfile
Normal file
@ -0,0 +1,69 @@
|
||||
FROM node:22-slim
|
||||
|
||||
# System dependencies:
|
||||
# - curl/ca-certificates: downloads (Claude Code installer, nvm, git-spice)
|
||||
# - git + ripgrep: Claude Code requirements
|
||||
# - build-essential, libssl-dev, libreadline-dev, zlib1g-dev: rbenv/ruby-build deps
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
git \
|
||||
ripgrep \
|
||||
bash \
|
||||
build-essential \
|
||||
libssl-dev \
|
||||
libreadline-dev \
|
||||
zlib1g-dev \
|
||||
libffi-dev \
|
||||
libyaml-dev \
|
||||
libpq-dev \
|
||||
socat \
|
||||
chromium \
|
||||
chromium-driver \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Selenium/Capybara discover Chrome via these env vars.
|
||||
# chromium-driver installs chromedriver at /usr/bin/chromedriver.
|
||||
ENV CHROME_BIN=/usr/bin/chromium
|
||||
ENV CHROMEDRIVER=/usr/bin/chromedriver
|
||||
|
||||
# --- rbenv (installed into home volume on first run, via entrypoint) ---
|
||||
# RBENV_ROOT points into the home volume so the install persists across rebuilds.
|
||||
ENV RBENV_ROOT=/home/coder/.rbenv
|
||||
ENV PATH="$RBENV_ROOT/bin:$RBENV_ROOT/shims:$PATH"
|
||||
|
||||
# Initialise rbenv shims for all bash sessions (no-op if not yet installed)
|
||||
RUN echo '[ -d "$RBENV_ROOT/bin" ] && eval "$(rbenv init - bash)"' >> /etc/bash.bashrc
|
||||
|
||||
# --- nvm (installed into home volume on first run, via entrypoint) ---
|
||||
# NVM_DIR points into the home volume so the install persists across rebuilds.
|
||||
ENV NVM_DIR=/home/coder/.nvm
|
||||
|
||||
# Source nvm for all bash sessions (nvm.sh is a no-op if not yet installed)
|
||||
RUN echo '[ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"' >> /etc/bash.bashrc
|
||||
|
||||
# --- git-spice ---
|
||||
# Releases are named git-spice.Linux-<arch>.tar.gz; uname -m gives the right arch directly.
|
||||
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
|
||||
|
||||
# --- non-root user ---
|
||||
RUN useradd -m -s /bin/bash -u 1001 coder
|
||||
|
||||
# Claude Code native installer lands in ~/.local/bin or ~/.claude/bin
|
||||
ENV PATH="/home/coder/.local/bin:/home/coder/.claude/bin:$PATH"
|
||||
|
||||
COPY --chmod=755 entrypoint.sh /usr/local/bin/entrypoint.sh
|
||||
|
||||
USER coder
|
||||
WORKDIR /code
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
|
||||
CMD ["bash"]
|
||||
40
example/docker-compose.yml
Normal file
40
example/docker-compose.yml
Normal file
@ -0,0 +1,40 @@
|
||||
services:
|
||||
claude-code:
|
||||
build: .
|
||||
image: claude-code:local
|
||||
container_name: claude-code
|
||||
|
||||
volumes:
|
||||
# Fixed home volume — persists Claude Code install, config, and credentials
|
||||
# across container restarts and image rebuilds.
|
||||
- claude-home:/home/coder
|
||||
|
||||
# Swappable project folder — override PROJECT_DIR to point at any directory:
|
||||
# PROJECT_DIR=/path/to/myproject docker compose run --rm claude-code
|
||||
- ${PROJECT_DIR:-./code}:/code
|
||||
|
||||
# Drop all Linux capabilities except NET_BIND_SERVICE, which socat needs
|
||||
# to proxy port 5432 on 127.0.0.1 inside the container.
|
||||
cap_drop:
|
||||
- ALL
|
||||
cap_add:
|
||||
- NET_BIND_SERVICE
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
|
||||
# Allow the container to reach the host's network (e.g. a local postgres).
|
||||
# On Linux, host.docker.internal isn't automatic — this creates it.
|
||||
# On Mac/Windows Docker Desktop it's already available but this is harmless.
|
||||
extra_hosts:
|
||||
- "host.docker.internal:host-gateway"
|
||||
|
||||
# Interactive terminal so `claude` works properly
|
||||
stdin_open: true
|
||||
tty: true
|
||||
|
||||
working_dir: /code
|
||||
|
||||
volumes:
|
||||
claude-home:
|
||||
# Named volume — Docker manages it; survives `docker compose down`
|
||||
# (use `docker compose down -v` to wipe it along with the install)
|
||||
78
example/entrypoint.sh.example
Normal file
78
example/entrypoint.sh.example
Normal file
@ -0,0 +1,78 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
# Install nvm if not already present in the home volume.
|
||||
if [ ! -s "$NVM_DIR/nvm.sh" ]; then
|
||||
echo "nvm not found — running installer..."
|
||||
curl -fsSL https://raw.githubusercontent.com/nvm-sh/nvm/HEAD/install.sh |
|
||||
NVM_DIR="$NVM_DIR" PROFILE=/dev/null bash
|
||||
echo "nvm installed successfully."
|
||||
fi
|
||||
. "$NVM_DIR/nvm.sh"
|
||||
|
||||
# Install rbenv + ruby-build if not already present in the home volume.
|
||||
if [ ! -d "$RBENV_ROOT/bin" ]; then
|
||||
echo "rbenv not found — installing..."
|
||||
git clone --depth=1 https://github.com/rbenv/rbenv.git "$RBENV_ROOT"
|
||||
git clone --depth=1 https://github.com/rbenv/ruby-build.git "$RBENV_ROOT/plugins/ruby-build"
|
||||
echo "rbenv installed successfully."
|
||||
fi
|
||||
eval "$(rbenv init - bash)"
|
||||
|
||||
# Determine the desired Ruby version: prefer .ruby-version in the workspace,
|
||||
# fall back to the rbenv global setting, then a hardcoded default.
|
||||
if [ -f "/code/.ruby-version" ]; then
|
||||
RUBY_VERSION=$(tr -d '[:space:]' </code/.ruby-version)
|
||||
elif [ -f "$RBENV_ROOT/version" ]; then
|
||||
RUBY_VERSION=$(cat "$RBENV_ROOT/version")
|
||||
else
|
||||
RUBY_VERSION="3.3.6"
|
||||
fi
|
||||
|
||||
# Install the Ruby version if it isn't already built.
|
||||
if ! rbenv versions --bare 2>/dev/null | grep -qx "$RUBY_VERSION"; then
|
||||
echo "Ruby $RUBY_VERSION not found — installing (this may take a few minutes)..."
|
||||
rbenv install "$RUBY_VERSION"
|
||||
echo "Ruby $RUBY_VERSION installed."
|
||||
fi
|
||||
rbenv global "$RUBY_VERSION"
|
||||
|
||||
# Ensure bundler is available for this Ruby version.
|
||||
if ! gem list bundler -i &>/dev/null; then
|
||||
gem install bundler --no-document
|
||||
fi
|
||||
|
||||
# Install gem dependencies for the linked workspace so rspec (and other gems)
|
||||
# are available without needing an explicit `bundle install` step.
|
||||
if [ -f "/code/Gemfile" ]; then
|
||||
echo "Gemfile found — running bundle install..."
|
||||
pushd /code
|
||||
# TODO: Elaborate or expand — wire secrets (e.g. a private gem registry token)
|
||||
# in from the environment instead of hardcoding. Compose's auto-loaded .env only
|
||||
# does YAML interpolation (next to the compose file), not container injection;
|
||||
# injecting needs `environment:`/`env_file:`. In the project-local model, point
|
||||
# this at the mounted project's .safeclaude/.env so secrets live with the project.
|
||||
BUNDLE_GEMS__GRAPHQL__PRO="${BUNDLE_GEMS__GRAPHQL__PRO:-}" bundle install
|
||||
popd
|
||||
fi
|
||||
|
||||
# Install Claude Code if not already present in the home volume.
|
||||
# Because the home directory is a volume, this install persists across
|
||||
# container restarts and rebuilds.
|
||||
if ! command -v claude &>/dev/null; then
|
||||
echo "Claude Code not found — running installer..."
|
||||
curl -fsSL https://claude.ai/install.sh | bash
|
||||
echo "Claude Code installed successfully."
|
||||
else
|
||||
echo "Claude Code $(claude --version 2>/dev/null || echo '(version unknown)') ready."
|
||||
fi
|
||||
|
||||
# Proxy host postgres to 127.0.0.1:5432 inside the container so the app can
|
||||
# use the same DATABASE_URL whether running inside or outside Docker.
|
||||
if ! ss -tlnp 2>/dev/null | grep -q ':5432'; then
|
||||
echo "Starting postgres proxy 127.0.0.1:5432 -> host.docker.internal:5432"
|
||||
socat TCP-LISTEN:5432,bind=127.0.0.1,fork,reuseaddr \
|
||||
TCP:host.docker.internal:5432 &
|
||||
fi
|
||||
|
||||
exec "$@"
|
||||
14
example/safeclaude
Executable file
14
example/safeclaude
Executable file
@ -0,0 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $(basename "$0") <path-to-project> [claude-args...]" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROJECT_DIR="$(cd "$1" && pwd)" # resolve to absolute path
|
||||
shift
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0")")" && pwd)"
|
||||
|
||||
PROJECT_DIR="$PROJECT_DIR" docker compose -f "$SCRIPT_DIR/docker-compose.yml" run -w /code --rm claude-code claude "$@"
|
||||
Reference in New Issue
Block a user