{
  "manifest_version": "1.0.0",
  "template": {
    "id": "f948b6e2-3876-4165-9276-de597bf23da4",
    "slug": "ragflow-document-rag-stack",
    "name": "RAGFlow Document RAG Stack",
    "description": "RAGFlow document RAG with Elasticsearch, MySQL, MinIO and Valkey",
    "url": "https://railway.com/deploy/ragflow-document-rag-stack",
    "upstream": {
      "image": "infiniflow/ragflow:v0.27.2"
    }
  },
  "services": [
    {
      "name": "Elasticsearch",
      "source": {
        "image": "elasticsearch:8.11.3"
      },
      "needs_volume": true,
      "volume_mount_path": "/usr/share/elasticsearch/data",
      "http": false
    },
    {
      "name": "MySQL",
      "source": {
        "image": "mysql:8.0.40"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/mysql",
      "http": false
    },
    {
      "name": "MinIO",
      "source": {
        "image": "quay.io/minio/minio:RELEASE.2025-03-12T18-04-18Z"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    },
    {
      "name": "RAGFlow",
      "source": {
        "image": "infiniflow/ragflow:v0.27.2"
      },
      "needs_volume": false,
      "http": true
    },
    {
      "name": "Redis",
      "source": {
        "image": "valkey/valkey:8.1.4"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": false
    }
  ],
  "required_inputs": [
    {
      "key": "ES_JAVA_OPTS",
      "service": "Elasticsearch",
      "description": "JVM heap. Elasticsearch sizes the heap from the visible machine memory when this is unset, which on Railway means a heap far larger than the service's limit. Keep Xms equal to Xmx; 512m works for small corpora, 2g upwards for large ones.",
      "secret": false,
      "strategy": "default",
      "default": "-Xms1g -Xmx1g"
    },
    {
      "key": "RAILWAY_RUN_UID",
      "service": "Elasticsearch",
      "description": "Run the container as root. Railway mounts volumes as root and this image runs as uid 1000, so the start command chowns the data directory and then drops back to uid 1000 (Elasticsearch refuses to run as root).",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "ELASTIC_PASSWORD",
      "service": "Elasticsearch",
      "description": "Password for the built-in 'elastic' user, referenced by the RAGFlow service. It is written into the security index at first bootstrap, so changing it later does not rotate the credential.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "Elasticsearch",
      "description": "Grace period so Elasticsearch can flush and shut down cleanly on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "MYSQL_USER",
      "service": "MySQL",
      "description": "Application user referenced by the RAGFlow service.",
      "secret": false,
      "strategy": "default",
      "default": "ragflow"
    },
    {
      "key": "MYSQL_DATABASE",
      "service": "MySQL",
      "description": "Database created on first boot and granted to MYSQL_USER. Same name as the upstream docker/init.sql.",
      "secret": false,
      "strategy": "default",
      "default": "rag_flow"
    },
    {
      "key": "MYSQL_PASSWORD",
      "service": "MySQL",
      "description": "Generated application password referenced by the RAGFlow service.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "MYSQL_PRIVATE_URL",
      "service": "MySQL",
      "description": "Convenience connection string over the private network (IPv6, includes the port).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MYSQL_ROOT_PASSWORD",
      "service": "MySQL",
      "description": "Generated root password. RAGFlow does not use it; it exists so the image can initialise.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "MySQL",
      "description": "Grace period so MySQL can shut down cleanly on redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "MINIO_ROOT_USER",
      "service": "MinIO",
      "description": "S3 access key. Referenced by the RAGFlow service as MINIO_USER.",
      "secret": false,
      "strategy": "default",
      "default": "ragflow"
    },
    {
      "key": "MINIO_ROOT_PASSWORD",
      "service": "MinIO",
      "description": "Generated S3 secret key. Referenced by the RAGFlow service as MINIO_PASSWORD.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "TZ",
      "service": "RAGFlow",
      "description": "Container timezone used for log and task timestamps (upstream ships Asia/Shanghai).",
      "secret": false,
      "strategy": "default",
      "default": "UTC"
    },
    {
      "key": "PORT",
      "service": "RAGFlow",
      "description": "Port the bundled nginx listens on. Railway's healthcheck and edge proxy probe $PORT, and the start command rewrites nginx's 'listen 80' to this port (plus an IPv6 listener). Keep it equal to the public domain's target port.",
      "secret": false,
      "strategy": "default",
      "default": "80"
    },
    {
      "key": "DB_TYPE",
      "service": "RAGFlow",
      "description": "Metadata database dialect (users, datasets, documents, agents). Upstream default.",
      "secret": false,
      "strategy": "default",
      "default": "mysql"
    },
    {
      "key": "ES_HOST",
      "service": "RAGFlow",
      "description": "Private hostname of the Elasticsearch service. RAGFlow builds 'http://$ES_HOST:9200' internally, so the port is fixed at 9200 and must not be included here.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "ES_USER",
      "service": "RAGFlow",
      "description": "Elasticsearch user. The image only bootstraps the built-in 'elastic' superuser.",
      "secret": false,
      "strategy": "default",
      "default": "elastic"
    },
    {
      "key": "DOC_ENGINE",
      "service": "RAGFlow",
      "description": "Search backend for chunks and vectors. 'elasticsearch' is the upstream default and the only engine this template ships; changing it without adding the matching service breaks the app.",
      "secret": false,
      "strategy": "default",
      "default": "elasticsearch"
    },
    {
      "key": "MINIO_HOST",
      "service": "RAGFlow",
      "description": "Private hostname of the MinIO service. RAGFlow builds '$MINIO_HOST:9000' internally, so the port is fixed at 9000 and must not be included here.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MINIO_USER",
      "service": "RAGFlow",
      "description": "MinIO access key (mirrors the MinIO service).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MYSQL_HOST",
      "service": "RAGFlow",
      "description": "Private hostname of the MySQL service. Also used by the start command to wait for MySQL before booting.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "MYSQL_PORT",
      "service": "RAGFlow",
      "description": "MySQL port on the private network.",
      "secret": false,
      "strategy": "default",
      "default": "3306"
    },
    {
      "key": "MYSQL_USER",
      "service": "RAGFlow",
      "description": "MySQL user. The MySQL image grants it every privilege on MYSQL_DBNAME, which is all RAGFlow's table creation and migrations need.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDIS_HOST",
      "service": "RAGFlow",
      "description": "Private hostname of the Valkey (Redis) service. RAGFlow builds '$REDIS_HOST:6379' internally, so the port is fixed at 6379 and must not be included here.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "HF_ENDPOINT",
      "service": "RAGFlow",
      "description": "Hugging Face mirror used for any model download. The image hardcodes https://hf-mirror.com, which is slow or unreachable from most Railway regions. The bundled DeepDoc OCR and layout models ship inside the image, so nothing is downloaded in the default configuration.",
      "secret": false,
      "strategy": "default",
      "default": "https://huggingface.co"
    },
    {
      "key": "USE_DOCLING",
      "service": "RAGFlow",
      "description": "Keep false. true makes the container pip-install Docling from a mirror on every boot.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MYSQL_DBNAME",
      "service": "RAGFlow",
      "description": "Metadata database name (created by the MySQL image on first boot).",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "STORAGE_IMPL",
      "service": "RAGFlow",
      "description": "Object storage backend for uploaded files. MINIO points at the MinIO service in this template. AWS_S3, OSS, GCS and AZURE_* are also supported by RAGFlow but need their own settings.",
      "secret": false,
      "strategy": "default",
      "default": "MINIO"
    },
    {
      "key": "DOC_BULK_SIZE",
      "service": "RAGFlow",
      "description": "Documents processed per batch by a task executor (upstream default).",
      "secret": false,
      "strategy": "default",
      "default": "4"
    },
    {
      "key": "MINIO_PASSWORD",
      "service": "RAGFlow",
      "description": "MinIO secret key (mirrors the MinIO service).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MYSQL_PASSWORD",
      "service": "RAGFlow",
      "description": "MySQL password (mirrors the MySQL service).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDIS_PASSWORD",
      "service": "RAGFlow",
      "description": "Valkey password (mirrors the Redis service).",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "API_PROXY_SCHEME",
      "service": "RAGFlow",
      "description": "Selects the pure-Python deployment: nginx proxies /v1 and /api to the Python API on 9380 and /api/v1/admin to the admin server on 9381. The 'go' and 'hybrid' schemes additionally need NATS, Kvrocks and ClickHouse services, which this template does not include.",
      "secret": false,
      "strategy": "default",
      "default": "python"
    },
    {
      "key": "ELASTIC_PASSWORD",
      "service": "RAGFlow",
      "description": "Elasticsearch password (mirrors the Elasticsearch service). Changing it later has no effect: the password is stored in the Elasticsearch security index after the first boot.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MYSQL_MAX_PACKET",
      "service": "RAGFlow",
      "description": "Client-side max_allowed_packet passed to the MySQL driver. Kept equal to the --max-allowed-packet the MySQL service starts with.",
      "secret": false,
      "strategy": "default",
      "default": "1073741824"
    },
    {
      "key": "REGISTER_ENABLED",
      "service": "RAGFlow",
      "description": "0 = self-service signup is closed, which is the safe default for a public URL: log in as admin@ragflow.io with ADMIN_DEFAULT_PASSWORD and invite teammates from the UI. Set it to 1 (and redeploy) to let anyone with the URL create an account.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "RAGFLOW_SECRET_KEY",
      "service": "RAGFlow",
      "description": "Signing key for the session tokens the API hands out at login. Without it RAGFlow generates one and caches it in Redis, so flushing Redis would log everyone out. Rotating it invalidates all existing sessions.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "EMBEDDING_BATCH_SIZE",
      "service": "RAGFlow",
      "description": "Chunks per embedding request (upstream default). Lower it if your embedding provider rate-limits you.",
      "secret": false,
      "strategy": "default",
      "default": "16"
    },
    {
      "key": "ADMIN_DEFAULT_PASSWORD",
      "service": "RAGFlow",
      "description": "Password of the admin@ragflow.io superuser that the admin server creates on first boot. This is your first login for BOTH the web UI and the admin API. Copy it, then change the password in the UI. It is only read when no superuser exists yet.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "DISABLE_PASSWORD_LOGIN",
      "service": "RAGFlow",
      "description": "Keep false. Setting it true hides the password form and leaves only OAuth/OIDC, which is not configured in this template, so nobody could log in.",
      "secret": true,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT",
      "service": "RAGFlow",
      "description": "Required by the .NET component used for .pptx parsing (upstream value).",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "REDIS_URL",
      "service": "Redis",
      "description": "Convenience connection string over the private network. RAGFlow uses database 1, as upstream does.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDIS_PASSWORD",
      "service": "Redis",
      "description": "Generated password (passed to valkey-server --requirepass by the start command) and referenced by the RAGFlow service.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "ragflow-document-rag-stack"
      }
    },
    "cli": "railway deploy --template ragflow-document-rag-stack",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "f948b6e2-3876-4165-9276-de597bf23da4",
            "serializedConfig": {
              "services": {
                "5ad730bb-2859-40a7-9b72-ce26ce5b6ff5": {
                  "icon": "https://devicons.railway.app/i/elasticsearch.svg",
                  "name": "Elasticsearch",
                  "deploy": {
                    "startCommand": "sh -c 'echo IyEvdXNyL2Jpbi9lbnYgYmFzaAojIFJhaWx3YXkgc3RhcnQgY29tbWFuZCBmb3IgdGhlIEVsYXN0aWNzZWFyY2ggc2VydmljZSAoZWxhc3RpY3NlYXJjaDo4LjExLjMpLgojIFRoaXMgZmlsZSBpcyB0aGUgU09VUkNFIG9mIHRoZSBiYXNlNjQgYmxvYiBpbiBzZXJpYWxpemVkLWNvbmZpZy5qc29uIC0+CiMgc2VydmljZXMuRWxhc3RpY3NlYXJjaC5kZXBsb3kuc3RhcnRDb21tYW5kLiBBZnRlciBlZGl0aW5nLCByZWdlbmVyYXRlIHdpdGg6CiMgICBiYXNlNjQgPCByYWdmbG93LXJhaWx3YXkvc3RhcnQtZWxhc3RpY3NlYXJjaC5zaCB8IHRyIC1kICdcbicKIwojIFdoeSBhIHdyYXBwZXIgYXQgYWxsOgojICAxLiBSYWlsd2F5IG1vdW50cyB2b2x1bWVzIGFzIHJvb3QgYW5kIHRoZSBvZmZpY2lhbCBpbWFnZSBydW5zIGFzIHVpZCAxMDAwLCBzbwojICAgICB0aGUgc2VydmljZSBzZXRzIFJBSUxXQVlfUlVOX1VJRD0wIGFuZCB0aGlzIHNjcmlwdCBjaG93bnMgdGhlIGRhdGEgZGlyIGFuZAojICAgICB0aGVuIGRyb3BzIGJhY2sgdG8gMTAwMC4gRWxhc3RpY3NlYXJjaCByZWZ1c2VzIHRvIHJ1biBhcyByb290LCBzbwojICAgICBSQUlMV0FZX1JVTl9VSUQ9MCBhbG9uZSBpcyBub3QgZW5vdWdoLgojICAyLiBUaGUgaW1hZ2UncyAvdG1wIGlzIG5vdCB3cml0YWJsZSBieSB1aWQgMTAwMCAodXBzdHJlYW0gY29tcG9zZSB3b3JrcyBhcm91bmQKIyAgICAgaXQgd2l0aCBhIHRtcGZzKTsgdGhlIEpWTSBuZWVkcyBhIHdyaXRhYmxlIHRlbXAgZGlyLgojICAzLiBUaGUgc3RvY2sgY29uZmlnL2VsYXN0aWNzZWFyY2gueW1sIGhhcyAibmV0d29yay5ob3N0OiAwLjAuMC4wIiAoSVB2NCBvbmx5KQojICAgICBhbmQgbm8gZXhwbGljaXQgc2VjdXJpdHkgc2V0dGluZ3MsIHdoaWNoIG1ha2VzIEVTIDggcnVuIGl0cyBUTFMKIyAgICAgYXV0by1jb25maWd1cmF0aW9uIG9uIGZpcnN0IGJvb3QuIFJBR0Zsb3cgdGFsa3MgcGxhaW4gaHR0cDovLyBvdmVyCiMgICAgIFJhaWx3YXkncyBJUHY2IHByaXZhdGUgbmV0d29yaywgc28gYm90aCBoYXZlIHRvIGJlIHBpbm5lZCBoZXJlLiBUaGUgZmlsZSBpcwojICAgICBvdmVyd3JpdHRlbiAobm90IGFwcGVuZGVkKSBiZWNhdXNlIGEgZHVwbGljYXRlIFlBTUwga2V5IGlzIGEgcGFyc2UgZXJyb3IuCnNldCAtZQoKREFUQV9ESVI9L3Vzci9zaGFyZS9lbGFzdGljc2VhcmNoL2RhdGEKbWtkaXIgLXAgIiREQVRBX0RJUiIKY2hvd24gLVIgMTAwMDowICIkREFUQV9ESVIiCmNobW9kIDE3NzcgL3RtcCB8fCB0cnVlCgpjYXQgPiAvdXNyL3NoYXJlL2VsYXN0aWNzZWFyY2gvY29uZmlnL2VsYXN0aWNzZWFyY2gueW1sIDw8J1lNTCcKY2x1c3Rlci5uYW1lOiAiZG9ja2VyLWNsdXN0ZXIiCm5vZGUubmFtZTogInJhZ2Zsb3ctZXMwMSIKZGlzY292ZXJ5LnR5cGU6IHNpbmdsZS1ub2RlCm5ldHdvcmsuaG9zdDogIjo6IgpodHRwLnBvcnQ6IDkyMDAKcGF0aC5kYXRhOiAvdXNyL3NoYXJlL2VsYXN0aWNzZWFyY2gvZGF0YQpib290c3RyYXAubWVtb3J5X2xvY2s6IGZhbHNlCm5vZGUuc3RvcmUuYWxsb3dfbW1hcDogZmFsc2UKeHBhY2suc2VjdXJpdHkuZW5hYmxlZDogdHJ1ZQp4cGFjay5zZWN1cml0eS5lbnJvbGxtZW50LmVuYWJsZWQ6IGZhbHNlCnhwYWNrLnNlY3VyaXR5Lmh0dHAuc3NsLmVuYWJsZWQ6IGZhbHNlCnhwYWNrLnNlY3VyaXR5LnRyYW5zcG9ydC5zc2wuZW5hYmxlZDogZmFsc2UKeHBhY2subGljZW5zZS5zZWxmX2dlbmVyYXRlZC50eXBlOiBiYXNpYwpZTUwKCmNkIC91c3Ivc2hhcmUvZWxhc3RpY3NlYXJjaAplY2hvICJlbGFzdGljc2VhcmNoOiBzdGFydGluZyBzaW5nbGUtbm9kZSBvbiBbOjpdOjkyMDAgYXMgdWlkIDEwMDAiCmlmIGNvbW1hbmQgLXYgc2V0cHJpdiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICBleGVjIHNldHByaXYgLS1yZXVpZD0xMDAwIC0tcmVnaWQ9MCAtLWNsZWFyLWdyb3VwcyAvYmluL3RpbmkgLS0gL3Vzci9sb2NhbC9iaW4vZG9ja2VyLWVudHJ5cG9pbnQuc2ggZXN3cmFwcGVyCmVsc2UKICBleGVjIGNocm9vdCAtLXVzZXJzcGVjPTEwMDA6MCAtLXNraXAtY2hkaXIgLyAvYmluL3RpbmkgLS0gL3Vzci9sb2NhbC9iaW4vZG9ja2VyLWVudHJ5cG9pbnQuc2ggZXN3cmFwcGVyCmZpCg== | base64 -d > /tmp/railway-start.sh && exec bash /tmp/railway-start.sh'",
                    "requiredMountPath": "/usr/share/elasticsearch/data",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 5
                  },
                  "source": {
                    "image": "elasticsearch:8.11.3"
                  },
                  "variables": {
                    "ES_JAVA_OPTS": {
                      "description": "JVM heap. Elasticsearch sizes the heap from the visible machine memory when this is unset, which on Railway means a heap far larger than the service's limit. Keep Xms equal to Xmx; 512m works for small corpora, 2g upwards for large ones.",
                      "defaultValue": "-Xms1g -Xmx1g"
                    },
                    "RAILWAY_RUN_UID": {
                      "description": "Run the container as root. Railway mounts volumes as root and this image runs as uid 1000, so the start command chowns the data directory and then drops back to uid 1000 (Elasticsearch refuses to run as root).",
                      "defaultValue": "0"
                    },
                    "ELASTIC_PASSWORD": {
                      "description": "Password for the built-in 'elastic' user, referenced by the RAGFlow service. It is written into the security index at first bootstrap, so changing it later does not rotate the credential.",
                      "defaultValue": "{{ELASTIC_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Grace period so Elasticsearch can flush and shut down cleanly on redeploy.",
                      "defaultValue": "60"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "b2762fc7-a412-4a61-b6ae-dafabe13af09": {
                      "mountPath": "/usr/share/elasticsearch/data"
                    }
                  }
                },
                "9a3c1582-c375-49d1-b3f5-bc7d06fa130e": {
                  "icon": "https://devicons.railway.app/i/mysql.svg",
                  "name": "MySQL",
                  "deploy": {
                    "startCommand": "docker-entrypoint.sh mysqld --datadir=/var/lib/mysql/data --bind-address=:: --max_connections=1000 --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci --default-authentication-plugin=mysql_native_password --max-allowed-packet=1073741824 --binlog_expire_logs_seconds=604800 --innodb-buffer-pool-size=256M --innodb-redo-log-capacity=268435456",
                    "requiredMountPath": "/var/lib/mysql",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 5
                  },
                  "source": {
                    "image": "mysql:8.0.40"
                  },
                  "variables": {
                    "MYSQL_USER": {
                      "description": "Application user referenced by the RAGFlow service.",
                      "defaultValue": "ragflow"
                    },
                    "MYSQL_DATABASE": {
                      "description": "Database created on first boot and granted to MYSQL_USER. Same name as the upstream docker/init.sql.",
                      "defaultValue": "rag_flow"
                    },
                    "MYSQL_PASSWORD": {
                      "description": "Generated application password referenced by the RAGFlow service.",
                      "defaultValue": "{{MYSQL_PASSWORD}}"
                    },
                    "MYSQL_PRIVATE_URL": {
                      "description": "Convenience connection string over the private network (IPv6, includes the port).",
                      "defaultValue": "mysql://${{MYSQL_USER}}:${{MYSQL_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:3306/${{MYSQL_DATABASE}}"
                    },
                    "MYSQL_ROOT_PASSWORD": {
                      "description": "Generated root password. RAGFlow does not use it; it exists so the image can initialise.",
                      "defaultValue": "{{MYSQL_ROOT_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": true,
                      "description": "Grace period so MySQL can shut down cleanly on redeploy.",
                      "defaultValue": "60"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "e25b248a-8be5-474c-bf7f-4f15c7ec3a4b": {
                      "mountPath": "/var/lib/mysql"
                    }
                  }
                },
                "9f412ea8-c54c-41ef-908c-1e9eb80c2cb5": {
                  "icon": "https://devicons.railway.app/i/minio.svg",
                  "name": "MinIO",
                  "deploy": {
                    "startCommand": "sh -c \"exec minio server /data --address :9000 --console-address :9001\"",
                    "requiredMountPath": "/data",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 5
                  },
                  "source": {
                    "image": "quay.io/minio/minio:RELEASE.2025-03-12T18-04-18Z"
                  },
                  "variables": {
                    "MINIO_ROOT_USER": {
                      "description": "S3 access key. Referenced by the RAGFlow service as MINIO_USER.",
                      "defaultValue": "ragflow"
                    },
                    "MINIO_ROOT_PASSWORD": {
                      "description": "Generated S3 secret key. Referenced by the RAGFlow service as MINIO_PASSWORD.",
                      "defaultValue": "{{MINIO_ROOT_PASSWORD}}"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "c712b2ac-5176-42c4-875f-fa1c1019837f": {
                      "mountPath": "/data"
                    }
                  }
                },
                "a6850e4b-4075-44a8-8d8e-4ab7daf8f851": {
                  "icon": "https://avatars.githubusercontent.com/u/69962740?v=4",
                  "name": "RAGFlow",
                  "deploy": {
                    "startCommand": "sh -c 'echo IyEvdXNyL2Jpbi9lbnYgYmFzaAojIFJhaWx3YXkgc3RhcnQgY29tbWFuZCBmb3IgdGhlIFJBR0Zsb3cgc2VydmljZSAoaW5maW5pZmxvdy9yYWdmbG93OnYwLjI3LjIpLgojIFRoaXMgZmlsZSBpcyB0aGUgU09VUkNFIG9mIHRoZSBiYXNlNjQgYmxvYiBpbiBzZXJpYWxpemVkLWNvbmZpZy5qc29uIC0+CiMgc2VydmljZXMuUkFHRmxvdy5kZXBsb3kuc3RhcnRDb21tYW5kLiBBZnRlciBlZGl0aW5nLCByZWdlbmVyYXRlIHdpdGg6CiMgICBiYXNlNjQgPCByYWdmbG93LXJhaWx3YXkvc3RhcnQtcmFnZmxvdy5zaCB8IHRyIC1kICdcbicKIyBhbmQgcGFzdGUgaXQgYmFjayBpbnRvIHRoZSBzdGFydENvbW1hbmQuCiMKIyBXaHkgYSB3cmFwcGVyIGF0IGFsbDoKIyAgMS4gVGhlIGltYWdlJ3Mgbmdpbnggb25seSBoYXMgImxpc3RlbiA4MDsiIChJUHY0KS4gUmFpbHdheSdzIGhlYWx0aGNoZWNrIGFuZAojICAgICBlZGdlIHByb3h5IHByb2JlICRQT1JULCBhbmQgdGhlIHByaXZhdGUgbmV0d29yayBpcyBJUHY2LCBzbyB3ZSByZXdyaXRlIHRoZQojICAgICBsaXN0ZW5lciB0byAkUE9SVCBhbmQgYWRkIGFuIFs6Ol0gbGlzdGVuZXIuCiMgIDIuIGVudHJ5cG9pbnQuc2ggY29ubmVjdHMgdG8gTXlTUUwgLyBFbGFzdGljc2VhcmNoIC8gUmVkaXMgLyBNaW5JTyBvbiB0aGUKIyAgICAgZmlyc3QgbGluZSBpdCBydW5zIChlbnN1cmVfZGJfaW5pdCkgYW5kIGhhcyBgc2V0IC1lYC4gV2l0aG91dCBhIHdhaXQgbG9vcCBhCiMgICAgIGNvbGQgc3RhcnQgcmFjZXMgdGhlIGRhdGFiYXNlcyBhbmQgdGhlIGNvbnRhaW5lciBjcmFzaC1sb29wcy4Kc2V0IC1lCgpQPSIke1BPUlQ6LTgwfSIKZm9yIGYgaW4gL2V0Yy9uZ2lueC9jb25mLmQvcmFnZmxvdy5jb25mLnB5dGhvbiAvZXRjL25naW54L2NvbmYuZC9yYWdmbG93LmNvbmYuaHlicmlkIC9ldGMvbmdpbngvY29uZi5kL3JhZ2Zsb3cuY29uZi5nb2xhbmc7IGRvCiAgaWYgWyAtZiAiJGYiIF07IHRoZW4KICAgIHNlZCAtaSAicy9eXCggKlwpbGlzdGVuIDgwOy9cMWxpc3RlbiAke1B9O1xuXDFsaXN0ZW4gWzo6XToke1B9Oy8iICIkZiIKICBmaQpkb25lCmVjaG8gInJhZ2Zsb3c6IG5naW54IHdpbGwgbGlzdGVuIG9uICR7UH0gKElQdjQgKyBJUHY2KSIKCmZvciB0IGluICIke01ZU1FMX0hPU1R9OiR7TVlTUUxfUE9SVDotMzMwNn0iICIke0VTX0hPU1R9OjkyMDAiICIke1JFRElTX0hPU1R9OjYzNzkiICIke01JTklPX0hPU1R9OjkwMDAiOyBkbwogIGg9IiR7dCU6Kn0iCiAgcD0iJHt0IyMqOn0iCiAgbj0wCiAgdW50aWwgKGV4ZWMgMzw+Ii9kZXYvdGNwLyRoLyRwIikgMj4vZGV2L251bGw7IGRvCiAgICBuPSQoKG4gKyAxKSkKICAgIGlmIFsgIiRuIiAtZ2UgMTUwIF07IHRoZW4KICAgICAgZWNobyAicmFnZmxvdzogZ2F2ZSB1cCB3YWl0aW5nIGZvciAkdCIKICAgICAgYnJlYWsKICAgIGZpCiAgICBlY2hvICJyYWdmbG93OiB3YWl0aW5nIGZvciAkdCIKICAgIHNsZWVwIDIKICBkb25lCmRvbmUKCiMgLS1lbmFibGUtYWRtaW5zZXJ2ZXIgICAgICAgICAgICBib290c3RyYXBzIHRoZSBhZG1pbkByYWdmbG93LmlvIHN1cGVydXNlciBmcm9tCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBBRE1JTl9ERUZBVUxUX1BBU1NXT1JEIGFuZCBzZXJ2ZXMgL2FwaS92MS9hZG1pbgojIC0taW5pdC1tb2RlbC1wcm92aWRlci10YWJsZXMgICAgcnVucyB0b29scy9zY3JpcHRzL3J1bl9taWdyYXRpb25zLnNoIChzYW1lIGFzCiMgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGUgdXBzdHJlYW0gZG9ja2VyLWNvbXBvc2UgY29tbWFuZCkKZXhlYyAuL2VudHJ5cG9pbnQuc2ggLS1lbmFibGUtYWRtaW5zZXJ2ZXIgLS1pbml0LW1vZGVsLXByb3ZpZGVyLXRhYmxlcwo= | base64 -d > /tmp/railway-start.sh && exec bash /tmp/railway-start.sh'",
                    "healthcheckPath": "/api/v1/system/healthz",
                    "restartPolicyType": "ON_FAILURE",
                    "healthcheckTimeout": 900,
                    "restartPolicyMaxRetries": 5
                  },
                  "source": {
                    "image": "infiniflow/ragflow:v0.27.2"
                  },
                  "variables": {
                    "TZ": {
                      "description": "Container timezone used for log and task timestamps (upstream ships Asia/Shanghai).",
                      "defaultValue": "UTC"
                    },
                    "PORT": {
                      "description": "Port the bundled nginx listens on. Railway's healthcheck and edge proxy probe $PORT, and the start command rewrites nginx's 'listen 80' to this port (plus an IPv6 listener). Keep it equal to the public domain's target port.",
                      "defaultValue": "80"
                    },
                    "DB_TYPE": {
                      "description": "Metadata database dialect (users, datasets, documents, agents). Upstream default.",
                      "defaultValue": "mysql"
                    },
                    "ES_HOST": {
                      "description": "Private hostname of the Elasticsearch service. RAGFlow builds 'http://$ES_HOST:9200' internally, so the port is fixed at 9200 and must not be included here.",
                      "defaultValue": "${{Elasticsearch.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "ES_USER": {
                      "description": "Elasticsearch user. The image only bootstraps the built-in 'elastic' superuser.",
                      "defaultValue": "elastic"
                    },
                    "DOC_ENGINE": {
                      "description": "Search backend for chunks and vectors. 'elasticsearch' is the upstream default and the only engine this template ships; changing it without adding the matching service breaks the app.",
                      "defaultValue": "elasticsearch"
                    },
                    "MINIO_HOST": {
                      "description": "Private hostname of the MinIO service. RAGFlow builds '$MINIO_HOST:9000' internally, so the port is fixed at 9000 and must not be included here.",
                      "defaultValue": "${{MinIO.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "MINIO_USER": {
                      "description": "MinIO access key (mirrors the MinIO service).",
                      "defaultValue": "${{MinIO.MINIO_ROOT_USER}}"
                    },
                    "MYSQL_HOST": {
                      "description": "Private hostname of the MySQL service. Also used by the start command to wait for MySQL before booting.",
                      "defaultValue": "${{MySQL.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "MYSQL_PORT": {
                      "description": "MySQL port on the private network.",
                      "defaultValue": "3306"
                    },
                    "MYSQL_USER": {
                      "description": "MySQL user. The MySQL image grants it every privilege on MYSQL_DBNAME, which is all RAGFlow's table creation and migrations need.",
                      "defaultValue": "${{MySQL.MYSQL_USER}}"
                    },
                    "REDIS_HOST": {
                      "description": "Private hostname of the Valkey (Redis) service. RAGFlow builds '$REDIS_HOST:6379' internally, so the port is fixed at 6379 and must not be included here.",
                      "defaultValue": "${{Redis.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "HF_ENDPOINT": {
                      "description": "Hugging Face mirror used for any model download. The image hardcodes https://hf-mirror.com, which is slow or unreachable from most Railway regions. The bundled DeepDoc OCR and layout models ship inside the image, so nothing is downloaded in the default configuration.",
                      "defaultValue": "https://huggingface.co"
                    },
                    "USE_DOCLING": {
                      "description": "Keep false. true makes the container pip-install Docling from a mirror on every boot.",
                      "defaultValue": "false"
                    },
                    "MYSQL_DBNAME": {
                      "description": "Metadata database name (created by the MySQL image on first boot).",
                      "defaultValue": "${{MySQL.MYSQL_DATABASE}}"
                    },
                    "STORAGE_IMPL": {
                      "description": "Object storage backend for uploaded files. MINIO points at the MinIO service in this template. AWS_S3, OSS, GCS and AZURE_* are also supported by RAGFlow but need their own settings.",
                      "defaultValue": "MINIO"
                    },
                    "DOC_BULK_SIZE": {
                      "description": "Documents processed per batch by a task executor (upstream default).",
                      "defaultValue": "4"
                    },
                    "MINIO_PASSWORD": {
                      "description": "MinIO secret key (mirrors the MinIO service).",
                      "defaultValue": "${{MinIO.MINIO_ROOT_PASSWORD}}"
                    },
                    "MYSQL_PASSWORD": {
                      "description": "MySQL password (mirrors the MySQL service).",
                      "defaultValue": "${{MySQL.MYSQL_PASSWORD}}"
                    },
                    "REDIS_PASSWORD": {
                      "description": "Valkey password (mirrors the Redis service).",
                      "defaultValue": "${{Redis.REDIS_PASSWORD}}"
                    },
                    "SANDBOX_ENABLED": {
                      "isOptional": true,
                      "description": "Optional, leave unset. RAGFlow's code sandbox needs a privileged Docker-in-Docker executor service, which Railway does not offer."
                    },
                    "API_PROXY_SCHEME": {
                      "description": "Selects the pure-Python deployment: nginx proxies /v1 and /api to the Python API on 9380 and /api/v1/admin to the admin server on 9381. The 'go' and 'hybrid' schemes additionally need NATS, Kvrocks and ClickHouse services, which this template does not include.",
                      "defaultValue": "python"
                    },
                    "ELASTIC_PASSWORD": {
                      "description": "Elasticsearch password (mirrors the Elasticsearch service). Changing it later has no effect: the password is stored in the Elasticsearch security index after the first boot.",
                      "defaultValue": "${{Elasticsearch.ELASTIC_PASSWORD}}"
                    },
                    "MYSQL_MAX_PACKET": {
                      "description": "Client-side max_allowed_packet passed to the MySQL driver. Kept equal to the --max-allowed-packet the MySQL service starts with.",
                      "defaultValue": "1073741824"
                    },
                    "REGISTER_ENABLED": {
                      "description": "0 = self-service signup is closed, which is the safe default for a public URL: log in as admin@ragflow.io with ADMIN_DEFAULT_PASSWORD and invite teammates from the UI. Set it to 1 (and redeploy) to let anyone with the URL create an account.",
                      "defaultValue": "0"
                    },
                    "MAX_CONTENT_LENGTH": {
                      "isOptional": true,
                      "description": "Optional. Maximum upload size in bytes (default 1 GB). The bundled nginx caps request bodies at 1024M, so raising this above that needs an nginx change too."
                    },
                    "RAGFLOW_SECRET_KEY": {
                      "description": "Signing key for the session tokens the API hands out at login. Without it RAGFlow generates one and caches it in Redis, so flushing Redis would log everyone out. Rotating it invalidates all existing sessions.",
                      "defaultValue": "{{RAGFLOW_SECRET_KEY}}"
                    },
                    "EMBEDDING_BATCH_SIZE": {
                      "description": "Chunks per embedding request (upstream default). Lower it if your embedding provider rate-limits you.",
                      "defaultValue": "16"
                    },
                    "ADMIN_DEFAULT_PASSWORD": {
                      "description": "Password of the admin@ragflow.io superuser that the admin server creates on first boot. This is your first login for BOTH the web UI and the admin API. Copy it, then change the password in the UI. It is only read when no superuser exists yet.",
                      "defaultValue": "{{ADMIN_DEFAULT_PASSWORD}}"
                    },
                    "DISABLE_PASSWORD_LOGIN": {
                      "description": "Keep false. Setting it true hides the password form and leaves only OAuth/OIDC, which is not configured in this template, so nobody could log in.",
                      "defaultValue": "false"
                    },
                    "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT": {
                      "description": "Required by the .NET component used for .pptx parsing (upstream value).",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {
                      "ragflow-dom": {
                        "port": 80
                      }
                    }
                  }
                },
                "dfde08c5-747f-4740-80b2-b2845acba63e": {
                  "icon": "https://devicons.railway.app/i/redis.svg",
                  "name": "Redis",
                  "deploy": {
                    "startCommand": "sh -c 'docker-entrypoint.sh valkey-server --requirepass \"$REDIS_PASSWORD\" --save 60 1 --dir /data --bind :: 0.0.0.0 --maxmemory 512mb --maxmemory-policy volatile-lru'",
                    "requiredMountPath": "/data",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 5
                  },
                  "source": {
                    "image": "valkey/valkey:8.1.4"
                  },
                  "variables": {
                    "REDIS_URL": {
                      "description": "Convenience connection string over the private network. RAGFlow uses database 1, as upstream does.",
                      "defaultValue": "redis://:${{REDIS_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:6379/1"
                    },
                    "REDIS_PASSWORD": {
                      "description": "Generated password (passed to valkey-server --requirepass by the start command) and referenced by the RAGFlow service.",
                      "defaultValue": "{{REDIS_PASSWORD}}"
                    }
                  },
                  "networking": {
                    "tcpProxies": {},
                    "serviceDomains": {}
                  },
                  "volumeMounts": {
                    "2f86c7f9-4350-4177-b17b-1cbd84331e86": {
                      "mountPath": "/data"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "RAGFlow",
      "method": "GET",
      "path": "/api/v1/system/healthz",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 5,
    "needs_volume": true
  },
  "generated_at": "2026-09-21T04:14:46.774Z",
  "generator_version": "0.1.0",
  "status": "validated",
  "validated_at": "2026-09-21T02:57:52.653Z",
  "success_rate_30d": 1,
  "validation": {
    "last_run_id": "run_1e1a24be43904e8a9781",
    "checks": [
      {
        "name": "workflow_completed",
        "passed": true
      },
      {
        "name": "all_services_deployed",
        "passed": true
      },
      {
        "name": "healthcheck",
        "passed": true
      },
      {
        "name": "stays_up",
        "passed": true
      }
    ],
    "typical_ready_seconds": 121,
    "typical_build_seconds": 0,
    "typical_start_seconds": 94,
    "slowest_service": "RAGFlow"
  }
}
