DeepSeek Docker Deployment: Compose Setup for the DeepSeek API

DeepSeek Docker deployment guide: a compose file for deepseek-flash and V4-Pro API calls, Docker secrets, GPU setup for R1 distills and a LiteLLM cost proxy.

Tutorials·April 25, 2026·By DS Guide Editorial
DeepSeek Docker deployment tutorial card showing the five steps to containerise both a hosted-API caller and a self-hosted distill: write a Dockerfile that copies the app and pip-installs dependencies, pass DEEPSEEK_API_KEY via environment or secret, use docker-compose for multi-service stacks, enable GPU passthrough via the NVIDIA Container Toolkit for self-hosted distills, and add health checks plus a LiteLLM proxy for cost control.

You want a containerised application that talks to the DeepSeek API, runs the same on your laptop and on a production VM, and does not leak your API key into a Git history. That is what this DeepSeek Docker deployment guide covers — concrete Dockerfiles, a working docker-compose.yml, and the parts that are easy to get wrong (GPU passthrough for local distills, secrets handling, health checks, and proxy patterns for cost control).

By the end you will have a reproducible setup for two scenarios: a Python service calling DeepSeek’s hosted API, and a self-hosted R1 distill model behind an OpenAI-compatible endpoint.

What you will build

Illustration: desk with a monitor showing a terminal running 'docker compose up' with api, worker and redis containers reported healthy, and a laptop showing a compose file; the 'deepseek/deepseek-v4' image name is fictional and this is not a real capture.

Two deployments, both production-shaped:

  • Scenario A — Hosted API client. A small FastAPI service in a container that calls DeepSeek’s hosted POST /chat/completions endpoint using the OpenAI SDK. No GPU required. Works with both current API models, deepseek-flash and deepseek-v4-pro.
  • Scenario B — Self-hosted distill. A vLLM container serving a DeepSeek R1 Distill model behind an OpenAI-compatible API on your own GPU box, with the NVIDIA Container Toolkit. Useful when you need offline inference or strict data residency.

Both scenarios share the same compose file with profiles, so you can bring them up independently.

Prerequisites

  • Docker Engine with the Compose v2 plugin (docker compose, not docker-compose).
  • A DeepSeek API key from the developer console — see get a DeepSeek API key if you have not generated one.
  • For Scenario B: an NVIDIA GPU with at least 24 GB VRAM and the NVIDIA Container Toolkit installed on the host. The 7B distill used below ships about 15.2 GB of bf16 weights, which leaves room for the KV cache on a 24 GB card; the 14B distill’s weights are about 29.5 GB and need a larger GPU.
  • Linux, macOS, or Windows with WSL2. Most of this is identical across them; GPU passthrough is Linux-first.
  • Familiarity with environment variables and a basic Python or Node service. If you are new to the API surface itself, skim the DeepSeek API getting started guide first.

The model IDs you will use

DeepSeek’s API lists two models today: deepseek-flash (served by DeepSeek-V4.1-Flash-1017, the default choice for most workloads and the only current model with image input) and deepseek-v4-pro (served by DeepSeek-V4-Pro-0813). Both have a 1M-token context and up to 384K output tokens.

Thinking mode is a request parameter, not a separate model, and it is on by default. Set the effort with reasoning_effort ("low", "high" or "max") and toggle thinking with extra_body={"thinking": {"type": "enabled"}} or {"type": "disabled"}. The legacy IDs deepseek-chat and deepseek-reasoner were scheduled for retirement on 2026-07-24 at 15:59 UTC and are no longer listed in the official docs; deepseek-v4-flash is still accepted but is now served by DeepSeek-V4.1-Flash-1017. If an older container still sends one of those names, change the model value — base_url stays https://api.deepseek.com. The DeepSeek API documentation guide covers the full request shape.

Project layout

Here is the directory we will build:

