feat(auth): support subscription login alongside API key
Make ANTHROPIC_API_KEY optional. Add CLAUDE_CODE_OAUTH_TOKEN pass-through for headless token-based auth (claude setup-token). When neither is set, Claude Code falls back to browser OAuth on port 54545. Add claude-config named volume mounted at ~/.claude/ in both claude and webui services so credentials persist across container runs. Pre-create ~/.claude/ in the Dockerfile so the volume is initialised with correct ownership. Add --service-ports to docker compose run calls to publish port 54545 during CLI sessions.
This commit is contained in:
parent
88805a3c24
commit
ba3730a24d
6 changed files with 90 additions and 19 deletions
20
.env.example
20
.env.example
|
|
@ -1,13 +1,27 @@
|
||||||
# Copy this file to .env and fill in your values.
|
# Copy this file to .env and fill in your values.
|
||||||
# .env is git-ignored — never commit it.
|
# .env is git-ignored — never commit it.
|
||||||
|
|
||||||
# Required: your Anthropic API key
|
# ─── Authentication (choose one) ──────────────────────────────────────────────
|
||||||
ANTHROPIC_API_KEY=sk-ant-...
|
|
||||||
|
# Option 1: Anthropic API key
|
||||||
|
# ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
|
||||||
|
# Option 2: OAuth token from a Claude.ai subscription (1-year validity)
|
||||||
|
# Generate with: claude setup-token (run on your host, not inside the container)
|
||||||
|
# CLAUDE_CODE_OAUTH_TOKEN=...
|
||||||
|
|
||||||
|
# Option 3: No key set — Claude Code will prompt for browser login on first run.
|
||||||
|
# Port 54545 must be reachable from your browser for the OAuth callback.
|
||||||
|
# Run: sbx ports <sandbox-name> --publish 54545:54545/tcp
|
||||||
|
|
||||||
|
# ─── Workspace (CLI mode only) ────────────────────────────────────────────────
|
||||||
|
|
||||||
# Optional: mount a host directory as /workspace inside the Claude container.
|
# Optional: mount a host directory as /workspace inside the Claude container.
|
||||||
# If unset, a named Docker volume is used (fully isolated from the host).
|
# If unset, a named Docker volume is used (fully isolated from the host).
|
||||||
# WORKSPACE_DIR=/absolute/path/to/your/project
|
# WORKSPACE_DIR=/absolute/path/to/your/project
|
||||||
|
|
||||||
# Web interface credentials (required for ./claude.sh web)
|
# ─── Web interface ────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
# Required for ./claude.sh web
|
||||||
# WEBUI_USER=claude
|
# WEBUI_USER=claude
|
||||||
# WEBUI_PASSWORD=changeme
|
# WEBUI_PASSWORD=changeme
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,11 @@ Key Docker network property: `claude-internal` has `internal: true`, meaning Doc
|
||||||
|
|
||||||
The `webui` service reuses `claude/Dockerfile`. Its entrypoint (`claude/webui-entrypoint.sh`) starts `ttyd --credential user:pass claude` instead of `claude` directly.
|
The `webui` service reuses `claude/Dockerfile`. Its entrypoint (`claude/webui-entrypoint.sh`) starts `ttyd --credential user:pass claude` instead of `claude` directly.
|
||||||
|
|
||||||
|
Auth supports three modes (checked at startup by `claude.sh`):
|
||||||
|
- `ANTHROPIC_API_KEY` — API key
|
||||||
|
- `CLAUDE_CODE_OAUTH_TOKEN` — 1-year token from `claude setup-token` (headless-friendly)
|
||||||
|
- Neither set — Claude Code prompts for browser login on first run; port 54545 is published for the OAuth callback. Credentials persist in the `claude-config` named volume.
|
||||||
|
|
||||||
## File Structure
|
## File Structure
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
|
||||||
36
README.md
36
README.md
|
|
@ -47,14 +47,46 @@ Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment
|
||||||
# 1. Clone / copy this repo
|
# 1. Clone / copy this repo
|
||||||
git clone <repo> docker-claude && cd docker-claude
|
git clone <repo> docker-claude && cd docker-claude
|
||||||
|
|
||||||
# 2. Configure credentials
|
# 2. Configure credentials (see Authentication below)
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
$EDITOR .env # set ANTHROPIC_API_KEY (and WEBUI_PASSWORD if using web mode)
|
$EDITOR .env
|
||||||
|
|
||||||
# 3. Make the control script executable
|
# 3. Make the control script executable
|
||||||
chmod +x claude.sh
|
chmod +x claude.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
Three options — pick one and set it in `.env`:
|
||||||
|
|
||||||
|
### Option 1 — API key
|
||||||
|
```bash
|
||||||
|
ANTHROPIC_API_KEY=sk-ant-...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 2 — OAuth token (subscription, headless-friendly)
|
||||||
|
|
||||||
|
Run this **on your host** (not inside the container) to generate a 1-year token:
|
||||||
|
```bash
|
||||||
|
claude setup-token
|
||||||
|
```
|
||||||
|
Then set the printed token in `.env`:
|
||||||
|
```bash
|
||||||
|
CLAUDE_CODE_OAUTH_TOKEN=...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Option 3 — Browser OAuth (interactive)
|
||||||
|
|
||||||
|
Leave both keys unset. On first run, Claude Code will print a login URL.
|
||||||
|
Port 54545 must be reachable from your browser for the OAuth callback:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sbx ports <sandbox-name> --publish 54545:54545/tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run `./claude.sh run` and follow the prompt. Credentials are stored in the
|
||||||
|
`claude-config` Docker volume and reused on every subsequent run.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### CLI mode
|
### CLI mode
|
||||||
|
|
|
||||||
17
claude.sh
Executable file → Normal file
17
claude.sh
Executable file → Normal file
|
|
@ -32,10 +32,13 @@ load_env() {
|
||||||
# shellcheck disable=SC1090
|
# shellcheck disable=SC1090
|
||||||
set -a; source "$env_file"; set +a
|
set -a; source "$env_file"; set +a
|
||||||
fi
|
fi
|
||||||
if [[ -z "${ANTHROPIC_API_KEY:-}" ]]; then
|
if [[ -z "${ANTHROPIC_API_KEY:-}" && -z "${CLAUDE_CODE_OAUTH_TOKEN:-}" ]]; then
|
||||||
error "ANTHROPIC_API_KEY is not set."
|
warn "No ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN found."
|
||||||
error "Copy .env.example → .env and add your key, or export it in your shell."
|
warn "Claude Code will prompt you to authenticate on first run."
|
||||||
exit 1
|
warn " Option 1 (API key): set ANTHROPIC_API_KEY in .env"
|
||||||
|
warn " Option 2 (token): run 'claude setup-token' and set CLAUDE_CODE_OAUTH_TOKEN in .env"
|
||||||
|
warn " Option 3 (browser): run './claude.sh run' and follow the login prompt;"
|
||||||
|
warn " port 54545 must be reachable from your browser."
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +75,7 @@ cmd_start() {
|
||||||
dc up -d proxy # no-op if already healthy; compose waits via depends_on
|
dc up -d proxy # no-op if already healthy; compose waits via depends_on
|
||||||
info "Launching Claude Code..."
|
info "Launching Claude Code..."
|
||||||
# shellcheck disable=SC2046
|
# shellcheck disable=SC2046
|
||||||
dc run --rm $(workspace_flag) claude "$@"
|
dc run --rm --service-ports $(workspace_flag) claude "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_stop() {
|
cmd_stop() {
|
||||||
|
|
@ -89,7 +92,7 @@ cmd_run() {
|
||||||
dc up -d proxy
|
dc up -d proxy
|
||||||
info "Launching Claude Code..."
|
info "Launching Claude Code..."
|
||||||
# shellcheck disable=SC2046
|
# shellcheck disable=SC2046
|
||||||
dc run --rm $(workspace_flag) claude "$@"
|
dc run --rm --service-ports $(workspace_flag) claude "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_update() {
|
cmd_update() {
|
||||||
|
|
@ -115,7 +118,7 @@ cmd_shell() {
|
||||||
load_env
|
load_env
|
||||||
warn "Opening debug shell inside Claude container (non-Claude entrypoint)."
|
warn "Opening debug shell inside Claude container (non-Claude entrypoint)."
|
||||||
# shellcheck disable=SC2046
|
# shellcheck disable=SC2046
|
||||||
dc run --rm --entrypoint /bin/bash $(workspace_flag) claude
|
dc run --rm --service-ports --entrypoint /bin/bash $(workspace_flag) claude
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd_web() {
|
cmd_web() {
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,12 @@ RUN addgroup -g 1000 claude \
|
||||||
# Install Claude Code globally (runs as root for npm -g, then drops)
|
# Install Claude Code globally (runs as root for npm -g, then drops)
|
||||||
RUN npm install -g @anthropic-ai/claude-code
|
RUN npm install -g @anthropic-ai/claude-code
|
||||||
|
|
||||||
# Workspace directory owned by claude user
|
# Workspace and Claude config dir — both owned by claude user.
|
||||||
RUN mkdir -p /workspace && chown claude:claude /workspace
|
# Pre-creating ~/.claude ensures the named volume is initialised with the
|
||||||
|
# correct ownership when first mounted (Docker copies image content into
|
||||||
|
# an empty named volume on first use).
|
||||||
|
RUN mkdir -p /workspace /home/claude/.claude \
|
||||||
|
&& chown -R claude:claude /workspace /home/claude/.claude
|
||||||
|
|
||||||
USER claude
|
USER claude
|
||||||
WORKDIR /workspace
|
WORKDIR /workspace
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ services:
|
||||||
|
|
||||||
# ─── Claude Code CLI container ─────────────────────────────────────────────
|
# ─── Claude Code CLI container ─────────────────────────────────────────────
|
||||||
# No direct internet access. All egress routes through the proxy sidecar.
|
# No direct internet access. All egress routes through the proxy sidecar.
|
||||||
# Run via "docker compose run --rm claude" (managed by claude.sh).
|
# Run via "docker compose run --rm --service-ports claude" (managed by claude.sh).
|
||||||
claude:
|
claude:
|
||||||
build:
|
build:
|
||||||
context: claude/
|
context: claude/
|
||||||
|
|
@ -33,19 +33,25 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- claude-internal # only — no route to the internet
|
- claude-internal # only — no route to the internet
|
||||||
environment:
|
environment:
|
||||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||||
|
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-}
|
||||||
- HTTP_PROXY=http://proxy:3128
|
- HTTP_PROXY=http://proxy:3128
|
||||||
- HTTPS_PROXY=http://proxy:3128
|
- HTTPS_PROXY=http://proxy:3128
|
||||||
- ALL_PROXY=http://proxy:3128
|
- ALL_PROXY=http://proxy:3128
|
||||||
- NO_PROXY=localhost,127.0.0.1
|
- NO_PROXY=localhost,127.0.0.1
|
||||||
|
ports:
|
||||||
|
# OAuth callback — required for browser-based login (claude login)
|
||||||
|
- "0.0.0.0:54545:54545"
|
||||||
|
volumes:
|
||||||
|
- claude-config:/home/claude/.claude
|
||||||
|
# Workspace is injected by claude.sh via --volume flag at run time.
|
||||||
|
# Default: named Docker volume. Override: set WORKSPACE_DIR on the host.
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
cap_drop:
|
cap_drop:
|
||||||
- ALL
|
- ALL
|
||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
# Workspace is injected by claude.sh via --volume flag at run time.
|
|
||||||
# Default: named Docker volume. Override: set WORKSPACE_DIR on the host.
|
|
||||||
|
|
||||||
# ─── Claude Code web interface ─────────────────────────────────────────────
|
# ─── Claude Code web interface ─────────────────────────────────────────────
|
||||||
# Serves Claude Code as a browser terminal via ttyd (port 7681).
|
# Serves Claude Code as a browser terminal via ttyd (port 7681).
|
||||||
|
|
@ -62,7 +68,8 @@ services:
|
||||||
networks:
|
networks:
|
||||||
- claude-internal # only — no route to the internet
|
- claude-internal # only — no route to the internet
|
||||||
environment:
|
environment:
|
||||||
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
|
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
|
||||||
|
- CLAUDE_CODE_OAUTH_TOKEN=${CLAUDE_CODE_OAUTH_TOKEN:-}
|
||||||
- HTTP_PROXY=http://proxy:3128
|
- HTTP_PROXY=http://proxy:3128
|
||||||
- HTTPS_PROXY=http://proxy:3128
|
- HTTPS_PROXY=http://proxy:3128
|
||||||
- ALL_PROXY=http://proxy:3128
|
- ALL_PROXY=http://proxy:3128
|
||||||
|
|
@ -72,7 +79,10 @@ services:
|
||||||
- WEBUI_PORT=7681
|
- WEBUI_PORT=7681
|
||||||
ports:
|
ports:
|
||||||
- "0.0.0.0:7681:7681"
|
- "0.0.0.0:7681:7681"
|
||||||
|
# OAuth callback — required for browser-based login (claude login)
|
||||||
|
- "0.0.0.0:54545:54545"
|
||||||
volumes:
|
volumes:
|
||||||
|
- claude-config:/home/claude/.claude
|
||||||
- claude-web-workspace:/workspace
|
- claude-web-workspace:/workspace
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
|
|
@ -93,5 +103,8 @@ networks:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
# Persists Claude Code auth credentials (~/.claude/) across container runs.
|
||||||
|
# Shared between the CLI and web interface so login carries over.
|
||||||
|
claude-config:
|
||||||
# Persistent workspace for the web interface
|
# Persistent workspace for the web interface
|
||||||
claude-web-workspace:
|
claude-web-workspace:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue