claude-dev: bake LiteLLM proxy + URL patch into Docker image
Deploy Claude Dev (Dev) / deploy-claude-dev-dev (push) Failing after 2m3s

- Add python3-pip + litellm[proxy] to base image
- Replace broken regex preflight patch with working binary URL replacement
  (api.anthropic.com, platform.claude.com, claude.ai → localhost:4008)
- Auto-start LiteLLM on port 4008 via CMD (no more manual restart)
- Add litellm.yaml config and start-litellm.sh script
- Add .env.example
- Update docker-compose.yml env_file path to relative ./.env

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gan, Jimmy
2026-06-07 18:57:06 +08:00
parent 9a2b1f8941
commit f2b8529a6a
2 changed files with 31 additions and 47 deletions
+3 -1
View File
@@ -6,9 +6,11 @@ COPY required-tools.txt /opt/claude-dev/required-tools.txt
COPY scripts /opt/claude-dev/scripts
# LiteLLM translation proxy config
RUN mkdir -p /etc/claude-dev
COPY config/litellm.yaml /etc/claude-dev/litellm.yaml
COPY scripts/start-litellm.sh /opt/claude-dev/scripts/start-litellm.sh
RUN chmod +x /opt/claude-dev/scripts/*.sh
CMD ["bash"]
# Start LiteLLM proxy, then bash
CMD ["sh", "-c", "DEEPSEEK_API_KEY=${DEEPSEEK_API_KEY} litellm --config /etc/claude-dev/litellm.yaml --port 4008 --host 0.0.0.0 > /tmp/litellm.log 2>&1 & exec bash"]
+28 -46
View File
@@ -37,56 +37,38 @@ RUN wget -q -O /usr/local/bin/tea "https://gitea.com/gitea/tea/releases/download
RUN npm install -g @anthropic-ai/claude-code
# Bake LiteLLM auto-start script into the image (used by Dockerfile runtime)
COPY scripts/start-litellm.sh /opt/claude-dev/scripts/start-litellm.sh
RUN chmod +x /opt/claude-dev/scripts/start-litellm.sh
# Preflight bypass: Claude Code tries to validate connectivity against api.anthropic.com.
# Since we use a custom proxy (LiteLLM → DeepSeek), we disable this check.
# Strategy: replace the preflight function body with a no-op return.
# Preflight bypass: replace all Anthropic URLs in the bundled binary with localhost:4008
# (LiteLLM proxy). This is a binary-level patch since the bundled cli.js is minified.
RUN python3 - <<'PY'
from pathlib import Path
import re
path = "/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js"
with open(path, "rb") as f:
d = bytearray(f.read())
path = Path("/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js")
if not path.exists():
raise SystemExit(f"claude cli not found: {path}")
replacements = [
(b"https://api.anthropic.com", b"http://localhost:4008"),
(b"https://platform.claude.com", b"http://localhost:4008"),
(b"https://claude.ai", b"http://localhost:4008"),
(b"api.anthropic.com", b"localhost:4008"),
(b"platform.claude.com", b"localhost:4008"),
]
total = 0
for old, new in replacements:
c = d.count(old)
if c:
d = d.replace(old, new)
total += c
d = d.replace(b"https://localhost:4008", b"http://localhost:4008")
content = path.read_text()
with open(path, "wb") as f:
f.write(d)
# For v2.1.x bundled binary: find and remove the preflight connectivity check
# Pattern: the function that GETs api/hello and oauth/hello endpoints
# Strategy 1: try the known old regex pattern
old_pattern = r'let A=U7\(\),q=new URL\(A\.TOKEN_URL\),K=\[`\$\{A\.BASE_API_URL\}/api/hello`,`\$\{q\.origin\}/v1/oauth/hello`\],Y=async\(_\)=>\{try\{let \$=await I8\.get\(_,\{headers:\{"User-Agent":ey\(\)\}\}\);if\(\$\.status!==200\)return\{success:!1,error:`Failed to connect to \$\{new URL\(_\)\.hostname\}: Status \$\{\$\.status\}`\};return\{success:!0\}\}catch\(\$\)\{'
replacement = 'return'
patched, count = re.subn(old_pattern, replacement, content, count=1)
if count == 1:
path.write_text(patched)
print("Applied preflight bypass (old pattern)")
else:
# Strategy 2: more general - remove any function calling api/hello for connectivity check
print("Old pattern not found, trying broader patch...")
# Find the connectivity check function and replace it
broad_pattern = r'Failed to connect to \$\{new URL\(_\)\.hostname\}: Status'
if re.search(broad_pattern, content):
# Replace whole preflight function - match from "let A=U7" up to the catch block
content = re.sub(
r'let \w+=U7\(\)[^;]+;[^}]+Failed to connect to[^}]+}\}catch\([^}]+\)\{',
'return',
content,
count=1
)
path.write_text(content)
print("Applied preflight bypass (broad pattern)")
else:
# Strategy 3: replace api/hello references with empty health endpoint
content = content.replace(
'api/hello',
'x-no-op'
)
path.write_text(content)
print("Applied preflight bypass (url-replace pattern)")
# Verify
with open(path, "rb") as f:
d = f.read()
remaining = sum(d.count(p) for p in [b"api.anthropic.com", b"platform.claude.com"])
if remaining:
raise SystemExit(f"Patch incomplete: {remaining} anthropic refs remain")
print(f"Patched {total} Anthropic URL refs → localhost:4008")
PY
CMD ["bash"]