ragflow Docker & Self-Hosted Deployment

GitHub

RAGFlow is a leading open-source Retrieval-Augmented Generation (RAG) engine that fuses cutting-edge RAG with Agent capabilities to create a superior...

RAW Compose
1. Pull Image (DOCKERHUB) docker pull infiniflow/infinity
2. Docker Run (1-Click Launch) docker run -d --name ragflow -p 2881:2881 --restart always infiniflow/infinity:v0.7.4-x64-v3
3. Podman Run podman run -d --name ragflow -p 2881:2881 --restart always infiniflow/infinity:v0.7.4-x64-v3
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://docker.io/infiniflow/infinity:v0.7.4-x64-v3
5. ORAS Pull (OCI Artifact) oras pull infiniflow/infinity:v0.7.4-x64-v3
6. Compose Deployment docker compose up -d
docker/docker-compose-base.yml
Download .yml
services:
  es01:
    profiles:
      - elasticsearch
    image: elasticsearch:${STACK_VERSION}
    volumes:
      - esdata01:/usr/share/elasticsearch/data
    # Official ES image ACL on /tmp denies writes for user elasticsearch (r-x only).
    # entrypoint.sh needs a writable temp dir for bash here-documents.
    tmpfs:
      - /tmp:mode=1777,size=512m
    ports:
      - ${ES_PORT}:9200
    env_file: .env
    environment:
      - node.name=es01
      - ELASTIC_PASSWORD=${ELASTIC_PASSWORD}
      - bootstrap.memory_lock=false
      - discovery.type=single-node
      - xpack.security.enabled=true
      - xpack.security.http.ssl.enabled=false
      - xpack.security.transport.ssl.enabled=false
      - cluster.routing.allocation.disk.watermark.low=5gb
      - cluster.routing.allocation.disk.watermark.high=3gb
      - cluster.routing.allocation.disk.watermark.flood_stage=2gb
    mem_limit: ${MEM_LIMIT}
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test: ["CMD-SHELL", "curl http://localhost:9200"]
      interval: 10s
      timeout: 10s
      retries: 120
    networks:
      - ragflow
    restart: unless-stopped


  opensearch01:
    profiles:
      - opensearch
    image: opensearchproject/opensearch:2.19.1
    volumes:
      - osdata01:/usr/share/opensearch/data
    ports:
      - ${OS_PORT}:9201
    env_file: .env
    environment:
      - node.name=opensearch01
      - OPENSEARCH_PASSWORD=${OPENSEARCH_PASSWORD}
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_PASSWORD}
      - bootstrap.memory_lock=false
      - discovery.type=single-node
      - plugins.security.disabled=false
      - plugins.security.ssl.http.enabled=false
      - plugins.security.ssl.transport.enabled=true
      - cluster.routing.allocation.disk.watermark.low=5gb
      - cluster.routing.allocation.disk.watermark.high=3gb
      - cluster.routing.allocation.disk.watermark.flood_stage=2gb
      - http.port=9201
    mem_limit: ${MEM_LIMIT}
    ulimits:
      memlock:
        soft: -1
        hard: -1
    healthcheck:
      test: ["CMD-SHELL", "curl http://localhost:9201"]
      interval: 10s
      timeout: 10s
      retries: 120
    networks:
      - ragflow
    restart: unless-stopped


  infinity:
    profiles:
      - infinity
    image: infiniflow/infinity:v0.7.4-x64-v3
    volumes:
      - infinity_data:/var/infinity
      - ./infinity_conf.toml:/infinity_conf.toml
    command: ["-f", "/infinity_conf.toml"]
    ports:
      - ${INFINITY_THRIFT_PORT}:23817
      - ${INFINITY_HTTP_PORT}:23820
      - ${INFINITY_PSQL_PORT}:5432
    env_file: .env
    mem_limit: ${MEM_LIMIT}
    ulimits:
      nofile:
        soft: 500000
        hard: 500000
    networks:
      - ragflow
    healthcheck:
      test: ["CMD", "curl", "http://localhost:23820/admin/node/current"]
      interval: 10s
      timeout: 10s
      retries: 120
    restart: unless-stopped


  serenedb:
    profiles:
      - serenedb
    image: serenedb/serenedb:26.07.5
    env_file: .env
    environment:
      - POSTGRES_PASSWORD=${SERENEDB_PASSWORD}
    ports:
      - ${SERENEDB_PORT}:7890
    volumes:
      - serenedb_data:/var/lib/serenedb
    mem_limit: ${MEM_LIMIT}
    networks:
      - ragflow
    healthcheck:
      test: ["CMD", "pg_isready", "-h", "127.0.0.1", "-p", "7890", "-U", "postgres"]
      interval: 10s
      timeout: 10s
      retries: 120
    restart: unless-stopped


  oceanbase:
    profiles:
      - oceanbase
    image: oceanbase/oceanbase-ce:4.4.1.0-100000032025101610
    entrypoint: ["bash", "/root/boot/ragflow-oceanbase-entrypoint.sh"]
    ulimits:
      nofile:
        soft: 655350
        hard: 655350
    volumes:
      - ./oceanbase/data:/root/ob
      - ./oceanbase/conf:/root/.obd/cluster
      - ./oceanbase/init.d:/root/boot/init.d
      - ./oceanbase-entrypoint.sh:/root/boot/ragflow-oceanbase-entrypoint.sh:ro
    ports:
      - ${OCEANBASE_PORT:-2881}:2881
    env_file: .env
    environment:
      - MODE=normal
      - OB_SERVER_IP=127.0.0.1
    mem_limit: ${MEM_LIMIT}
    healthcheck:
      test: [ 'CMD-SHELL', 'obclient -h127.0.0.1 -P2881 -uroot@${OB_TENANT_NAME:-ragflow} -p${OB_TENANT_PASSWORD:-infini_rag_flow} -e "CREATE DATABASE IF NOT EXISTS ${OCEANBASE_DOC_DBNAME:-ragflow_doc};"' ]
      interval: 10s
      retries: 30
      start_period: 30s
      timeout: 10s
    networks:
      - ragflow
    restart: unless-stopped


  seekdb:
    profiles:
      - seekdb
    image: oceanbase/seekdb:latest
    container_name: seekdb
    volumes:
      # SeekDB 1.4+ stores data in /var/lib/seekdb.
      # For older 1.3.x images, change the container path to /var/lib/oceanbase.
      - ./seekdb:/var/lib/seekdb
    ports:
      - ${SEEKDB_PORT:-2881}:2881
    env_file: .env
    environment:
      - ROOT_PASSWORD=${SEEKDB_PASSWORD:-infini_rag_flow}
      - MEMORY_LIMIT=${SEEKDB_MEMORY_LIMIT:-2G}
      - REPORTER=ragflow-seekdb
    mem_limit: ${MEM_LIMIT}
    healthcheck:
      test: ['CMD-SHELL', 'mysql -h127.0.0.1 -P2881 -uroot -p${SEEKDB_PASSWORD:-infini_rag_flow} -e "CREATE DATABASE IF NOT EXISTS ${SEEKDB_DOC_DBNAME:-ragflow_doc};"']
      interval: 5s
      retries: 60
      timeout: 5s
    networks:
      - ragflow
    restart: unless-stopped


  vastbase:
    profiles:
      - vastbase
    image: ${VB_IMAGE:-vastbase-g100-single-v1.4-3.0.9.31338-linux-x86_64:20260513112809}
    env_file: .env
    environment:
      - VB_PASSWORD=${VB_PASSWORD}
      - VB_USERNAME=${VB_USERNAME}
      - VB_VEC_DBNAME=${VB_VEC_DBNAME:-ragflow}
      - VB_DBCOMPATIBILITY=${VB_DBCOMPATIBILITY:-B}
    # Bind the published port to loopback by default: the database ships
    # default credentials, and RAGFlow reaches it over the shared `ragflow`
    # network. Remote access is an explicit opt-in through VB_BIND_ADDR
    # (e.g. 0.0.0.0 or a specific interface IP) in docker/.env.
    ports:
      - ${VB_BIND_ADDR:-127.0.0.1}:${EXPOSE_VB_PORT:-5432}:5432
    volumes:
      - vastbase_data:/home/vastbase/data
      - ./vastbase/init:/docker-entrypoint-initdb.d
      - ./vastbase/license:/home/vastbase/vastbase/lic
    mem_limit: ${MEM_LIMIT}
    networks:
      - ragflow
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "vsql -c 'SELECT 1'"]
      interval: 10s
      timeout: 10s
      retries: 120


  sandbox-executor-manager:
    profiles:
      - sandbox
    image: ${SANDBOX_EXECUTOR_MANAGER_IMAGE-infiniflow/sandbox-executor-manager:latest}
    privileged: true
    # Bind to loopback only: the /run endpoint executes arbitrary sandboxed
    # code. RAGFlow reaches this service over the shared `ragflow` network.
    ports:
      - 127.0.0.1:${SANDBOX_EXECUTOR_MANAGER_PORT:-9385}:9385
    env_file: .env
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      - ragflow
    security_opt:
      - no-new-privileges:true
    environment:
      - SANDBOX_EXECUTOR_MANAGER_POOL_SIZE=${SANDBOX_EXECUTOR_MANAGER_POOL_SIZE:-3}
      - SANDBOX_BASE_PYTHON_IMAGE=${SANDBOX_BASE_PYTHON_IMAGE:-infiniflow/sandbox-base-python:latest}
      - SANDBOX_BASE_NODEJS_IMAGE=${SANDBOX_BASE_NODEJS_IMAGE:-infiniflow/sandbox-base-nodejs:latest}
      - SANDBOX_ENABLE_SECCOMP=${SANDBOX_ENABLE_SECCOMP:-false}
      - SANDBOX_MAX_MEMORY=${SANDBOX_MAX_MEMORY:-256m}
      - SANDBOX_TIMEOUT=${SANDBOX_TIMEOUT:-10s}
      # Shared secret for the /run API. Set it in .env (same value is passed
      # to the ragflow service). Current Sandbox source rejects unconfigured
      # authentication; verify the behavior of the deployed image version.
      - SANDBOX_EXECUTOR_MANAGER_API_TOKEN=${SANDBOX_EXECUTOR_MANAGER_API_TOKEN:-}
      # Sandbox runner containers get no external network by default.
      - SANDBOX_CONTAINER_NETWORK=${SANDBOX_CONTAINER_NETWORK:-none}
    healthcheck:
      test: ["CMD", "curl", "http://localhost:9385/healthz"]
      interval: 10s
      timeout: 10s
      retries: 120
    restart: unless-stopped


  mysql:
    profiles:
      - mysql
      - metadata-mysql
      - metadata-MySQL
      - metadata-MYSQL
    # mysql:5.7 linux/arm64 image is unavailable.
    image: mysql:8.0.40
    env_file: .env
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_PASSWORD}
    # Ingestion throughput tuning, OPT-IN. The ingestion sink commits per progress
    # event, so MySQL's own defaults (innodb_flush_log_at_trx_commit=1 +
    # sync_binlog=1 on a 128MB buffer pool) spent 68ms of CPU and two fsyncs on
    # every commit: 211k commits had burned 14.4k seconds of MySQL CPU and capped
    # ingestion at ~23 documents/minute, with mysqld the busiest process on the
    # box. Relaxing the two settings brings the same commit down to 1.6ms and
    # ingestion to ~56 documents/minute (measured, same load).
    #
    # They are NOT the default because they apply to every write in this instance,
    # not only to the ingestion bookkeeping they were measured for: after a
    # host-level crash (not a mysqld restart) a client can lose transactions it
    # already observed as committed. A deployment that treats the whole database as
    # rebuildable opts in through docker/.env:
    #   MYSQL_FLUSH_LOG_AT_TRX_COMMIT=2
    #   MYSQL_SYNC_BINLOG=0
    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-flush-log-at-trx-commit=${MYSQL_FLUSH_LOG_AT_TRX_COMMIT:-1}
      - --sync-binlog=${MYSQL_SYNC_BINLOG:-1}
      # 1G, not 4G. The database is 3.6G but its hot working set is under 1G
      # (document/file/file2document/ingestion_task_log sum to ~880M), and 2.66G
      # of the 3.6G is pipeline_operation_log, a log table that is written but
      # not re-read. Ingestion is embedding-bound, not database-bound: the
      # per-stage database cost is 0.03-0.10s, unchanged when the pool was
      # shrunk from 4G to 1G online. The 4G pool simply wasted 3G of a box with
      # no swap. Restart mysqld after changing this (an online shrink returns
      # only part of the memory to the OS).
      - --innodb-buffer-pool-size=1G
      - --innodb-io-capacity=2000
      - --innodb-io-capacity-max=4000
      - --innodb-redo-log-capacity=1G
    ports:
      - ${EXPOSE_MYSQL_PORT}:3306
    volumes:
      - mysql_data:/var/lib/mysql
      - ./init.sql:/data/application/init.sql
    networks:
      - ragflow
    healthcheck:
      test: ["CMD", "mysqladmin" ,"ping", "-uroot", "-p${MYSQL_PASSWORD}"]
      interval: 1s
      timeout: 2s
      retries: 60
      start_period: 30s
    restart: unless-stopped


  minio:
    image: pgsty/silo:RELEASE.2026-08-06T00-00-00Z
    command: ["server", "--console-address", ":9001", "/data"]
    ports:
      - ${MINIO_PORT}:9000
      - ${MINIO_CONSOLE_PORT}:9001
    env_file: .env
    environment:
      - MINIO_ROOT_USER=${MINIO_USER}
      - MINIO_ROOT_PASSWORD=${MINIO_PASSWORD}
    volumes:
      - minio_data:/data
    networks:
      - ragflow
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 10s
      timeout: 10s
      retries: 120


  kvrocks:
    # Apache Kvrocks: a RocksDB-backed, Redis-protocol-compatible store used
    # ONLY by the Go services (ragflow-cpu / ragflow-gpu), which override
    # REDIS_HOST=kvrocks in docker-compose-go.yml. The Python services keep
    # using the `redis` (Valkey) service above, so this is a dual-backend setup.
    # Pinned to 2.16.0+ on purpose (see docker/kvrocks-entrypoint.sh): older
    # tags such as kvrocks/kvrocks:latest (== 2.0.6) carry a TTL contract bug
    # that breaks the RunTracker CAS lease, and they reject the Redis-style
    # maxmemory/maxmemory-policy settings.
    image: apache/kvrocks:2.16.0
    # Kvrocks' config file does not expand ${ENV} and its CLI rejects
    # `--requirepass` as a flag, so the entrypoint renders the config (including
    # the password from REDIS_PASSWORD) at startup. `user: "0"` lets it write the
    # config and the RocksDB data dir regardless of the mounted volume ownership.
    entrypoint: ["/bin/sh", "/etc/kvrocks/entrypoint.sh"]
    user: "0:0"
    env_file: .env
    # Publish the Kvrocks port so a host-run Go binary (e.g. ./bin/ragflow_server
    # --ingestor) can reach it. The host-facing port uses KVROCKS_PORT (default 6379
    # locally, so a local host binary connects to localhost:6379). In CI the port is
    # offset per job (KVROCKS_PORT = 6381 + PORT_OFFSET, reserved alongside the other
    # CI ports) so concurrent test runs on a shared runner do not clash, and it stays
    # distinct from REDIS_PORT (used by the Valkey `redis` service that the Python
    # deployment still runs next to Kvrocks). The container port (right side) stays
    # 6379, and the Go services reach `kvrocks:6379` inside the docker network
    # regardless of the host mapping (KVROCKS_PORT stays 6379, set by the Go overlay).
    ports:
      - "${KVROCKS_PORT:-6379}:6379"
    volumes:
      - kvrocks_data:/var/lib/kvrocks
      - ./kvrocks-entrypoint.sh:/etc/kvrocks/entrypoint.sh:ro
    networks:
      - ragflow
    restart: unless-stopped
    healthcheck:
      # The Kvrocks image ships redis-cli and requirepass is enforced, so ping
      # with the password. (The image has no nc, so a raw-tcp probe is wrong.)
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 10s
      retries: 120


  jaeger:
    profiles:
      - jaeger
    image: jaegertracing/jaeger:${JAEGER_VERSION:-2.19.0}
    ports:
      - ${JAEGER_OTLP_GRPC_PORT:-4317}:4317
      - ${JAEGER_OTLP_HTTP_PORT:-4318}:4318
      - ${JAEGER_UI_PORT:-16686}:16686
    env_file: .env
    environment:
      - COLLECTOR_OTLP_ENABLED=true
    networks:
      - ragflow
    restart: unless-stopped


  nats:
    image: nats:2.14.2
    ports:
      - ${EXPOSE_NATS_PORT:-4222}:4222
      - ${EXPOSE_NATS_MONITORING_PORT:-8222}:8222
    volumes:
      - nats_data:/data
    command: -js -sd /data --http_port 8222
    env_file: .env
    networks:
      - ragflow
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "nats-server", "--health-check"]
      interval: 10s
      timeout: 10s
      retries: 30


  tei-cpu:
    profiles:
      - tei-cpu
    image: ${TEI_IMAGE_CPU}
    hostname: tei
    ports:
      - ${TEI_PORT-6380}:80
    env_file: .env
    networks:
      - ragflow
    command: ["--model-id", "/data/${TEI_MODEL}", "--auto-truncate"]
    restart: unless-stopped


  tei-gpu:
    profiles:
      - tei-gpu
    image: ${TEI_IMAGE_GPU}
    hostname: tei
    ports:
      - ${TEI_PORT-6380}:80
    env_file: .env
    networks:
      - ragflow
    command: ["--model-id", "/data/${TEI_MODEL}", "--auto-truncate"]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]
    restart: unless-stopped


  kibana:
    profiles:
      - kibana
    image: kibana:${STACK_VERSION}
    ports:
      - ${KIBANA_PORT-5601}:5601
    env_file: .env
    volumes:
      - kibana_data:/usr/share/kibana/data
    depends_on:
      es01:
        condition: service_started
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5601/api/status"]
      interval: 10s
      timeout: 10s
      retries: 120
    networks:
      - ragflow
    restart: unless-stopped


  clickhouse:
    profiles:
      - clickhouse
    image: clickhouse/clickhouse-server:26.5.5.8
    volumes:
      - clickhouse_data:/var/lib/clickhouse
      - ./init-clickhouse.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - ${EXPOSE_CLICKHOUSE_TCP_PORT:-9900}:9000
      - ${CLICKHOUSE_HTTP_PORT:-8123}:8123
    env_file: .env
    environment:
      - CLICKHOUSE_USER=${CLICKHOUSE_USER:-ragflow}
      - CLICKHOUSE_PASSWORD=${CLICKHOUSE_PASSWORD:-infini_rag_flow}
      - CLICKHOUSE_DEFAULT_ACCESS_MANAGEMENT=1
    mem_limit: ${MEM_LIMIT}
    ulimits:
      nofile:
        soft: 655350
        hard: 655350
    healthcheck:
      test: ["CMD", "clickhouse-client", "--user", "${CLICKHOUSE_USER:-ragflow}", "--password", "${CLICKHOUSE_PASSWORD:-infini_rag_flow}", "-q", "SELECT 1"]
      interval: 10s
      timeout: 10s
      retries: 30
      start_period: 30s
    networks:
      - ragflow
    restart: unless-stopped


volumes:
  esdata01:
    driver: local
  osdata01:
    driver: local
  infinity_data:
    driver: local
  serenedb_data:
    driver: local
  ob_data:
    driver: local
  seekdb_data:
    driver: local
  vastbase_data:
    driver: local
  mysql_data:
    driver: local
  minio_data:
    driver: local
  redis_data:
    driver: local
  kvrocks_data:
    driver: local
  tei_data:
    driver: local
  kibana_data:
    driver: local
  nats_data:
    driver: local
  clickhouse_data:
    driver: local

networks:
  ragflow:
    driver: bridge