deepseek-stack/
├── .env                      # never committed
├── .env.example
├── docker-compose.yml
├── api-client/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── app.py
└── proxy/
    └── litellm.config.yaml   # optional, for Scenario A+

Step 1 — Write the API client image

Illustration: laptop code editor showing a three-line Dockerfile (FROM python:3.12-slim, COPY app/ /app/, CMD python server.py) beside an image-layer size panel; not a real build capture.

Scenario A is a Python 3.12 service. Create api-client/requirements.txt with three pinned dependencies (the latest releases on PyPI as of October 2026):

openai==3.37.0
fastapi==0.162.0
uvicorn[standard]==0.54.0

Then api-client/app.py — a minimal endpoint that forwards a prompt to DeepSeek. Note the explicit base_url; chat requests hit POST /chat/completions, the OpenAI-compatible endpoint. The key is read from a Docker secret file if one is mounted, otherwise from the DEEPSEEK_API_KEY environment variable:

import os
from pathlib import Path

from fastapi import FastAPI, HTTPException
from openai import OpenAI
from pydantic import BaseModel


def load_api_key() -> str:
    secret = Path("/run/secrets/deepseek_api_key")  # Docker secret (Step 2)
    if secret.exists():
        return secret.read_text().strip()
    return os.environ["DEEPSEEK_API_KEY"]


client = OpenAI(
    api_key=load_api_key(),
    base_url="https://api.deepseek.com",
)
MODEL = os.environ.get("DEEPSEEK_MODEL", "deepseek-flash")

app = FastAPI()


class Ask(BaseModel):
    prompt: str
    thinking: bool = False


@app.get("/healthz")
def healthz():
    return {"ok": True}


@app.post("/ask")
def ask(body: Ask):
    kwargs = {
        "model": MODEL,
        "messages": [{"role": "user", "content": body.prompt}],
        "max_tokens": 8192,
    }
    if body.thinking:
        kwargs["reasoning_effort"] = "high"
        kwargs["extra_body"] = {"thinking": {"type": "enabled"}}
    else:
        # Thinking is on by default, so switch it off explicitly.
        kwargs["extra_body"] = {"thinking": {"type": "disabled"}}
        kwargs["temperature"] = 1.3  # DeepSeek's guidance for general conversation
    try:
        resp = client.chat.completions.create(**kwargs)
    except Exception as e:
        raise HTTPException(status_code=502, detail=str(e))
    msg = resp.choices[0].message
    return {
        "content": msg.content,
        "reasoning_content": getattr(msg, "reasoning_content", None),
    }

The API is stateless — each request must carry the full messages array if you want multi-turn context. The web chat keeps session history; the API does not. When thinking is enabled, the response returns reasoning_content alongside the final content. In thinking mode temperature is accepted but has no effect, which is why the sample sets it only on the non-thinking path.

Now api-client/Dockerfile. Use a slim base, run as non-root, and copy requirements.txt before the source so layer caching survives code edits:

FROM python:3.12-slim AS base

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    PIP_NO_CACHE_DIR=1

RUN useradd --create-home --uid 1000 app
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py .
RUN chown -R app:app /app
USER app

EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')" || exit 1

CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8000"]

Step 2 — Handle secrets properly

Never bake API keys into images. Two patterns work; pick one:

  1. Environment variables via .env (development). Create .env.example committed to Git, and a real .env in .gitignore:
    # .env.example
    DEEPSEEK_API_KEY=sk-replace-me
    DEEPSEEK_MODEL=deepseek-flash
    # only needed for the optional LiteLLM proxy (Step 5)
    LITELLM_MASTER_KEY=sk-replace-with-a-long-random-value
  2. Docker secrets (production). Compose mounts each secret as a file at /run/secrets/<secret_name>, which is the path load_api_key() checks first. With Compose v2 this works on a single host without Swarm, using a file: source:
    services:
      api-client:
        secrets:
          - deepseek_api_key
    
    secrets:
      deepseek_api_key:
        file: ./secrets/deepseek_api_key.txt   # keep this file out of Git

