This post shows how to set up RAGFlow, the open-source RAG engine, using Docker Compose with Traefik as the reverse proxy — like the Onyx and ERPNext setups before it. The ragflow container bundles an nginx that routes /api and /v1 to the Python API server, /api/v1/admin to the admin server and everything else to the static frontend, so a single Host() router covers the whole application.
Why 0.27 and not 1.0.0-rc1? v0.27.2 is the latest stable release and the last one on the Python deployment model. v1.0.0-rc1 is a preview of a full Go rewrite whose image no longer ships the Python task executor — it needs NATS, Kvrocks and ClickHouse on top of MySQL/MinIO/Redis, migrates your data irreversibly on first start, and still misses DeepDoc-GPU support, the local sandbox and the Team/Me permission model. The setup below stays on the 5-container Python stack.
Directory setup
First create the data directories — all containers fix ownership themselves:
#!/bin/sh
mkdir -p mysql_data minio_data redis_data infinity_data ragflow_logsmkdir -p ragflow && cd ragflow
./init.shMySQL needs an init script that creates the rag_flow database (mounted at /data/application/init.sql):
CREATE DATABASE IF NOT EXISTS rag_flow;
USE rag_flow;And the Infinity doc engine needs its config file (from docker/infinity_conf.toml):
[general]
version = "0.7.3"
time_zone = "utc-8"
[network]
server_address = "0.0.0.0"
postgres_port = 5432
http_port = 23820
client_port = 23817
connection_pool_size = 128
[log]
log_filename = "infinity.log"
log_dir = "/var/infinity/log"
log_to_stdout = true
log_file_max_size = "100MB"
log_file_rotate_count = 10
log_level = "info"
[storage]
persistence_dir = "/var/infinity/persistence"
data_dir = "/var/infinity/data"
optimize_interval = "10s"Configuration
Create a .env file next to docker-compose.yml:
# Domain Settings
DOMAIN=ragflow.mydomain.com
WEB_DOMAIN=https://ragflow.mydomain.com
# RAGFlow Version (v0.22+ images contain no embedding models)
RAGFLOW_IMAGE=infiniflow/ragflow:v0.27.2
# Doc Engine (elasticsearch / infinity / opensearch / seekdb / oceanbase)
DOC_ENGINE=infinity
# Which API server deployment to run (python / go / hybrid)
# REQUIRED: without it the entrypoint starts no server at all!
API_PROXY_SCHEME=python
# Database Credentials
MYSQL_PASSWORD=INSERT_YOUR_PASSWORD_HERE
MYSQL_DBNAME=rag_flow
# MinIO Credentials
MINIO_USER=rag_flow
MINIO_PASSWORD=INSERT_YOUR_PASSWORD_HERE
# Redis/Valkey Password
REDIS_PASSWORD=INSERT_YOUR_PASSWORD_HERE
# Admin server, user admin@ragflow.io
ADMIN_DEFAULT_PASSWORD=INSERT_YOUR_PASSWORD_HERE
# User Registration (1 = enabled, 0 = disabled)
REGISTER_ENABLED=1
ENABLE_REGISTER=1
# Local time zone
TZ=Europe/Berlin
# Traefik Settings
TRAEFIK_ENTRYPOINT=websecure
TRAEFIK_CERTRESOLVER=cloudflareGenerate random passwords with openssl rand -hex 16.
docker-compose.yml
services:
ragflow:
image: ${RAGFLOW_IMAGE}
restart: unless-stopped
command:
- --enable-adminserver
- --init-model-provider-tables
- --init-superuser
ulimits:
nofile:
soft: 65535
hard: 65535
nproc:
soft: 65535
hard: 65535
env_file: .env
volumes:
- ./ragflow_logs:/ragflow/logs
depends_on:
mysql:
condition: service_healthy
minio:
condition: service_healthy
redis:
condition: service_healthy
infinity:
condition: service_healthy
extra_hosts:
- "host.docker.internal:host-gateway"
labels:
- "traefik.enable=true"
- "traefik.http.routers.ragflow.rule=Host(`${DOMAIN}`)"
- "traefik.http.routers.ragflow.entrypoints=${TRAEFIK_ENTRYPOINT}"
- "traefik.http.routers.ragflow.tls.certresolver=${TRAEFIK_CERTRESOLVER}"
- "traefik.http.services.ragflow.loadbalancer.server.port=80"
mysql:
image: mysql:8.0.40
restart: unless-stopped
env_file: .env
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
command:
- --max_connections=1000
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --default-authentication-plugin=mysql_native_password
- --tls_version=TLSv1.2,TLSv1.3
- --init-file=/data/application/init.sql
- --binlog_expire_logs_seconds=604800
- --innodb-buffer-pool-size=1G
- --innodb-io-capacity=2000
- --innodb-io-capacity-max=4000
- --innodb-redo-log-capacity=1G
volumes:
- ./mysql_data:/var/lib/mysql
- ./init.sql:/data/application/init.sql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-uroot", "-p${MYSQL_PASSWORD}"]
interval: 10s
timeout: 10s
retries: 60
minio:
image: pgsty/silo:RELEASE.2026-08-06T00-00-00Z
restart: unless-stopped
command: ["server", "--console-address", ":9001", "/data"]
env_file: .env
environment:
- MINIO_ROOT_USER=${MINIO_USER}
- MINIO_ROOT_PASSWORD=${MINIO_PASSWORD}
volumes:
- ./minio_data:/data
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 10s
timeout: 10s
retries: 60
redis:
image: valkey/valkey:8
restart: unless-stopped
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}", "--maxmemory", "128mb", "--maxmemory-policy", "volatile-lru"]
env_file: .env
volumes:
- ./redis_data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 10s
retries: 60
infinity:
image: infiniflow/infinity:v0.7.3-x64-v3
restart: unless-stopped
command: ["-f", "/infinity_conf.toml"]
env_file: .env
ulimits:
nofile:
soft: 500000
hard: 500000
volumes:
- ./infinity_data:/var/infinity
- ./infinity_conf.toml:/infinity_conf.toml
healthcheck:
test: ["CMD", "curl", "http://localhost:23820/admin/node/current"]
interval: 10s
timeout: 10s
retries: 60Notes on the configuration
No explicit networks. All services run on the compose default network. As long as Traefik joins that network, it can reach ragflow and no extra networks: section or traefik.docker.network label is needed. If your Traefik runs in a separate compose project, either start both files together (docker compose -f docker-compose.yml -f docker-compose.traefik.yml up -d) or point it at the same project with -p ragflow. To reuse a Traefik that is already running elsewhere, attach it to the project network once: docker network connect ragflow_default <traefik-container>.
API_PROXY_SCHEME=python is required. The variable only exists in the upstream .env, not in any compose file. Without it the entrypoint starts neither the API server nor the admin server: nginx still serves the static frontend (the UI loads!) but every API call fails with a 502 — a very confusing failure mode.
Doc engine infinity. The upstream default elasticsearch needs vm.max_map_count >= 262144 on the host plus a JVM’s worth of RAM. Infinity is RAGFlow’s own doc engine, uses far fewer resources, and is selected via DOC_ENGINE=infinity in .env — the service_conf.yaml shipped in the image already contains matching defaults. To switch to Elasticsearch, set DOC_ENGINE=elasticsearch and add an es01 service from the upstream docker-compose-base.yml.
No path routing needed. The bundled nginx sends ^/(v1|api) to the API server on 9380, ^/api/v1/admin to the admin server on 9381, and everything else to the static SPA — UI, REST API and admin API all work through the single Host() router. Note that routing two Traefik routers on the same container requires explicit traefik.http.routers.<name>.service=<service> labels, otherwise Traefik logs Router ... cannot be linked automatically with multiple Services and answers 404.
init.sql. MySQL’s --init-file runs on every start (idempotent CREATE DATABASE IF NOT EXISTS), not just on first boot like /docker-entrypoint-initdb.d.
No embedding models. Images ≥ v0.22 ship no built-in embedding models. For actual RAG work you need an external provider or the optional tei embedding service (infiniflow/text-embeddings-inference) — the bundled UI and document parsing work without it.
Start it
docker compose up -dThe first start takes a few minutes while MySQL initializes and ragflow runs the schema and model provider migrations. Watch progress with docker compose logs -f ragflow. Verify the stack is up:
curl -sk https://ragflow.mydomain.com/api/v1/system/healthz
# {"db":"ok","doc_engine":"ok","redis":"ok","status":"ok","storage":"ok"}Open https://ragflow.mydomain.com and log in as admin@ragflow.io with ADMIN_DEFAULT_PASSWORD (created via --init-superuser), or register a user through the UI — the first registered user gets full access.
You can setup autostart using Create a systemd service for your docker-compose project in 10 seconds, TL;DR:
curl -fsSL https://techoverflow.net/scripts/create-docker-compose-service.sh | sudo bash /dev/stdin