Claude API Docker Container Setup Guide
Running a Claude API integration inside a Docker container is the standard way to make your app portable, reproducible, and ready for deployment to any cloud platform. This guide walks through building a Dockerfile for a Claude-powered service, handling API keys and secrets safely, and avoiding the common networking and build mistakes that trip people up.
The short version: you don't containerize "the Claude API" itself — Anthropic's API is a hosted HTTPS service, not something you run locally. What you containerize is your application that calls that API. The setup guide below covers a Node.js example, but the same pattern applies to Python, Go, or any other runtime.
Why containerize a Claude API app
Docker gives you three things that matter for AI-backed services:
- Reproducibility — the same image runs identically in dev, staging, and production, so you don't debug "works on my machine" issues tied to SDK or Node versions.
- Isolation — your API keys, rate-limit logic, and dependencies stay scoped to the container, not leaked into the host environment.
- Easy deployment — once containerized, your app deploys the same way to AWS ECS, Google Cloud Run, Fly.io, Railway, or a bare VM running Docker Compose.
This matters whether you're calling Anthropic's API directly or routing through a proxy like SubToAPI, which exposes your Claude access as a standard HTTPS API with its own sub_live_ key — the container setup is the same either way, since from your app's perspective it's just an HTTP client making requests.
Basic Dockerfile for a Claude API service
Here's a minimal, production-reasonable Dockerfile for a Node.js app that calls a Claude-compatible API:
FROM node:20-slim AS base
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "server.js"]
Key points:
- Use
node:20-slim(or-alpineif you need a smaller image) rather than the fullnodeimage — you don't need build tools at runtime. - Run
npm ciinstead ofnpm installfor deterministic builds frompackage-lock.json. - Copy
package*.jsonbefore the rest of the source so Docker caches the dependency layer and only reinstalls when dependencies change.
For multi-stage builds (recommended if you use TypeScript or bundlers):
FROM node:20-slim AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-slim AS runtime
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY package*.json ./
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/server.js"]
This keeps your final image lean since dev dependencies and build tooling never ship.
Handling the API key safely
Never bake your API key into the image with ENV in the Dockerfile — it ends up permanently in the image layers and in docker history. Instead, pass it at runtime:
docker run -d \
-e SUBTOAPI_KEY=sub_live_xxx \
-p 3000:3000 \
my-claude-app:latest
Or with a .env file and Docker Compose:
version: "3.9"
services:
app:
build: .
ports:
- "3000:3000"
env_file:
- .env
# .env (never commit this file)
SUBTOAPI_KEY=sub_live_xxx
Your application code then reads the key from process.env:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic({
apiKey: process.env.SUBTOAPI_KEY,
baseURL: "https://api.subtoapi.app/v1",
});
const message = await client.messages.create({
model: "claude-sonnet-4-5",
max_tokens: 1024,
messages: [{ role: "user", content: "Summarize this in one sentence." }],
});
If you're calling the Anthropic SDK against SubToAPI's gateway instead of Anthropic directly, check /docs/quickstart for the exact base URL and auth header format, and /docs/messages for request/response shapes.
Networking: container to internet
A common mistake is assuming a container has no outbound internet access by default — it does, unless you've explicitly restricted it with a custom network or firewall rule. Claude API calls go out over HTTPS on port 443, so standard Docker networking (bridge mode) works without extra configuration. Things to verify if requests fail from inside a container:
- DNS resolution works inside the container (
docker exec -it <container> nslookup api.subtoapi.app). - No corporate proxy or VPN on the host is blocking outbound traffic that the container inherits.
- The container's base image includes CA certificates (
ca-certificatespackage) — minimal/distroless images sometimes strip these, causing TLS handshake failures.
Streaming responses from inside a container
If your app streams Claude responses (useful for chat UIs), make sure your reverse proxy or load balancer in front of the container doesn't buffer the response. Nginx, for example, needs proxy_buffering off; on the relevant location block, otherwise streamed tokens arrive in one burst instead of incrementally. See /docs/streaming for the server-sent-events format if you're consuming a streaming endpoint.
Health checks and graceful shutdown
Add a HEALTHCHECK so orchestrators (Docker Compose, ECS, Kubernetes) know when your app is actually ready to serve traffic, not just that the process started:
HEALTHCHECK --interval=30s --timeout=5s \
CMD curl -f http://localhost:3000/health || exit 1
And handle SIGTERM in your app so in-flight Claude API calls aren't dropped mid-stream when the container is scaled down or redeployed:
process.on("SIGTERM", () => {
server.close(() => process.exit(0));
});
A note on API key management across environments
If you run multiple containers across dev, staging, and production, resist the temptation to share one API key across all of them — it makes usage tracking and incident response much harder. SubToAPI lets you issue separate sub_live_ keys per environment or team member from one dashboard, with usage metadata per key, which is a cleaner fit for containerized multi-environment setups than managing raw Anthropic keys by hand. See /signup if you want to try that approach alongside your existing Docker setup.
questions
Do I need Docker to use the Claude API? No. Docker is optional — it's useful for packaging and deploying your application consistently, but you can call the API from any environment, local or otherwise.
Can I run Claude models locally inside my container? No. Claude is a hosted model accessed via HTTPS API calls; your container runs your application code that makes those calls, not the model itself.
Why is my container getting TLS or connection errors to the API? Usually missing CA certificates in a minimal base image, DNS failures inside the container network, or an outbound firewall/proxy on the host blocking port 443 traffic.