For Kubernetes, use a Secret mounted as an env var or projected file. Whatever you do, audit your image with docker history <image> before pushing — keys in build args are visible there.

Step 3 — Compose the stack

Here is the docker-compose.yml that ties everything together. Profiles let you bring up only the API client by default and opt in to the local GPU service when needed. The vLLM service follows vLLM’s own Docker instructions (ipc: host, a mounted Hugging Face cache, an optional HF_TOKEN), and --reasoning-parser deepseek_r1 splits the distill’s reasoning from its answer:

name: deepseek-stack

services:
  api-client:
    build: ./api-client
    image: deepseek-api-client:latest
    env_file: .env
    ports:
      - "8000:8000"
    restart: unless-stopped
    read_only: true
    tmpfs:
      - /tmp
    security_opt:
      - no-new-privileges:true
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/healthz')"]
      interval: 30s
      timeout: 5s
      retries: 3

  litellm:
    image: ghcr.io/berriai/litellm:v1.106.3
    profiles: ["proxy"]
    env_file: .env
    command: ["--config", "/app/config.yaml", "--port", "4000"]
    ports:
      - "4000:4000"
    volumes:
      - ./proxy/litellm.config.yaml:/app/config.yaml:ro
    restart: unless-stopped

  vllm-distill:
    image: vllm/vllm-openai:v0.32.0
    profiles: ["gpu"]
    ipc: host
    ports:
      - "8001:8000"
    volumes:
      - hf-cache:/root/.cache/huggingface
    environment:
      - HF_TOKEN=${HF_TOKEN:-}
    command: >
      --model deepseek-ai/DeepSeek-R1-Distill-Qwen-7B
      --dtype bfloat16
      --max-model-len 32768
      --gpu-memory-utilization 0.90
      --reasoning-parser deepseek_r1
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    restart: unless-stopped

volumes:
  hf-cache:

Bring it up with docker compose up -d --build for Scenario A, or docker compose --profile gpu up -d to add the local distill (Scenario B). The read_only root filesystem and no-new-privileges flag are cheap hardening wins; few applications need to write outside /tmp. Image tags are pinned to the current releases (LiteLLM v1.106.3 and vLLM v0.32.0 as of October 2026); LiteLLM’s deployment docs recommend pinning a version rather than a moving tag.

Step 4 — Verify it worked

Probe the health endpoint and run a real call:

# Health
curl -s http://localhost:8000/healthz

# Round-trip a prompt
curl -s -X POST http://localhost:8000/ask \
  -H "Content-Type: application/json" \
  -d '{"prompt": "Summarise Docker layer caching in two sentences.", "thinking": false}'

For the GPU service, hit it directly — it speaks OpenAI-compatible JSON on port 8001. With the reasoning parser enabled, vLLM returns the distill’s reasoning in a field named reasoning (it used to be called reasoning_content), next to the final content:

curl -s http://localhost:8001/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"deepseek-ai/DeepSeek-R1-Distill-Qwen-7B","messages":[{"role":"user","content":"hi"}]}'

If you want a deeper local-only path without Docker, the running DeepSeek on Ollama walkthrough covers the same R1 Distill family on a different runtime.

Step 5 — Add a proxy for cost control (optional)

Once you have more than one service calling DeepSeek, route them through an internal proxy. LiteLLM is a small Python service that exposes an OpenAI-compatible API and forwards to multiple backends; its DeepSeek provider takes any DeepSeek model name behind a deepseek/ prefix. A minimal proxy/litellm.config.yaml:

model_list:
  - model_name: chat-default
    litellm_params:
      model: deepseek/deepseek-flash
      api_base: https://api.deepseek.com
      api_key: os.environ/DEEPSEEK_API_KEY
  - model_name: chat-pro
    litellm_params:
      model: deepseek/deepseek-v4-pro
      api_base: https://api.deepseek.com
      api_key: os.environ/DEEPSEEK_API_KEY

