← Blog

Claude API Docker Containerization Guide

2026-10-03 · 5 min read · SubToAPI Team

Containerizing an application that calls the Claude API means packaging your code, runtime, and dependencies into a Docker image that runs consistently in dev, CI, and production — without ever baking your API key into the image itself. This guide walks through building that image, handling secrets safely, and structuring a docker-compose.yml for local development and deployment.

The core challenge isn't Claude-specific — it's the same as containerizing any service that talks to an external HTTPS API: keep secrets out of the image, keep the container stateless, and make sure network egress and timeouts are configured for long-running or streaming requests. Below is a concrete, reproducible setup you can copy into a real project.

Project structure

A minimal Node.js service that proxies requests to Claude (or to an API-compatible layer like SubToAPI) typically looks like this:

app/
├── Dockerfile
├── docker-compose.yml
├── package.json
├── src/
│   └── server.js
└── .dockerignore

Keep node_modules, .env, and .git out of the build context with .dockerignore:

node_modules
.env
.git
*.log

Writing the Dockerfile

Use a multi-stage build to keep the final image small and avoid shipping build tools or dev dependencies into production.

# ---- build stage ----
FROM node:20-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .

# ---- runtime stage ----
FROM node:20-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app /app
EXPOSE 3000
USER node
CMD ["node", "src/server.js"]

Key points:

Handling the API key securely

Never hardcode ANTHROPIC_API_KEY or SUBTOAPI_KEY in the Dockerfile or source code. Pass it in at runtime as an environment variable:

docker run -d \
  --name claude-service \
  -e SUBTOAPI_KEY=sub_live_xxx \
  -p 3000:3000 \
  my-claude-app:latest

For local development, use a .env file that's excluded from version control and loaded via docker-compose.yml:

version: "3.9"
services:
  app:
    build: .
    ports:
      - "3000:3000"
    env_file:
      - .env
    restart: unless-stopped

In production (Kubernetes, ECS, Cloud Run, etc.), inject the key via a secrets manager rather than a plain env var when possible — Docker itself doesn't encrypt environment variables at rest.

Example server code

A simple Express endpoint that calls Claude through SubToAPI's HTTPS interface — containerized the same way you'd containerize a call to the native Anthropic API:

import express from "express";
const app = express();
app.use(express.json());

app.post("/chat", async (req, res) => {
  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-sonnet-4",
      max_tokens: 1024,
      messages: req.body.messages
    })
  });
  const data = await response.json();
  res.json(data);
});

app.listen(3000, () => console.log("listening on 3000"));

This pattern works identically whether you're calling Anthropic directly or routing through SubToAPI, since both speak a compatible HTTPS request/response format. See /docs/messages for the full request schema.

Networking and timeout considerations

Containers add a network hop, so configure timeouts generously, especially for streaming responses:

If you're building a streaming chat interface inside a container, check /docs/streaming for details on handling server-sent events correctly through a proxy layer.

Health checks and graceful shutdown

Add a HEALTHCHECK so orchestrators know when your container is ready to receive traffic:

HEALTHCHECK --interval=30s --timeout=5s \
  CMD wget -qO- http://localhost:3000/health || exit 1

Implement /health as a lightweight endpoint that doesn't call Claude itself — you don't want a transient API outage to take your container out of the load balancer pool.

Handle SIGTERM in your app so in-flight requests (especially streaming ones) finish cleanly before the container stops:

process.on("SIGTERM", () => {
  server.close(() => process.exit(0));
});

Scaling with multiple containers

Since the container itself holds no state and no secrets baked in, scaling horizontally is just a matter of running more replicas behind a load balancer, each pulling the API key from the same secrets source. This is also where centralizing key management pays off — if you're running the same key across dozens of containers, rotating it manually in every environment is error-prone. Tools like SubToAPI let you manage application keys, usage metadata, and team seats from one dashboard rather than tracking raw Anthropic credentials across every container and environment. See /pricing for plan details or /signup to try it.

Build and run checklist

Questions

Do I need to containerize the Claude API itself, or just my app? Just your app. Claude is a hosted API — there's nothing to run locally. You're containerizing the service that calls it, not Claude itself.

Should I store my API key in the Docker image? No. Never COPY or ARG secrets into an image layer, since they remain inspectable in the image history. Inject keys at runtime via environment variables or a secrets manager.

How do I test Claude API calls inside a container locally? Use docker-compose with an .env file holding a test key, mount your source with a volume for hot-reload during development, and call your container's exposed port with curl or Postman to verify responses before deploying.

Turn your Claude access into an HTTPS API

SubToAPI gives you application API keys, streaming, tool use and usage insights on top of your existing Claude access — set up in minutes.

Start free  Read the quickstart →