How to Set Up Claude API in a Docker Container
Setting up Claude API access inside a Docker container mainly comes down to two things: getting your API credentials into the container securely, and making sure outbound HTTPS traffic from the container can actually reach the API endpoint. Neither is complicated, but there are a few mistakes — baking keys into images, using plaintext env files committed to git, missing SSL certs in minimal base images — that cause most of the pain developers run into.
This guide walks through a working Dockerfile and docker-compose setup for calling Claude from a containerized app, how to pass credentials safely, and what changes if you're running multiple services that each need API access.
Base image and dependencies
Pick a base image that already has a current CA certificate bundle and a recent runtime. For Node.js:
FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY . .
CMD ["node", "server.js"]
For Python:
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "server.py"]
Both -slim variants include ca-certificates, which you need for outbound TLS calls to any API endpoint. Alpine-based images sometimes don't, and you'll see SSL: CERTIFICATE_VERIFY_FAILED errors until you manually install the package.
Never bake the API key into the image
The most common mistake: putting ENV ANTHROPIC_API_KEY=sk-... or similar directly in the Dockerfile. That key becomes part of the image layer history and will leak if the image is ever pushed to a public or shared registry.
Instead, pass the key at runtime:
docker run -e ANTHROPIC_API_KEY="your-key" -p 3000:3000 my-claude-app
Or with an .env file (kept out of version control via .gitignore):
docker run --env-file .env -p 3000:3000 my-claude-app
In docker-compose.yml:
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
For production deployments on Kubernetes or ECS, use native secrets managers (Kubernetes Secrets, AWS Secrets Manager, Docker Swarm secrets) instead of plain env files. The pattern is the same everywhere: the key never touches the image, only the running container's environment.
Making the API call from inside the container
Once the key is available as an environment variable, your application code reads it like any other secret:
const response = await fetch("https://api.anthropic.com/v1/messages", {
method: "POST",
headers: {
"x-api-key": process.env.ANTHROPIC_API_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-opus-4",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from inside Docker" }],
}),
});
This works fine for a single service. Where it gets more involved is multi-container setups — a worker service, a web service, and maybe a background job runner all needing to call Claude, each requiring their own rate limits, usage tracking, and key rotation policy.
Multi-service setups and key sprawl
In a typical docker-compose stack with several services, you end up either:
- Sharing one API key across every container (hard to attribute usage, hard to revoke access to one service without breaking the others), or
- Issuing separate keys per service and manually tracking rate limits, billing, and rotation for each.
This is where a layer like SubToAPI simplifies things if you're already paying for Claude access and want application-level API keys instead of managing Anthropic credentials directly inside every container. You generate scoped keys (sub_live_...) per service from one dashboard, point each container at https://api.subtoapi.app/v1/messages instead of the raw Anthropic endpoint, and get usage metadata and seat-level tracking without changing your container architecture. The request/response shape is the same, so the Dockerfile and compose setup above don't change — only the base URL and bearer token do:
const response = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "claude-opus-4",
max_tokens: 1024,
messages: [{ role: "user", content: "Hello from inside Docker" }],
}),
});
The same env-var-at-runtime pattern applies: SUBTOAPI_KEY gets injected via docker run -e, --env-file, or your orchestrator's secrets mechanism, never hardcoded. Each container or service gets its own key, so you can revoke or rate-limit one without touching the others. See the /docs/quickstart guide for the full setup and /docs/messages for the request schema.
Networking and health checks
Two things trip people up specifically because of the container boundary:
- Outbound internet access: if your container runs in a locked-down network (common in CI runners or internal clusters), make sure egress to port 443 is allowed for your API provider's domain.
- DNS resolution: minimal images occasionally ship without a working
/etc/resolv.confinside certain network modes. If calls hang rather than fail fast, check DNS resolution first withdocker exec -it <container> getent hosts api.subtoapi.appor the equivalent for your provider.
A simple health check endpoint in your app that pings the API with a minimal request is worth adding to docker-compose.yml so container orchestration tools can detect a broken key or network path before traffic hits it:
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
Streaming responses in a container
If your service streams Claude's output back to clients (e.g., over SSE or WebSocket), make sure your container's HTTP server and any reverse proxy in front of it (nginx, Envoy) don't buffer the response. Buffering defeats the purpose of streaming and adds latency. See /docs/streaming for details on handling streamed chunks correctly if you're using SubToAPI's endpoint, or check your provider's streaming docs otherwise.
questions
Do I need a special base image to call the Claude API from Docker? No. Any standard Linux-based image with current CA certificates (like node:20-slim or python:3.12-slim) works. Alpine images sometimes need ca-certificates installed manually.
How should I pass my API key into the container? Use runtime environment variables via docker run -e, --env-file, or your orchestrator's secrets manager. Never set the key with ENV in the Dockerfile itself, since it gets baked into the image layers.
Can I use one API key across multiple containers in the same stack? You can, but it makes usage tracking and revocation per-service impossible. Issuing separate keys per container, or using a dashboard-managed system like SubToAPI (see /pricing), keeps usage and limits isolated per service.