litellm_settings:
  drop_params: true
  cache: true
  cache_params:
    type: local   # in-memory; without this LiteLLM expects Redis

Bring it up with docker compose --profile proxy up -d. Now your other services point at http://litellm:4000 instead of https://api.deepseek.com and authenticate with the LITELLM_MASTER_KEY (or a virtual key issued from it), and you get centralised logging, retries, and per-team budgets without changing call sites. An in-memory cache lives inside one worker process; LiteLLM’s caching docs recommend Redis once you run more than one worker.

Cost math you should run before deploying

DeepSeek Docker deployment cost title card: eyebrow 'Docker · API cost', title 'API Cost Check', tagline 'deepseek-flash, USD per 1M tokens, October 2026.', and four stats: 0.30 cache miss, peak; 0.15 miss, off-peak; 1.20 output, peak; 0.60 output, off-peak; beside a scales illustration.

Container orchestration does not change pricing — it changes how often you make calls. Before you ship, estimate spend from the three token buckets DeepSeek bills separately: input on a cache hit, input on a cache miss, and output. As of October 2026 the official pricing page lists, per 1M tokens, deepseek-flash at $0.006 cache hit, $0.30 cache miss and $1.20 output in peak hours, and half that off-peak ($0.003, $0.15 and $0.60). deepseek-v4-pro is $0.044, $1.32 and $3.96 at peak, and $0.022, $0.66 and $1.98 off-peak. Peak hours are 01:00–04:00 and 06:00–10:00 UTC, Monday to Friday, excluding Chinese public holidays; every other hour is off-peak.

Two container-specific habits move the bill most: a restart or retry loop that resends a long prompt, and batch jobs scheduled inside peak hours when they could run off-peak. LiteLLM’s DeepSeek docs say its cost tracking applies the off-peak rate automatically. The DeepSeek API pricing reference has worked examples, and the DeepSeek context caching guide explains how cache hits are detected.

Common errors and fixes

Symptom Likely cause Fix
401 Unauthorized from DeepSeek Empty DEEPSEEK_API_KEY at runtime Run docker compose config to confirm the variable is interpolated; check .env sits next to docker-compose.yml
Container exits immediately Healthcheck or app crash on import docker compose logs api-client; pin SDK versions; confirm Python version
could not select device driver "nvidia" NVIDIA Container Toolkit not installed Install nvidia-container-toolkit on the host and restart Docker
Empty content from JSON mode Truncation or missing schema hint Set max_tokens high; include the word “json” plus a short example schema in the prompt — see DeepSeek API JSON mode
Streaming stalls behind a load balancer Proxy buffering server-sent events Disable response buffering on the proxy; for nginx, set proxy_buffering off
Out-of-memory on vllm-distill Context length too high for VRAM Lower --max-model-len or pick a smaller distill

Production hardening checklist

  • Pin image digests (image: vllm/vllm-openai@sha256:...) for reproducible deploys.
  • Set resource limits with deploy.resources.limits so a runaway container cannot starve the host.
  • Log structured JSON from your application; ship logs with the Docker JSON-file driver or a sidecar.
  • Rotate API keys regularly and use separate keys per environment.
  • Monitor token spend with the LiteLLM proxy or your own middleware — a retry loop can multiply calls far faster than real users do.
  • Back up the Hugging Face cache volume if you self-host; redownloading model weights takes time you do not want during an incident.

Where this fits in your stack

If you are building a chatbot, the same image pattern wraps cleanly into a DeepSeek Discord bot service. For retrieval-augmented workflows, point the container at a vector store and follow the DeepSeek RAG tutorial. Teams shipping internal tools should also read the DeepSeek API best practices notes on retries, idempotency, and timeouts before going live.

Next steps

