Yuxi Docker & Self-Hosted Deployment

GitHub

可私有部署的多租户知识智能体平台:统一 RAG、知识图谱、多智能体、MCP/Skills、沙盒与权限管理。Yuxi = Cloud Agents + Knowledge RAG, Self-hosted knowledge agent platform for RAG, knowledge graphs and...

7,412 stars Python Python 128 files · ~125,366 tokens #agentic-rag#ai-agents#deepagents#docker#document-ai#fastapi#harness#kbqa
RAW Compose
1. Pull Image (DOCKERHUB) docker pull library/neo4j
2. Docker Run (1-Click Launch) docker run -d --name yuxi -p 6050:6050 --restart always neo4j:5.26.29
3. Podman Run podman run -d --name yuxi -p 6050:6050 --restart always neo4j:5.26.29
4. Skopeo Inspect (Zero-Pull Manifest) skopeo inspect docker://docker.io/library/neo4j:5.26.29
5. ORAS Pull (OCI Artifact) oras pull neo4j:5.26.29
6. Compose Deployment docker compose up -d
docker-compose.prod.yml
Download .yml
x-api-worker-env: &api-worker-env
  HOST_IP: ${HOST_IP:-}
  RUNNING_IN_DOCKER: "true"
  YUXI_ENV: production
  PYTHONPATH: /app/package
  JWT_SECRET_KEY: ${JWT_SECRET_KEY:?Set JWT_SECRET_KEY in .env.prod}
  API_KEY_DERIVATION_SECRET: ${API_KEY_DERIVATION_SECRET:?Set API_KEY_DERIVATION_SECRET in .env.prod}
  RAPIDOCR_MODEL_DIR: /home/yuxi/.cache/rapidocr/models
  YUXI_INSTANCE_ID: ${YUXI_INSTANCE_ID:?Set YUXI_INSTANCE_ID in .env.prod}
  POSTGRES_URL: ${POSTGRES_URL:-postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env.prod}@postgres:5432/${POSTGRES_DB:-yuxi}}
  REDIS_URL: ${REDIS_URL:-redis://redis:6379/0}
  RUN_CANCEL_KEY_TTL_SECONDS: ${RUN_CANCEL_KEY_TTL_SECONDS:-1800}
  RUN_EVENTS_STREAM_TTL_SECONDS: ${RUN_EVENTS_STREAM_TTL_SECONDS:-7200}
  READINESS_PROBE_TIMEOUT_SECONDS: ${READINESS_PROBE_TIMEOUT_SECONDS:-2}
  READINESS_CACHE_TTL_SECONDS: ${READINESS_CACHE_TTL_SECONDS:-1}
  YUXI_CORS_ORIGINS: ${YUXI_CORS_ORIGINS:-}
  NEO4J_URI: ${NEO4J_URI:-bolt://graph:7687}
  NEO4J_USERNAME: ${NEO4J_USERNAME:-neo4j}
  NEO4J_PASSWORD: ${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env.prod}
  MILVUS_URI: ${MILVUS_URI:-http://milvus:19530}
  MILVUS_TOKEN: ${MILVUS_TOKEN:-}
  MINERU_API_URI: ${MINERU_API_URI:-http://mineru-api:30001}
  PADDLEX_URI: ${PADDLEX_URI:-http://paddlex:8080}
  MINIO_URI: ${MINIO_URI:-http://minio:9000}
  SANDBOX_PROVIDER: ${SANDBOX_PROVIDER:-provisioner}
  SANDBOX_PROVISIONER_URL: ${SANDBOX_PROVISIONER_URL:-http://sandbox-provisioner:8002}
  SANDBOX_PROVISIONER_TOKEN: ${SANDBOX_PROVISIONER_TOKEN:?Set SANDBOX_PROVISIONER_TOKEN in .env.prod}
  SANDBOX_PROVISIONER_DELETE_TIMEOUT_SECONDS: ${SANDBOX_PROVISIONER_DELETE_TIMEOUT_SECONDS:-120}
  SANDBOX_VIRTUAL_PATH_PREFIX: ${SANDBOX_VIRTUAL_PATH_PREFIX:-/home/gem/user-data}
  SANDBOX_EXEC_TIMEOUT_SECONDS: ${SANDBOX_EXEC_TIMEOUT_SECONDS:-180}
  SANDBOX_MAX_OUTPUT_BYTES: ${SANDBOX_MAX_OUTPUT_BYTES:-262144}
  TASKER_DEFAULT_TIMEOUT_SECONDS: ${TASKER_DEFAULT_TIMEOUT_SECONDS:-21600}
  YUXI_DATASET_PERSIST_BATCH_SIZE: ${YUXI_DATASET_PERSIST_BATCH_SIZE:-1}
  YUXI_SKILL_DATA_DIR: /app/skill-sources
  YUXI_SKILL_PROJECTION_DIR: /app/skill-projections
  NO_PROXY: localhost,127.0.0.1,milvus,graph,minio,etcd,mineru,paddlex,sandbox-provisioner,api.siliconflow.cn
  no_proxy: localhost,127.0.0.1,milvus,graph,minio,etcd,mineru,paddlex,sandbox-provisioner,api.siliconflow.cn

# 长驻服务的容器日志轮转上界,防止 docker json.log 无限增长占满磁盘。
x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "3"

services:
  api:
    ports:
      - "${YUXI_API_PORT:-6050}:5050"
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "1000:1000"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    extra_hosts:
      - "host.docker.internal:host-gateway"
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    # region api_envs
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/api
      YUXI_USER_DATA_DIR: /app/user-data
      POSTGRES_POOL_SIZE: ${API_POSTGRES_POOL_SIZE:-120}
      POSTGRES_MAX_OVERFLOW: ${API_POSTGRES_MAX_OVERFLOW:-40}
      POSTGRES_POOL_TIMEOUT_SECONDS: ${API_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      LANGGRAPH_POSTGRES_POOL_SIZE: ${API_LANGGRAPH_POSTGRES_POOL_SIZE:-10}
      LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS: ${API_LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      REDIS_MAX_CONNECTIONS: ${API_REDIS_MAX_CONNECTIONS:-256}
    # endregion api_envs
    command: uv run --no-sync --no-dev uvicorn server.main:app --host 0.0.0.0 --port 5050
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:5050/api/system/ready || exit 1"]
      interval: 30s
      timeout: 15s
      retries: 8
      start_period: 180s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio:
        condition: service_healthy
      sandbox-provisioner:
        condition: service_healthy
      storage-migrator:
        condition: service_completed_successfully

  worker:
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "1000:1000"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    extra_hosts:
      - "host.docker.internal:host-gateway"
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/worker
      YUXI_USER_DATA_DIR: /app/user-data
      ARQ_MAX_JOBS: ${ARQ_MAX_JOBS:-140}
      REDIS_MAX_CONNECTIONS: ${WORKER_REDIS_MAX_CONNECTIONS:-256}
      POSTGRES_POOL_SIZE: ${WORKER_POSTGRES_POOL_SIZE:-120}
      POSTGRES_MAX_OVERFLOW: ${WORKER_POSTGRES_MAX_OVERFLOW:-40}
      POSTGRES_POOL_TIMEOUT_SECONDS: ${WORKER_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
      LANGGRAPH_POSTGRES_POOL_SIZE: ${WORKER_LANGGRAPH_POSTGRES_POOL_SIZE:-120}
      LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS: ${WORKER_LANGGRAPH_POSTGRES_POOL_TIMEOUT_SECONDS:-30}
    command: uv run --no-sync --no-dev python -m server.worker_main
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "python", "-m", "yuxi.services.worker_health"]
      interval: 10s
      timeout: 10s
      retries: 6
      start_period: 180s
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      minio:
        condition: service_healthy
      sandbox-provisioner:
        condition: service_healthy
      storage-migrator:
        condition: service_completed_successfully

  storage-migrator:
    build:
      context: .
      dockerfile: docker/api.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-api:${YUXI_VERSION:-0.7.3}
    working_dir: /app
    user: "0:0"
    networks:
      - app-network
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi:/app/legacy-saves
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-sources:/app/skill-sources
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      <<: *api-worker-env
      YUXI_RUNTIME_DIR: /app/runtime/storage-migrator
      YUXI_LEGACY_STORAGE_DIR: /app/legacy-saves
      YUXI_USER_DATA_DIR: /app/user-data
    command: uv run --no-sync --no-dev python -m yuxi.storage_migration
    restart: "no"
    depends_on:
      postgres:
        condition: service_healthy

  sandbox-provisioner:
    ports:
      - "127.0.0.1:${YUXI_SANDBOX_PORT:-9002}:8002"
    build:
      context: ./docker/sandbox_provisioner
      dockerfile: Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-sandbox-provisioner:${YUXI_VERSION:-0.7.3}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/threads:/app/user-data
      - ${YUXI_STATE_DIR:-./docker/volumes}/yuxi/skill-projections:/app/skill-projections
      - ${SANDBOX_ENV_FILE:-./docker/sandbox_provisioner/sandbox.env}:/app/sandbox.env:ro
      - /var/run/docker.sock:/var/run/docker.sock
    networks:
      app-network:
        # 动态接入沙盒网络时保留管理端口的默认路由。
        gw_priority: 1
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      # Common provisioner settings
      - PROVISIONER_BACKEND=${SANDBOX_PROVISIONER_BACKEND:-docker}
      - PROVISIONER_PUBLIC_URL=${SANDBOX_PROVISIONER_URL:-http://sandbox-provisioner:8002}
      - SANDBOX_PROVISIONER_TOKEN=${SANDBOX_PROVISIONER_TOKEN:?Set SANDBOX_PROVISIONER_TOKEN in .env.prod}
      - SANDBOX_IMAGE=${SANDBOX_IMAGE:-enterprise-public-cn-beijing.cr.volces.com/vefaas-public/all-in-one-sandbox:1.11.0}
      - SANDBOX_RUNTIME_PROFILE=${SANDBOX_RUNTIME_PROFILE:-core}
      - SANDBOX_CONTAINER_PORT=${SANDBOX_CONTAINER_PORT:-8080}
      - MEMORY_SANDBOX_URL_TEMPLATE=${MEMORY_SANDBOX_URL_TEMPLATE:-http://agent-sandbox:8000}
      - SANDBOX_HEALTH_TIMEOUT_SECONDS=${SANDBOX_HEALTH_TIMEOUT_SECONDS:-300}
      - SANDBOX_IDLE_TIMEOUT_SECONDS=${SANDBOX_IDLE_TIMEOUT_SECONDS:-120}
      - SANDBOX_IDLE_CHECK_INTERVAL_SECONDS=${SANDBOX_IDLE_CHECK_INTERVAL_SECONDS:-10}
      - SANDBOX_EXEC_TIMEOUT_SECONDS=${SANDBOX_EXEC_TIMEOUT_SECONDS:-180}
      - SANDBOX_DELETE_CONCURRENCY=${SANDBOX_DELETE_CONCURRENCY:-32}
      - SANDBOX_CONTAINER_STOP_TIMEOUT_SECONDS=${SANDBOX_CONTAINER_STOP_TIMEOUT_SECONDS:-2}
      # 单沙盒执行的资源上界
      - SANDBOX_MEM_LIMIT=${SANDBOX_MEM_LIMIT:-2g}
      - SANDBOX_CPUS=${SANDBOX_CPUS:-2}
      - SANDBOX_PIDS_LIMIT=${SANDBOX_PIDS_LIMIT:-512}
      # Optional proxy for sandbox-provisioner container
      - HTTP_PROXY=${SANDBOX_HTTP_PROXY:-}
      - HTTPS_PROXY=${SANDBOX_HTTPS_PROXY:-}
      - NO_PROXY=localhost,127.0.0.1,host.docker.internal
      # Docker backend only
      - DOCKER_NETWORK_PREFIX=${SANDBOX_DOCKER_NETWORK_PREFIX:-${COMPOSE_PROJECT_NAME:-yuxi}-sandbox}
      - DOCKER_ADDRESS_POOL=${SANDBOX_DOCKER_ADDRESS_POOL:-10.253.240.0/20}
      - DOCKER_SUBNET_PREFIX=${SANDBOX_DOCKER_SUBNET_PREFIX:-28}
      - DOCKER_USER_DATA_HOST_PATH=${SANDBOX_DOCKER_USER_DATA_HOST_PATH:-}
      - DOCKER_SKILL_PROJECTIONS_HOST_PATH=${SANDBOX_DOCKER_SKILL_PROJECTIONS_HOST_PATH:-}
      - DOCKER_SANDBOX_PREFIX=${SANDBOX_DOCKER_SANDBOX_PREFIX:-${COMPOSE_PROJECT_NAME:-yuxi}-sandbox}
      # Kubernetes backend only
      - K8S_NAMESPACE=${SANDBOX_K8S_NAMESPACE:-yuxi-know}
      - NODE_HOST=${SANDBOX_NODE_HOST:-host.docker.internal}
      - KUBECONFIG_PATH=${KUBECONFIG_PATH:-}
      - USER_DATA_PVC=${USER_DATA_PVC:-yuxi-user-data}
      - SKILLS_PVC=${SKILLS_PVC:-yuxi-skills}
    command: ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8002"]
    healthcheck:
      test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8002/health').read()"]
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 10s
    restart: unless-stopped
    depends_on:
      storage-migrator:
        condition: service_completed_successfully

  web:
    build:
      context: .
      dockerfile: docker/web.Dockerfile
      target: production
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-web:${YUXI_VERSION:-0.7.3}
    ports:
      - "${YUXI_WEB_PORT:-80}:80"
    depends_on:
      - api
    networks:
      - app-network
    environment:
      - NODE_ENV=production
    command: nginx -g "daemon off;"
    restart: unless-stopped

  graph:
    ports:
      - "127.0.0.1:${YUXI_NEO4J_HTTP_PORT:-8474}:7474"
      - "127.0.0.1:${YUXI_NEO4J_BOLT_PORT:-8687}:7687"
    image: neo4j:5.26.29
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/neo4j/data:/data
      - ${YUXI_STATE_DIR:-./docker/volumes}/neo4j/logs:/logs
    healthcheck:
      test: ["CMD-SHELL", "cypher-shell -a bolt://localhost:7687 -u \"$${NEO4J_AUTH%%/*}\" -p \"$${NEO4J_AUTH#*/}\" 'RETURN 1;' >/dev/null || exit 1"]
      interval: 20s
      timeout: 10s
      retries: 5
      start_period: 30s
    environment:
      - NEO4J_AUTH=${NEO4J_USERNAME:-neo4j}/${NEO4J_PASSWORD:?Set NEO4J_PASSWORD in .env.prod}
      - NEO4J_server_bolt_listen__address=0.0.0.0:7687
      - NEO4J_server_http_listen__address=0.0.0.0:7474
      - ENTITY_EMBEDDING=true
    networks:
      - app-network
    restart: unless-stopped

  etcd:
    image: quay.io/coreos/etcd:v3.5.5
    logging: *default-logging
    environment:
      - ETCD_AUTO_COMPACTION_MODE=revision
      - ETCD_AUTO_COMPACTION_RETENTION=1000
      - ETCD_QUOTA_BACKEND_BYTES=4294967296
      - ETCD_SNAPSHOT_COUNT=50000
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/etcd:/etcd
    command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
    healthcheck:
      test: ["CMD", "etcdctl", "endpoint", "health"]
      interval: 60s
      timeout: 30s
      retries: 5
      start_period: 30s
    networks:
      - app-network
    restart: unless-stopped

  minio:
    build:
      context: ./docker/minio
      dockerfile: Dockerfile
    ports:
      - "127.0.0.1:${YUXI_MINIO_API_PORT:-10000}:9000"
      - "127.0.0.1:${YUXI_MINIO_CONSOLE_PORT:-10001}:9001"
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-minio:RELEASE.2023-03-20T20-16-18Z
    environment:
      MINIO_ACCESS_KEY: ${MINIO_ACCESS_KEY:?Set MINIO_ACCESS_KEY in .env.prod}
      MINIO_SECRET_KEY: ${MINIO_SECRET_KEY:?Set MINIO_SECRET_KEY in .env.prod}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/minio:/data
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/minio_config:/root/.minio
    command: minio server /data --address 0.0.0.0:9000 --console-address 0.0.0.0:9001
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
      interval: 60s
      timeout: 30s
      retries: 5
      start_period: 30s
    networks:
      - app-network
    restart: unless-stopped

  milvus:
    ports:
      - "127.0.0.1:${YUXI_MILVUS_PORT:-20530}:19530"
      - "127.0.0.1:${YUXI_MILVUS_HEALTH_PORT:-10091}:9091"
    image: milvusdb/milvus:v2.5.6
    command: ["milvus", "run", "standalone"]
    # v2.5.6 不读取 MILVUS_LOG_LEVEL;日志级别只能由镜像内 milvus.yaml 拥有,
    # 这里只对 stdout/stderr 轮转封顶。内存随知识库数据量线性增长,不设 mem_limit:
    # 硬上界终会被合法增长击穿,击穿模式是 OOM→重启→重加载→再 OOM 的循环。
    # cpus 上限同时让内置 maxprocs 按 cgroup quota 收敛 GOMAXPROCS。
    logging: *default-logging
    cpus: ${YUXI_MILVUS_CPUS:-2}
    security_opt:
      - seccomp:unconfined
    environment:
      ETCD_ENDPOINTS: etcd:2379
      MINIO_ADDRESS: minio:9000
      MINIO_ACCESS_KEY_ID: ${MINIO_ACCESS_KEY:?Set MINIO_ACCESS_KEY in .env.prod}
      MINIO_SECRET_ACCESS_KEY: ${MINIO_SECRET_KEY:?Set MINIO_SECRET_KEY in .env.prod}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/milvus:/var/lib/milvus
      - ${YUXI_STATE_DIR:-./docker/volumes}/milvus/logs:/var/lib/milvus/logs
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
      interval: 60s
      start_period: 120s
      timeout: 30s
      retries: 5
    depends_on:
      - "etcd"
      - "minio"
    networks:
      - app-network
    restart: unless-stopped

  postgres:
    ports:
      - "127.0.0.1:${YUXI_POSTGRES_PORT:-6432}:5432"
    image: postgres:16
    command: ["postgres", "-c", "max_connections=${POSTGRES_MAX_CONNECTIONS:-600}"]
    environment:
      - POSTGRES_USER=${POSTGRES_USER:-postgres}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env.prod}
      - POSTGRES_DB=${POSTGRES_DB:-yuxi}
      - TZ=${TZ:-Asia/Shanghai}
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/postgresql:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-yuxi} || exit 1"]
      interval: 5s
      timeout: 3s
      retries: 30
    networks:
      - app-network
    restart: unless-stopped

  redis:
    ports:
      - "127.0.0.1:${YUXI_REDIS_PORT:-7379}:6379"
    image: redis:7.4.10-alpine
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 10
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/redis:/data
    networks:
      - app-network
    restart: unless-stopped

  mineru-api:
    ports:
      - "127.0.0.1:${YUXI_MINERU_PORT:-30001}:30001"
    build:
      context: .
      dockerfile: docker/mineru.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-mineru:latest
    profiles:
      - all
    env_file:
      - ${YUXI_ENV_FILE:-.env.prod}
    environment:
      MINERU_MODEL_SOURCE: local
    entrypoint: mineru-api
    command:
      --host 0.0.0.0
      --port 30001
      # --allow-public-http-client  # Disabled by default; when binding to 0.0.0.0 or ::, this re-enables *-http-client backends and server_url. Enable only if you accept the SSRF risk.
      # parameters for vllm-engine
      # --gpu-memory-utilization 0.5  # If encountering VRAM shortage, reduce the KV cache size by this parameter; if VRAM issues persist, try lowering it further to `0.4` or below.
    ulimits:
      memlock: -1
      stack: 67108864
    ipc: host
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:30001/health || exit 1"]
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ["0"]  # Modify for multiple GPUs: ["0", "1"]
              capabilities: [gpu]
    networks:
      - app-network
    restart: unless-stopped

  paddlex:
    ports:
      - "127.0.0.1:${YUXI_PADDLEX_PORT:-8080}:8080"
    build:
      context: .
      dockerfile: docker/paddlex.Dockerfile
    image: ${COMPOSE_PROJECT_NAME:-yuxi}-paddlex:latest
    profiles:
      - all
    volumes:
      - ${YUXI_STATE_DIR:-./docker/volumes}/paddlex:/paddlex
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:8080/health || exit 1"]
      interval: 20s
      timeout: 10s
      retries: 5
      start_period: 60s
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              device_ids: ['0']
              capabilities: [gpu]
    networks:
      - app-network
    restart: unless-stopped

networks:
  app-network:
    driver: bridge