Run Claude Science Linux edition with a third-party API backend in a Docker container, eliminating the need for a direct Anthropic API key.
┌─────────────────────────────────────────┐
│ Docker Container │
│ │
│ ┌──────────────┐ ┌────────────────┐ │
│ │ API Bridge │◄───│ Claude Science │ │
│ │ (:9876) │ │ (:9981) │ │
│ └──────┬───────┘ └────────────────┘ │
│ │ │
└─────────┼────────────────────────────────┘
│
┌────────────┴────────────┐
│ Third-Party API (LLM) │
│ (e.g. SiliconFlow, │
│ Moonshot, DeepSeek) │
└─────────────────────────┘
- claude-science — The official Claude Science Linux binary, configured to route API calls through the internal API bridge.
- API Bridge — Translates Anthropic-style API calls from Claude Science to OpenAI-compatible third-party endpoints (DeepSeek, SiliconFlow, Moonshot, etc.). Based on claude-science-api-bridge by Jyx0208.
The container successfully runs both claude-science and the API bridge. Key accomplishments:
- API routing works — Claude Science routes Anthropic-style API calls through the bridge, which translates them to OpenAI-compatible third-party endpoints. Models are callable via the web UI.
- Environment-based configuration — All API keys and backend options are configurable via environment variables; the entrypoint script (
apply_config) writes them into the bridge'sconfig.jsonat startup. - OAuth token auto-setup — On first run, the entrypoint generates an encryption key and OAuth token for claude-science.
- Multiple backend support — Custom (SiliconFlow, Moonshot, etc.), DeepSeek, and OpenAI backends are all supported. The default is the DeepSeek official API with model
deepseek-v4-flash-vision-exp(displayed as "DeepSeek V4 Flash"), and image requests fall back to the same vision model.
fonts-noto-cjk is installed in the image. On every boot, the entrypoint
idempotently patches the runtime conda env's matplotlib defaults
(scripts/configure-cjk-fonts.py) so Python plots render Chinese with Noto
Sans/Serif/Mono CJK instead of tofu boxes. The code-execution sandbox gives
each spawn a fresh per-workspace MPLCONFIGDIR and a tmpfs $HOME, so the
only matplotlib config read there is the packaged matplotlibrc, which the
script edits in place. R already resolves sans to Noto CJK via fontconfig,
so R plots are unaffected. The script no-ops until claude-science provisions
the conda env on first boot and re-applies on every later boot.
I am not a professional software developer, so the reasons for some of the features below are based on speculation and are for reference only.
- MCP directory connectors unavailable: The error that appears upon entering the settings interface does not seem to cause any appreciable effect in practice. According to the literature review, bioinformatics analysis can be carried out normally.
- The
web_searchtool is unavailable: Compatibility issue: OpenAI-compatible model endpoints do not provide the tool-calling capabilities of Anthropic-compatible endpoints, and therefore cannot use web search. For example, I encounter this issue when using DeepSeek in opencode-go, but when using the Anthropic-compatible endpoint of the official DeepSeek API, web search works normally. - Missing image interpretation config: The current .env example does not explicitly configure image handling policies, which may cause unexpected behavior during runtime.
- Tasks terminate unexpectedly: Tasks running on DeepSeek Flash sometimes terminate unexpectedly mid-execution, whereas DeepSeek Pro experiences this issue far less often.
-
Clone the repo
git clone https://github.com/SilenWang/Claude-Science-Container.git cd Claude-Science-Container -
Configure environment
cp .env.example .env
Edit
.envand set your DeepSeek official API key. The default backend is DeepSeek official API:DEEPSEEK_API_KEY=sk-your-api-key-here DEFAULT_BACKEND=deepseek
The default model is
deepseek-v4-flash-vision-exp(shown as DeepSeek V4 Flash in the model selector), and image requests automatically fall back to the same DeepSeek vision model. -
Build and start
- Without Nvidia GPU (default):
docker compose up -d
- With Nvidia GPU (requires host with Nvidia driver + nvidia-container-toolkit, GPU is mounted into the container via CDI — see GPU Support (via CDI)):
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d
Two compose configurations are provided:
docker-compose.yml— no GPU access (runs anywhere)docker-compose.gpu.yml— CDI override that mounts the host GPU into the container via the CDI devicenvidia.com/gpu=all, on top of the base file
- Without Nvidia GPU (default):
-
Open Claude Science
- Check the container logs for the login URL, you will find something like
http://localhost:9981/?nonce=token, use this to enter webui of Claude Science in browser.
- Check the container logs for the login URL, you will find something like
The container supports Nvidia GPUs, but the image itself is not a CUDA-flavored Docker image (no nvidia/cuda base, no CUDA toolkit baked in). The Dockerfile is built on plain ubuntu:24.04. Instead, GPU access is provided at runtime by mounting the host GPU into the container through CDI (Container Device Interface). The host's NVIDIA driver, driver libraries, and device nodes are exposed to the container via a CDI device spec, so GPU tools (nvidia-smi, CUDA) work inside the container without shipping a CUDA image.
The docker-compose.gpu.yml override mounts the GPU by declaring the CDI device directly on the service:
services:
claude-science:
devices:
- nvidia.com/gpu=allnvidia.com/gpu=all is a CDI-qualified device name. It is equivalent to passing --device nvidia.com/gpu=all (or --gpus all) to docker run; the GPU device is injected into the container by the CDI runtime rather than preinstalled in the image.
- NVIDIA driver installed on the host (kernel driver +
nvidia-smiworking). - nvidia-container-toolkit installed and Docker configured to use it:
# Debian/Ubuntu sudo apt-get install -y nvidia-container-toolkit sudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker
CDI requires a device spec (nvidia.yaml) describing the host GPU. The nvidia-container-toolkit provides nvidia-ctk to generate it:
-
Generate the CDI spec (one time on the host):
sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml
-
Verify the CDI devices are visible:
nvidia-ctk cdi list # expected output: nvidia.com/gpu=0, nvidia.com/gpu=1, ..., nvidia.com/gpu=allIf the
nvidia.com/gpu=allentry is missing, the spec was not generated correctly — regenerate it before starting the container. -
Start the container with the GPU override:
docker compose -f docker-compose.yml -f docker-compose.gpu.yml up -d
-
Sanity-check inside the container:
docker exec -it claude-science-dev nvidia-smiFor a plain
docker run, the equivalent invocation is:docker run --rm -it --device nvidia.com/gpu=all claude-science-container-dev nvidia-smi
You can also mount a specific GPU by index instead of all of them (e.g. nvidia.com/gpu=0).
claude-science keeps GPU passthrough into its sandbox off unless config.toml sets gpu_enabled = true. The entrypoint writes it automatically based on the ENABLE_GPU env var in .env:
ENABLE_GPU |
Behavior |
|---|---|
auto (default) |
Enable GPU when a GPU device is present in the container (i.e. mounted via CDI) |
true |
Always enable GPU passthrough |
false |
Never enable it |
When enabled, the sandbox gets /dev/nvidia* and /sys/module/nvidia* dev-bound into it. Note that this only applies to new sessions — sessions created before GPU was enabled keep their previous gpu_mode. CUDA/torch are not preinstalled in the sandbox's default Python env; install them as needed.
A Go-based SSH launcher tool that connects to a remote server running the container, sets up port forwarding, automatically retrieves the login URL, and opens the browser.
# Build (requires Go or use pixi)
pixi run build
# or: cd helper && CGO_ENABLED=0 go build -o launcher .
# Configure
cp helper/config.example.json helper/config.json
# Edit config.json with your SSH server details
# Run
./helper/launcher| Field | Description |
|---|---|
ssh_host |
Remote server hostname/IP |
ssh_port |
SSH port (default: 22) |
ssh_user |
SSH user (default: root) |
ssh_key |
Path to SSH private key |
container_id |
Docker container name/ID (default: claude-science) |
port_forwards |
List of {local, remote} port mappings |
The launcher automatically:
- Connects via SSH and sets up port forwarding (default: 9876, 9981)
- Fetches the Claude Science login URL from the container
- Opens the URL in your default browser
- Reconnects automatically on connection loss
If connecting via SSH directly:
ssh -L 9876:127.0.0.1:9876 -L 9981:127.0.0.1:9981 user@your-server
# Then fetch the URL from the container:
docker exec claude-science claude-science urlAll configuration is done via environment variables in .env:
| Variable | Description | Default |
|---|---|---|
CUSTOM_API_KEY |
API key for the custom backend | — |
CUSTOM_BASE_URL |
API base URL (e.g., SiliconFlow, Moonshot) | — |
DEEPSEEK_API_KEY |
API key for DeepSeek official API | — |
DEEPSEEK_BASE_URL |
DeepSeek API base URL | https://api.deepseek.com |
DEEPSEEK_UPSTREAM_MODE |
DeepSeek upstream protocol: openai or anthropic |
anthropic |
OPENAI_API_KEY |
API key for OpenAI backend | — |
OPENAI_BASE_URL |
OpenAI API base URL | https://api.openai.com |
DEFAULT_BACKEND |
Default backend selection: custom, deepseek, openai |
deepseek |
FORCE_MODEL |
Force a specific model name | deepseek-v4-flash-vision-exp |
CUSTOM_UPSTREAM_MODE |
Custom upstream protocol: openai or anthropic |
openai |
INLINE_IMAGE_POLICY |
Image handling: preserve, omit, omit_inline, auto |
preserve |
REASONING_CONTENT_POLICY |
Reasoning content handling: never, preserve, auto |
never |
MODEL_ALIASES |
JSON array of model aliases for multi-model support | [{"id":"claude-haiku-4-5","display_name":"DeepSeek V4 Flash","backend":"deepseek","model":"deepseek-v4-flash-vision-exp"}] |
MODEL_LIST_MODE |
Model list mode: aliases (auto-set when MODEL_ALIASES is used) |
— |
MODEL_MENU_STRATEGY |
Model menu strategy: claude_compatible (auto-set when MODEL_ALIASES is used) |
— |
CUSTOM_MODEL_PATTERN |
Regex pattern for custom backend model matching | — |
DEEPSEEK_MODEL_PATTERN |
Regex pattern for DeepSeek model matching | deepseek|deep-seek |
OPENAI_MODEL_PATTERN |
Regex pattern for OpenAI model matching | ^(gpt-|o1|o3|o4|chatgpt) |
IMAGE_FALLBACK_MODE |
Image fallback mode: auto (fall back to vision model when needed) |
auto |
IMAGE_FALLBACK_BACKEND |
Backend used for image fallback: deepseek, openai, custom |
deepseek |
IMAGE_FALLBACK_MODEL |
Vision model used for image requests | deepseek-v4-flash-vision-exp |
The bridge supports two fundamentally different API modes. Choosing the right one depends on your needs:
| Feature | Custom (OpenAI style) | DeepSeek Anthropic style |
|---|---|---|
| Configuration | CUSTOM_API_KEY + CUSTOM_BASE_URL |
DEEPSEEK_API_KEY + DEEPSEEK_UPSTREAM_MODE=anthropic |
| Protocol conversion | Anthropic → OpenAI (via bridge translation) | Pass-through (direct Anthropic API) |
| Tool calls | ❌ Unsupported — web search, native tool use not available | ✅ Fully supported |
| Model selection | More third-party providers (SiliconFlow, Moonshot, etc.) | Limited to DeepSeek official models |
When using third-party providers like SiliconFlow or Moonshot, the bridge converts Claude Science's Anthropic-format requests to OpenAI-compatible format. During this conversion, Anthropic-specific features — particularly tool calls — are lost. This means:
- The web search tool in Claude Science will not function
- Extended thinking is not available
- Only basic text generation and code execution work
Recommended for: Users who want access to a wide range of models from various providers and don't need web search.
DeepSeek provides an official Anthropic-compatible API endpoint (/anthropic/v1/messages). When this mode is enabled, the bridge passes requests through without format conversion, preserving all Anthropic protocol features:
- The web search tool in Claude Science works normally
- Tool calls and function calling are fully supported
- All Anthropic message features are preserved
Recommended for: Users who need web search, tool use, or want the best compatibility with Claude Science features.
⚠️ Note: When usingDEEPSEEK_UPSTREAM_MODE=anthropic, thethinkingblock is automatically stripped from requests since DeepSeek's Anthropic API does not support extended thinking (thinking.type: "auto"is not a valid value). This does not affect normal functionality.
# Option 1 (default): DeepSeek official API with vision model (web search, tool support)
DEEPSEEK_API_KEY=sk-xxx
DEFAULT_BACKEND=deepseek
DEEPSEEK_UPSTREAM_MODE=anthropic
# Default model deepseek-v4-flash-vision-exp is shown as "DeepSeek V4 Flash"
# (set via MODEL_ALIASES); image requests fall back to it via IMAGE_FALLBACK_*.
# Option 2: Custom OpenAI API (wider model selection, no web search)
CUSTOM_API_KEY=sk-xxx
CUSTOM_BASE_URL=https://api.siliconflow.cn
DEFAULT_BACKEND=custom
MODEL_ALIASES=For more detailed configuration instructions, please refer to the documentation in the claude-science-api-bridge.
| Port | Service | Description |
|---|---|---|
9876 |
API Bridge | Proxy endpoint for third-party API translation |
9981 |
Claude Science | Web UI for Claude Science |
Container data (Claude Science configuration and sessions) is stored in a Docker volume named cs-data, mounted at /root/.claude-science.
When using the DeepSeek official API in Anthropic-compatible mode, you can configure multiple models (e.g., DeepSeek Flash and Pro) to appear in Claude Science's model selector simultaneously. This uses the bridge's model_aliases mechanism instead of FORCE_MODEL.
Set MODEL_ALIASES as a JSON array where each entry maps a Claude-compatible ID to a real backend model. The entrypoint automatically sets model_list_mode=aliases and model_menu_strategy=claude_compatible when MODEL_ALIASES is provided.
Claude Science's model selector will show the display_name from each alias, while actual requests use the mapped model value on the configured backend.
DEEPSEEK_API_KEY=sk-xxx
DEFAULT_BACKEND=deepseek
DEEPSEEK_UPSTREAM_MODE=anthropic
MODEL_ALIASES='[{"id":"claude-haiku-4-5","display_name":"DeepSeek V4 Flash","backend":"deepseek","model":"deepseek-v4-flash-vision-exp"},{"id":"claude-sonnet-5","display_name":"DeepSeek V4 Pro","backend":"deepseek","model":"deepseek-v4-pro"}]'With this configuration, the model selector shows DeepSeek V4 Flash and DeepSeek V4 Pro as two options. When MODEL_ALIASES is set, FORCE_MODEL is ignored (the alias mappings take precedence).
By default (no MODEL_ALIASES override), the container ships with a single alias that shows DeepSeek V4 Flash in the selector while sending deepseek-v4-flash-vision-exp to the DeepSeek official API.
| Field | Description |
|---|---|
id |
Claude-compatible model ID (e.g., claude-sonnet-5, claude-haiku-4-5) |
display_name |
Name shown in Claude Science's model selector |
backend |
Backend to use: deepseek, openai, or custom |
model |
The real model name sent to the API |
If you only need a single model, simply keep using FORCE_MODEL as before — no need to set MODEL_ALIASES.
DEEPSEEK_API_KEY=sk-xxx
DEFAULT_BACKEND=deepseek
DEEPSEEK_UPSTREAM_MODE=anthropic
FORCE_MODEL=deepseek-v4-flash-vision-expIf the selected model is text-only (e.g. DeepSeek) but a request contains an image, the bridge can automatically route that request to a configured vision model (image_fallback_mode=auto). Configure the fallback via env vars:
IMAGE_FALLBACK_MODE=auto
IMAGE_FALLBACK_BACKEND=deepseek
IMAGE_FALLBACK_MODEL=deepseek-v4-flash-vision-expBy default the container uses exactly this setup: text requests go to DeepSeek official API, and image-bearing requests are handled by the DeepSeek vision model deepseek-v4-flash-vision-exp, avoiding errors from text-only models.
This project uses claude-science-api-bridge by Jyx0208 as the API translation layer. All credit for the bridge's backend configuration options and protocol translation logic goes to the original project.
MIT