Diagram: a Docker host with api, worker and redis containers; HTTP traffic enters the api container, which sends a 'V4 call' to the DeepSeek API, and the worker writes to a logs store.
  • Browse other DeepSeek tutorials for adjacent integrations (LangChain, LlamaIndex, VS Code).
  • If you would rather skip Docker for local development, the install DeepSeek locally guide covers bare-metal options.

Frequently asked questions

How do I pass my DeepSeek API key into a Docker container safely?

Use an .env file referenced by env_file: in your compose file for development, and Docker secrets or a Kubernetes Secret in production. Never bake the key into the image with ENV or build args — it stays visible in docker history. For full setup steps see the get a DeepSeek API key guide and the DeepSeek API authentication reference.

What model ID should I use in my Dockerised app today?

Use deepseek-flash (DeepSeek-V4.1-Flash-1017) as the default; it is also the only current API model that accepts images. deepseek-v4-pro (DeepSeek-V4-Pro-0813) is the second option and costs 3.3× as much per output token. The legacy IDs deepseek-chat and deepseek-reasoner were scheduled for retirement on 2026-07-24 at 15:59 UTC, so any image still sending them needs its model value changed. The DeepSeek V4-Flash page covers the Flash line in detail.

Can I run DeepSeek V4 itself inside a Docker container on my own GPU?

Not on a single workstation. The open weights of DeepSeek-V4.1-Flash-1017 are about 510 GB and those of DeepSeek-V4-Pro-0813 about 893 GB on Hugging Face, and vLLM’s recipe for V4.1-Flash lists roughly 614 GB of GPU memory as the minimum. For self-hosted Docker deployments on one GPU, use the smaller DeepSeek R1 Distill family (1.5B to 70B) served by vLLM, and use the hosted API for the current models. The DeepSeek offline setup guide covers the multi-GPU options, and the DeepSeek system requirements page lists VRAM needs by model size.

Does Docker change how DeepSeek’s API behaves at runtime?

No. The container is just a network client. The API is still stateless — your code must resend the full messages array on every POST /chat/completions call to maintain multi-turn context, regardless of where the client runs. For request-shape details see the DeepSeek API documentation and DeepSeek OpenAI SDK compatibility notes.

Why use a LiteLLM proxy container instead of calling DeepSeek directly?

A proxy gives you one place for retries, caching, key rotation, per-team budgets, and structured logs — without editing every service that calls the API. It also makes it simple to fail over to a self-hosted distill or another provider during incidents. For more on retry and timeout patterns, see the DeepSeek API best practices guide alongside other step-by-step DeepSeek guides.

Sources & Methodology

Last source check: October 25, 2026

Primary sources

  • Model cardDeepSeek on Hugging FaceWeights, license, usage notes per modelLast checked: October 25, 2026
  • Official documentationDeepSeek API docs — Your First API CallModel names deepseek-flash / deepseek-v4-pro, base_url, thinking and reasoning_effort fields, key from DEEPSEEK_API_KEYLast checked: October 25, 2026
  • Official documentationDeepSeek API docs — Thinking ModeThinking on by default, extra_body toggle, reasoning_content, temperature has no effect in thinking modeLast checked: October 25, 2026
  • Official documentationDeepSeek pricing pagePeak and off-peak per-1M-token rates, peak hours, 1M context / 384K outputLast checked: October 25, 2026

Technical references

Methodology

Information in this article was checked against the official DeepSeek documentation, the article's cited primary sources, and the deepseekai.guide pricing dataset on the date shown above. Where third-party numbers are quoted, the originating source is linked inline so the reader can verify and re-check.

Data confidence

High for facts cited inline with primary-source links; medium for context narrative.

Editorial note

AI tooling moves quickly. If you spot an outdated number or a broken link, the contact link in the footer is the fastest way to flag it for the editorial team.

Leave a Reply

Your email address will not be published. Required fields are marked *