RAGFlow 0.27 Docker Compose setup with Traefik reverse proxy

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:

init.sh
#!/bin/sh
mkdir -p mysql_data minio_data redis_data infinity_data ragflow_logs
Setup-RAGFlow.sh
mkdir -p ragflow && cd ragflow
./init.sh

MySQL needs an init script that creates the rag_flow database (mounted at /data/application/init.sql):

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

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:

.env
# 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=cloudflare

Generate random passwords with openssl rand -hex 16.

docker-compose.yml

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

Notes 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

Start-RAGFlow.sh
docker compose up -d

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

Verify-RAGFlow.sh
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:

Setup-Autostart.sh
curl -fsSL https://techoverflow.net/scripts/create-docker-compose-service.sh | sudo bash /dev/stdin

Check out similar posts by category: Docker