How to Deploy DeerFlow with Docker for Production
Purpose
This post explains DeerFlow’s Docker deployment options for production environments. The key difference is understanding development vs production deployment modes.
Problem
I wanted to deploy DeerFlow to a production server. I ran the Docker setup I used for local development:
make docker-initmake docker-startIt worked on my server. I thought I was done.
Then I noticed strange behavior:
[WARN] Source code mounted in production mode[WARN] Hot-reload enabled - NOT recommended for production[WARN] .env file exposed via volume mountI realized I was running in development mode. My production server was:
- Mounting source code instead of built images
- Enabling hot-reload for debugging
- Exposing configuration files directly
I needed to understand DeerFlow’s actual production deployment approach.
Environment
- Linux server (Ubuntu 22.04+ recommended)
- Docker 24.0+
- Docker Compose 2.20+
- At least 8GB RAM, 16GB recommended
- LLM API keys (OpenAI, Anthropic, or compatible)
Solution: Production Deployment Mode
DeerFlow provides two distinct Docker deployment modes:
| Mode | Command | Use Case |
|---|---|---|
| Development | make docker-init && make docker-start | Local development, hot-reload |
| Production | make up | Production, optimized builds |
I’ll explain both, focusing on production.
Architecture Overview
Before deploying, I needed to understand the architecture:
┌─────────────────────────────────────┐ │ Nginx (Port 2026) │ │ Reverse Proxy │ └───────────────┬─────────────────────┘ │ ┌───────────────────────┼───────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │ Frontend │ │ Gateway │ │ LangGraph │ │ (Next.js) │ │ (FastAPI) │ │ Server │ │ Port 3000 │ │ Port 8001 │ │ Port 2024 │ └───────────────┘ └───────────────┘ └───────────────┘ │ ▼ ┌───────────────┐ │ Sandbox │ │ (Container) │ └───────────────┘Nginx routes requests like this:
/api/langgraph/* → LangGraph Server (2024)/api/* (other) → Gateway API (8001)/ (non-API) → Frontend (3000)Understanding this helped me configure production correctly.
Development Mode (What I Was Doing Wrong)
Development mode uses make docker-start. This:
- Mounts source code for hot-reload
- Mounts
config.yamland.envfor live config changes - Starts provisioner only if using Kubernetes sandbox
# One-time setupmake docker-init # Pull sandbox image
# Start services (development mode)make docker-startThis is great for development but wrong for production because:
- Source code is mounted, not built into images
- Hot-reload adds overhead
- Configuration changes apply immediately (risky in production)
Production Mode (The Right Way)
Production mode uses make up. This:
- Builds Docker images from Dockerfiles
- Mounts runtime config only (not source)
- Uses production-optimized settings
# Build and start all servicesmake up
# Stop all servicesmake downThe make up command:
- Builds optimized images with multi-stage Dockerfiles
- Removes development dependencies from final images
- Sets production environment variables
- Uses gunicorn/uvicorn workers for better performance
Step-by-Step Production Deployment
Step 1: Prepare Configuration
I created my production config:
# LLM Provider Configurationmodels: - name: gpt-4o display_name: GPT-4o use: langchain_openai:ChatOpenAI model: gpt-4o api_key: $OPENAI_API_KEY max_tokens: 4096
# Sandbox Configuration (Docker mode for production)sandbox: use: deerflow.community.aio_sandbox:AioSandboxProvider image: ghcr.io/bytedance/deer-flow-sandbox:latest
# Optional: Kubernetes sandbox for scaling# sandbox:# use: deerflow.community.aio_sandbox:AioSandboxProvider# provisioner_url: http://provisioner:8002For sandbox, I chose Docker mode for isolation:
# Local mode: Fast but less isolated (NOT for production)sandbox: use: deerflow.sandbox.local:LocalSandboxProvider
# Docker mode: Each thread gets isolated container (RECOMMENDED)sandbox: use: deerflow.community.aio_sandbox:AioSandboxProvider image: ghcr.io/bytedance/deer-flow-sandbox:latest
# Kubernetes mode: Scalable multi-user (for enterprise)sandbox: use: deerflow.community.aio_sandbox:AioSandboxProvider provisioner_url: http://provisioner:8002Step 2: Set Environment Variables
I created my .env file:
# Required: LLM API keysOPENAI_API_KEY=sk-proj-your-key-here
# Optional: Additional providersANTHROPIC_API_KEY=sk-ant-your-key-hereDEEPSEEK_API_KEY=your-deepseek-key
# Optional: Tool API keysTAVILY_API_KEY=tvly-your-keyGITHUB_TOKEN=ghp_your-token
# Optional: IM channels for notificationsTELEGRAM_BOT_TOKEN=your-bot-tokenSLACK_BOT_TOKEN=xoxb-your-tokenSLACK_APP_TOKEN=xapp-your-tokenStep 3: Build and Deploy
# Build and start all servicesmake upI got this output:
Building services...[+] Building 45.2s => [frontend internal] load build definition => [frontend internal] load .dockerignore => [backend internal] load build definition => CACHED [backend 1/5] FROM python:3.12-slim => [backend 2/5] RUN pip install uv => [backend 3/5] COPY pyproject.toml uv.lock ./ => [backend 4/5] RUN uv sync --frozen => [backend 5/5] COPY . .
Starting services...[+] Running 5/5 Network deer-flow_default Created Container deer-flow-nginx Started Container deer-flow-frontend Started Container deer-flow-gateway Started Container deer-flow-langgraph Started Container deer-flow-sandbox Started
DeerFlow production deployment running at http://localhost:2026Step 4: Verify Deployment
I created a health check script:
#!/bin/bash
echo "Checking DeerFlow services..."
# Check nginxcurl -sf http://localhost:2026/health || echo "Nginx: FAILED"
# Check gatewaycurl -sf http://localhost:8001/health || echo "Gateway: FAILED"
# Check langgraph (via nginx proxy)curl -sf http://localhost:2026/api/langgraph/ok || echo "LangGraph: FAILED"
# Check modelscurl -sf http://localhost:2026/api/models || echo "Models: FAILED"
echo "Health check complete."Running it:
Checking DeerFlow services...{"status":"healthy"}Nginx: OK{"status":"healthy"}Gateway: OK{"status":"ok"}LangGraph: OK{"models":["gpt-4o","claude-3-5-sonnet"]}Models: OKHealth check complete.Docker Compose Configuration
DeerFlow uses Docker Compose under the hood. Here’s a simplified view:
services: nginx: image: nginx:alpine ports: - "2026:2026" depends_on: - frontend - gateway - langgraph volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro
frontend: build: context: ./frontend dockerfile: Dockerfile environment: - NEXT_PUBLIC_BACKEND_BASE_URL=http://gateway:8001
gateway: build: context: ./backend dockerfile: Dockerfile command: make gateway volumes: - ./config.yaml:/app/config.yaml:ro - ./.env:/app/.env:ro
langgraph: build: context: ./backend dockerfile: Dockerfile command: make dev volumes: - ./config.yaml:/app/config.yaml:ro - ./.env:/app/.env:ro
sandbox: image: ghcr.io/bytedance/deer-flow-sandbox:latest # Isolated execution environmentNotice the key differences from development:
- Images are built, not mounted
- Config is read-only (
:ro) - No source code mounts
Production Dockerfile (Backend)
The backend Dockerfile looks like this:
FROM python:3.12-slim
WORKDIR /app
# Install uv package managerRUN pip install uv
# Copy and install dependenciesCOPY pyproject.toml uv.lock ./RUN uv sync --frozen
# Copy application codeCOPY . .
# Run with uvicorn for productionCMD ["uv", "run", "uvicorn", "app.gateway.app:app", "--host", "0.0.0.0", "--port", "8001"]This multi-stage approach ensures:
- Smaller image size (no dev dependencies)
- Faster builds (cached layers)
- Reproducible deployments
Common Issues
Issue 1: Port 2026 Already in Use
I got this error:
Error starting userland proxy: listen tcp 0.0.0.0:2026: bind: address already in useI checked what was using the port:
lsof -i :2026Found another nginx instance. I had two options:
Option 1: Stop the conflicting service
sudo systemctl stop nginxOption 2: Change DeerFlow’s port
services: nginx: ports: - "2080:2026" # Use 2080 externallyIssue 2: Image Build Fails
I got this error during make up:
ERROR: failed to solve: process "/bin/sh -c uv sync" did not complete successfullyI checked the detailed logs:
DOCKER_BUILDKIT=1 docker compose build --progress=plainThe issue was a missing system dependency. I fixed it by updating the Dockerfile:
FROM python:3.12-slim
# Install build dependenciesRUN apt-get update && apt-get install -y \ build-essential \ && rm -rf /var/lib/apt/lists/*
WORKDIR /appRUN pip install uvCOPY pyproject.toml uv.lock ./RUN uv sync --frozenCOPY . .CMD ["uv", "run", "uvicorn", "app.gateway.app:app", "--host", "0.0.0.0", "--port", "8001"]Issue 3: Sandbox Container Fails to Start
I saw this error:
ERROR: Sandbox container failed to startPull access denied for ghcr.io/bytedance/deer-flow-sandboxThe sandbox image requires authentication for private registries. I pulled it manually:
docker pull ghcr.io/bytedance/deer-flow-sandbox:latestIf it’s a private registry, I authenticated:
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdindocker pull ghcr.io/bytedance/deer-flow-sandbox:latestIssue 4: Environment Variables Not Loading
My .env file wasn’t being read. I checked the Docker Compose config:
# WRONG: Mounting as directoryvolumes: - ./.env:/app/.env
# CORRECT: Use env_file directiveenv_file: - .envI also verified the .env file format:
# WRONG: Spaces around equalsOPENAI_API_KEY = sk-proj-xxx
# CORRECT: No spacesOPENAI_API_KEY=sk-proj-xxxDevelopment vs Production Comparison
Here’s what I learned about the key differences:
| Aspect | Development | Production |
|---|---|---|
| Command | make docker-start | make up |
| Images | Pulled pre-built | Built from Dockerfiles |
| Source | Mounted for hot-reload | Built into image |
| Config | Read-write mount | Read-only mount |
| Environment | Debug enabled | Production optimized |
| Sandbox | Local or Docker | Docker or Kubernetes |
| Restart | Manual (for code changes) | On failure only |
Scaling for Production
For multi-user production, I configured Kubernetes sandbox:
sandbox: use: deerflow.community.aio_sandbox:AioSandboxProvider provisioner_url: http://provisioner:8002This requires running the provisioner service:
services: provisioner: image: ghcr.io/bytedance/deer-flow-provisioner:latest ports: - "8002:8002" environment: - KUBERNETES_CONFIG=/app/kubeconfig volumes: - ./kubeconfig:/app/kubeconfig:roThe provisioner creates isolated pods for each thread execution, providing:
- Strong isolation between users
- Horizontal scaling
- Resource limits per execution
Summary
In this post, I explained DeerFlow’s Docker deployment options for production:
- Use
make upfor production deployment, notmake docker-start - Production mode builds optimized images without source mounts
- Configure sandbox mode based on isolation requirements
- Set environment variables properly in
.env - Use Kubernetes sandbox for multi-user production
The key mistake I made was using development mode for production. Understanding the difference between make docker-start (dev) and make up (prod) is essential for secure, performant deployments.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments