Skip to content

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:

my-mistake.sh
make docker-init
make docker-start

It worked on my server. I thought I was done.

Then I noticed strange behavior:

production-issues.txt
[WARN] Source code mounted in production mode
[WARN] Hot-reload enabled - NOT recommended for production
[WARN] .env file exposed via volume mount

I 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:

ModeCommandUse Case
Developmentmake docker-init && make docker-startLocal development, hot-reload
Productionmake upProduction, optimized builds

I’ll explain both, focusing on production.

Architecture Overview

Before deploying, I needed to understand the architecture:

architecture-diagram.txt
┌─────────────────────────────────────┐
│ 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:

nginx-routing.txt
/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:

  1. Mounts source code for hot-reload
  2. Mounts config.yaml and .env for live config changes
  3. Starts provisioner only if using Kubernetes sandbox
development-mode.sh
# One-time setup
make docker-init # Pull sandbox image
# Start services (development mode)
make docker-start

This 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:

  1. Builds Docker images from Dockerfiles
  2. Mounts runtime config only (not source)
  3. Uses production-optimized settings
production-mode.sh
# Build and start all services
make up
# Stop all services
make down

The 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:

config.yaml
# LLM Provider Configuration
models:
- 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:8002

For sandbox, I chose Docker mode for isolation:

sandbox-config.yaml
# 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:8002

Step 2: Set Environment Variables

I created my .env file:

.env
# Required: LLM API keys
OPENAI_API_KEY=sk-proj-your-key-here
# Optional: Additional providers
ANTHROPIC_API_KEY=sk-ant-your-key-here
DEEPSEEK_API_KEY=your-deepseek-key
# Optional: Tool API keys
TAVILY_API_KEY=tvly-your-key
GITHUB_TOKEN=ghp_your-token
# Optional: IM channels for notifications
TELEGRAM_BOT_TOKEN=your-bot-token
SLACK_BOT_TOKEN=xoxb-your-token
SLACK_APP_TOKEN=xapp-your-token

Step 3: Build and Deploy

deploy.sh
# Build and start all services
make up

I got this output:

deploy-output.txt
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:2026

Step 4: Verify Deployment

I created a health check script:

check-health.sh
#!/bin/bash
echo "Checking DeerFlow services..."
# Check nginx
curl -sf http://localhost:2026/health || echo "Nginx: FAILED"
# Check gateway
curl -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 models
curl -sf http://localhost:2026/api/models || echo "Models: FAILED"
echo "Health check complete."

Running it:

health-check-output.txt
Checking DeerFlow services...
{"status":"healthy"}Nginx: OK
{"status":"healthy"}Gateway: OK
{"status":"ok"}LangGraph: OK
{"models":["gpt-4o","claude-3-5-sonnet"]}Models: OK
Health check complete.

Docker Compose Configuration

DeerFlow uses Docker Compose under the hood. Here’s a simplified view:

docker-compose.yml
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 environment

Notice 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:

Dockerfile.backend
FROM python:3.12-slim
WORKDIR /app
# Install uv package manager
RUN pip install uv
# Copy and install dependencies
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
# Copy application code
COPY . .
# Run with uvicorn for production
CMD ["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:

port-conflict-error.txt
Error starting userland proxy: listen tcp 0.0.0.0:2026: bind: address already in use

I checked what was using the port:

check-port.sh
lsof -i :2026

Found another nginx instance. I had two options:

Option 1: Stop the conflicting service

stop-conflict.sh
sudo systemctl stop nginx

Option 2: Change DeerFlow’s port

docker-compose.override.yml
services:
nginx:
ports:
- "2080:2026" # Use 2080 externally

Issue 2: Image Build Fails

I got this error during make up:

build-error.txt
ERROR: failed to solve: process "/bin/sh -c uv sync" did not complete successfully

I checked the detailed logs:

verbose-build.sh
DOCKER_BUILDKIT=1 docker compose build --progress=plain

The issue was a missing system dependency. I fixed it by updating the Dockerfile:

Dockerfile.fix
FROM python:3.12-slim
# Install build dependencies
RUN apt-get update && apt-get install -y \
build-essential \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
RUN pip install uv
COPY pyproject.toml uv.lock ./
RUN uv sync --frozen
COPY . .
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:

sandbox-error.txt
ERROR: Sandbox container failed to start
Pull access denied for ghcr.io/bytedance/deer-flow-sandbox

The sandbox image requires authentication for private registries. I pulled it manually:

pull-sandbox.sh
docker pull ghcr.io/bytedance/deer-flow-sandbox:latest

If it’s a private registry, I authenticated:

registry-auth.sh
echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin
docker pull ghcr.io/bytedance/deer-flow-sandbox:latest

Issue 4: Environment Variables Not Loading

My .env file wasn’t being read. I checked the Docker Compose config:

check-volumes.yml
# WRONG: Mounting as directory
volumes:
- ./.env:/app/.env
# CORRECT: Use env_file directive
env_file:
- .env

I also verified the .env file format:

check-env-format.sh
# WRONG: Spaces around equals
OPENAI_API_KEY = sk-proj-xxx
# CORRECT: No spaces
OPENAI_API_KEY=sk-proj-xxx

Development vs Production Comparison

Here’s what I learned about the key differences:

AspectDevelopmentProduction
Commandmake docker-startmake up
ImagesPulled pre-builtBuilt from Dockerfiles
SourceMounted for hot-reloadBuilt into image
ConfigRead-write mountRead-only mount
EnvironmentDebug enabledProduction optimized
SandboxLocal or DockerDocker or Kubernetes
RestartManual (for code changes)On failure only

Scaling for Production

For multi-user production, I configured Kubernetes sandbox:

kubernetes-sandbox.yaml
sandbox:
use: deerflow.community.aio_sandbox:AioSandboxProvider
provisioner_url: http://provisioner:8002

This requires running the provisioner service:

docker-compose.k8s.yml
services:
provisioner:
image: ghcr.io/bytedance/deer-flow-provisioner:latest
ports:
- "8002:8002"
environment:
- KUBERNETES_CONFIG=/app/kubeconfig
volumes:
- ./kubeconfig:/app/kubeconfig:ro

The 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 up for production deployment, not make 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