{
  "manifest_version": "1.0.0",
  "template": {
    "id": "54b8c06a-7e2e-4e74-9ffc-364d9fa0892e",
    "slug": "librechat-ai-chat",
    "name": "LibreChat | Open Source ChatGPT Alternative",
    "description": "5-service LibreChat with search and RAG. ~1.5 GB RAM, about $25/month",
    "url": "https://railway.com/deploy/librechat-ai-chat",
    "upstream": {
      "image": "ghcr.io/danny-avila/librechat:v0.8.7"
    }
  },
  "services": [
    {
      "name": "RAG",
      "source": {
        "image": "ghcr.io/danny-avila/librechat-rag-api-dev-lite:v0.9.0"
      },
      "needs_volume": false,
      "http": false
    },
    {
      "name": "MongoDB",
      "source": {
        "image": "mongo:8.0.29"
      },
      "needs_volume": true,
      "volume_mount_path": "/data/db",
      "http": false
    },
    {
      "name": "VectorDB",
      "source": {
        "image": "pgvector/pgvector:0.8.6-pg17-trixie"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/postgresql",
      "http": false
    },
    {
      "name": "Meilisearch",
      "source": {
        "image": "getmeili/meilisearch:v1.53.2"
      },
      "needs_volume": true,
      "volume_mount_path": "/meili_data",
      "http": false
    },
    {
      "name": "LibreChat",
      "source": {
        "image": "ghcr.io/danny-avila/librechat:v0.8.7"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": true
    }
  ],
  "required_inputs": [
    {
      "key": "PORT",
      "service": "RAG",
      "description": "Port Railway associates with this service. The application itself reads RAG_PORT and never looks at PORT — both are set, to the same number.",
      "secret": false,
      "strategy": "default",
      "default": "8000"
    },
    {
      "key": "DB_HOST",
      "service": "RAG",
      "description": "PostgreSQL host, over the private network.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "DB_PORT",
      "service": "RAG",
      "description": "PostgreSQL port.",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "RAG_HOST",
      "service": "RAG",
      "description": "Bind address, and the single setting that decides whether file search works. Railway's private network is IPv6, and this Python server defaults to IPv4 only — left alone it is unreachable from the chat service, which is why other deployments give it a public domain instead. There is no healthcheck on this service for the same reason: Railway probes over IPv4.",
      "secret": false,
      "strategy": "default",
      "default": "::"
    },
    {
      "key": "RAG_PORT",
      "service": "RAG",
      "description": "Port the application actually binds.",
      "secret": false,
      "strategy": "default",
      "default": "8000"
    },
    {
      "key": "JWT_SECRET",
      "service": "RAG",
      "description": "Must match the chat service's secret — it is what authenticates the requests between them.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "POSTGRES_DB",
      "service": "RAG",
      "description": "Database holding the embeddings.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "DEBUG_RAG_API",
      "service": "RAG",
      "description": "Verbose logging plus a set of extra debugging routes.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "POSTGRES_USER",
      "service": "RAG",
      "description": "PostgreSQL role.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "VECTOR_DB_TYPE",
      "service": "RAG",
      "description": "Vector store backend. pgvector keeps the embeddings in the PostgreSQL service next door.",
      "secret": false,
      "strategy": "default",
      "default": "pgvector"
    },
    {
      "key": "EMBEDDINGS_MODEL",
      "service": "RAG",
      "description": "Embedding model. Changing it after documents are indexed makes the existing vectors incomparable.",
      "secret": false,
      "strategy": "default",
      "default": "text-embedding-3-small"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "RAG",
      "description": "PostgreSQL password, referenced from the database service.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "RAG_OPENAI_API_KEY",
      "service": "RAG",
      "description": "Replace with a real OpenAI key to turn on file search. It is a placeholder rather than an empty value on purpose: the embeddings client is built when the process starts and an empty key crashes it in a loop, so the service would be down instead of merely feature-less.",
      "secret": true,
      "strategy": "default",
      "default": "sk-placeholder-set-a-real-openai-key-to-enable-rag"
    },
    {
      "key": "SCARF_NO_ANALYTICS",
      "service": "RAG",
      "description": "Stops the document-parsing library from phoning home on startup.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "EMBEDDINGS_PROVIDER",
      "service": "RAG",
      "description": "Who computes the embeddings. This is the lightweight image, so remote providers only; the local-model build is several gigabytes.",
      "secret": false,
      "strategy": "default",
      "default": "openai"
    },
    {
      "key": "PORT",
      "service": "MongoDB",
      "description": "Port MongoDB listens on inside the private network. There is no public proxy pointed at it.",
      "secret": false,
      "strategy": "default",
      "default": "27017"
    },
    {
      "key": "MONGO_INITDB_ROOT_PASSWORD",
      "service": "MongoDB",
      "description": "Generated once on deploy. Changing it here does not change it inside the database. The alphabet is restricted on purpose — a `/` or `@` would break the connection string.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "MONGO_INITDB_ROOT_USERNAME",
      "service": "MongoDB",
      "description": "Root user created on first boot.",
      "secret": false,
      "strategy": "default",
      "default": "mongo"
    },
    {
      "key": "PORT",
      "service": "VectorDB",
      "description": "Port PostgreSQL listens on inside the private network.",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "PGDATA",
      "service": "VectorDB",
      "description": "Data directory, deliberately below the volume's mount point: PostgreSQL refuses to initialise into a directory that already contains the volume's `lost+found`.",
      "secret": false,
      "strategy": "default",
      "default": "/var/lib/postgresql/data/pgdata"
    },
    {
      "key": "POSTGRES_DB",
      "service": "VectorDB",
      "description": "Database created on first boot, holding the document embeddings.",
      "secret": false,
      "strategy": "default",
      "default": "vectordb"
    },
    {
      "key": "POSTGRES_USER",
      "service": "VectorDB",
      "description": "Superuser role created on first boot.",
      "secret": false,
      "strategy": "default",
      "default": "postgres"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "VectorDB",
      "description": "Generated once on deploy. Changing it here does not change it inside the database.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "PORT",
      "service": "Meilisearch",
      "description": "Port Meilisearch listens on inside the private network.",
      "secret": false,
      "strategy": "default",
      "default": "7700"
    },
    {
      "key": "MEILI_ENV",
      "service": "Meilisearch",
      "description": "Production mode. The `development` value exposes an unauthenticated web interface.",
      "secret": false,
      "strategy": "default",
      "default": "production"
    },
    {
      "key": "MEILI_DB_PATH",
      "service": "Meilisearch",
      "description": "Index location, on the volume.",
      "secret": false,
      "strategy": "default",
      "default": "/meili_data/data.ms"
    },
    {
      "key": "MEILI_HTTP_ADDR",
      "service": "Meilisearch",
      "description": "Bind address. Railway's private network is IPv6, and this socket stays dual-stack, so both sides are covered.",
      "secret": false,
      "strategy": "default",
      "default": "[::]:7700"
    },
    {
      "key": "MEILI_MASTER_KEY",
      "service": "Meilisearch",
      "description": "Shared with the chat service — nothing else may query the index.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "MEILI_NO_ANALYTICS",
      "service": "Meilisearch",
      "description": "Stops Meilisearch from sending usage data home.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "HOST",
      "service": "LibreChat",
      "description": "Bind address. Nothing needs to reach this service over the private network, so IPv4 is enough and is what upstream documents.",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0"
    },
    {
      "key": "PORT",
      "service": "LibreChat",
      "description": "Port the app listens on and Railway routes public traffic and healthchecks to.",
      "secret": false,
      "strategy": "default",
      "default": "3080"
    },
    {
      "key": "SEARCH",
      "service": "LibreChat",
      "description": "Full-text search over conversations, served by the Meilisearch service. Turning it off makes that service dead weight.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "CREDS_IV",
      "service": "LibreChat",
      "description": "Initialisation vector for the same encryption. Exactly 32 hexadecimal characters, same warning as CREDS_KEY.",
      "secret": false,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "NO_INDEX",
      "service": "LibreChat",
      "description": "Asks search engines not to index this deployment.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "APP_TITLE",
      "service": "LibreChat",
      "description": "Name shown in the browser tab and on the login screen.",
      "secret": false,
      "strategy": "default",
      "default": "LibreChat"
    },
    {
      "key": "CREDS_KEY",
      "service": "LibreChat",
      "description": "AES-256 key encrypting the API keys users paste in. Exactly 64 hexadecimal characters. Nothing validates it at startup, so a malformed value surfaces much later as an unrelated-looking decryption error — and changing it makes every stored key unreadable.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "ENDPOINTS",
      "service": "LibreChat",
      "description": "Which built-in endpoints appear. `custom` is what enables the two dozen providers from the config file above.",
      "secret": false,
      "strategy": "default",
      "default": "openAI,agents,assistants,google,anthropic,custom"
    },
    {
      "key": "LOGIN_MAX",
      "service": "LibreChat",
      "description": "Failed logins allowed inside the window below.",
      "secret": false,
      "strategy": "default",
      "default": "7"
    },
    {
      "key": "MONGO_URI",
      "service": "LibreChat",
      "description": "Connection string. The database name (`/LibreChat`) and `authSource=admin` belong together: the root user lives in the `admin` database, so naming a database without the auth source breaks the login, and omitting both puts every conversation in mongo's default `test` database.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "GOOGLE_KEY",
      "service": "LibreChat",
      "description": "Gemini models. Replace with a real key to provide them for everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "JWT_SECRET",
      "service": "LibreChat",
      "description": "Signs session tokens, and signs the requests to the file-search service — which is why that service carries the same value.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "MEILI_HOST",
      "service": "LibreChat",
      "description": "Meilisearch address on the private network. There is no public port pointed at it.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "CONFIG_PATH",
      "service": "LibreChat",
      "description": "The endpoint and model catalogue, maintained by the LibreChat organisation and regenerated daily — which is why it points at a branch rather than a pinned commit: pin it and the model lists stop at the day you deployed. Replace with your own URL or file path to take control of it.",
      "secret": false,
      "strategy": "default",
      "default": "https://raw.githubusercontent.com/LibreChat-AI/librechat-config-yaml/main/librechat-env-l.yaml"
    },
    {
      "key": "LOG_TO_FILE",
      "service": "LibreChat",
      "description": "Writes rotating log files inside the container. Railway already captures stdout, so this only consumes disk.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "RAG_API_URL",
      "service": "LibreChat",
      "description": "The file-search sidecar, over the private network. It answers there because RAG_HOST on that service is set to `::` — a Python server left on its default binds IPv4 only and is unreachable inside Railway, which is why other deployments end up giving it a public domain.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "TITLE_CONVO",
      "service": "LibreChat",
      "description": "Lets the model name each conversation. Costs one short extra request per conversation.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "TRUST_PROXY",
      "service": "LibreChat",
      "description": "Number of proxies in front of the app. Railway terminates TLS one hop away; without this the rate limiter and the login ban see the proxy instead of the visitor and treat everyone as one person.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "XAI_API_KEY",
      "service": "LibreChat",
      "description": "xAI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "BAN_DURATION",
      "service": "LibreChat",
      "description": "How long a ban lasts, in milliseconds (2 hours).",
      "secret": false,
      "strategy": "default",
      "default": "7200000"
    },
    {
      "key": "BAN_INTERVAL",
      "service": "LibreChat",
      "description": "Score at which a ban is issued.",
      "secret": false,
      "strategy": "default",
      "default": "20"
    },
    {
      "key": "CONSOLE_JSON",
      "service": "LibreChat",
      "description": "Structured JSON logs. This is not cosmetic: the app prints its whole resolved config at info level on every boot, which the model catalogue makes about 114 KB, and the JSON transport is the only one that truncates it. Without this a single restart floods the log rate limit.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "GITHUB_TOKEN",
      "service": "LibreChat",
      "description": "GitHub Models, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "GROQ_API_KEY",
      "service": "LibreChat",
      "description": "Groq, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "LOGIN_WINDOW",
      "service": "LibreChat",
      "description": "Login window, in minutes.",
      "secret": false,
      "strategy": "default",
      "default": "5"
    },
    {
      "key": "REGISTER_MAX",
      "service": "LibreChat",
      "description": "Registrations allowed from one address inside the window below.",
      "secret": false,
      "strategy": "default",
      "default": "5"
    },
    {
      "key": "AI302_API_KEY",
      "service": "LibreChat",
      "description": "302AI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "DEBUG_CONSOLE",
      "service": "LibreChat",
      "description": "Debug-level console output, even noisier than the above.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "DEBUG_LOGGING",
      "service": "LibreChat",
      "description": "Verbose application logging. Upstream ships it on; on Railway that hits the per-deployment log rate limit and drops the lines a failed boot needs.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "DOMAIN_CLIENT",
      "service": "LibreChat",
      "description": "Public address of the app. It is also a hard requirement: an empty or malformed value stops the server from starting at all.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "DOMAIN_SERVER",
      "service": "LibreChat",
      "description": "Public address the API advertises, used for OAuth callbacks and links in emails.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "UNIFY_API_KEY",
      "service": "LibreChat",
      "description": "Unify, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "APIPIE_API_KEY",
      "service": "LibreChat",
      "description": "APIpie, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "BAN_VIOLATIONS",
      "service": "LibreChat",
      "description": "Temporarily bans an account or address that trips the scores below. Worth knowing before you script against the API: a request with no browser user-agent scores 20 and bans on the spot, and the ban is stored in MongoDB, so restarting does not clear it.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "COHERE_API_KEY",
      "service": "LibreChat",
      "description": "Cohere, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "MESSAGE_IP_MAX",
      "service": "LibreChat",
      "description": "Messages allowed per address inside the window below.",
      "secret": false,
      "strategy": "default",
      "default": "40"
    },
    {
      "key": "NVIDIA_API_KEY",
      "service": "LibreChat",
      "description": "Nvidia, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "OPENAI_API_KEY",
      "service": "LibreChat",
      "description": "`user_provided` means each person enters their own key in the interface. Replace with a real key to serve the whole deployment from one account — the same applies to every provider key below.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "OPENROUTER_KEY",
      "service": "LibreChat",
      "description": "OpenRouter, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "SESSION_EXPIRY",
      "service": "LibreChat",
      "description": "Access token lifetime in milliseconds (15 minutes).",
      "secret": false,
      "strategy": "default",
      "default": "900000"
    },
    {
      "key": "KLUSTER_API_KEY",
      "service": "LibreChat",
      "description": "Kluster, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "MISTRAL_API_KEY",
      "service": "LibreChat",
      "description": "Mistral, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "NANOGPT_API_KEY",
      "service": "LibreChat",
      "description": "NanoGPT, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "RAILWAY_RUN_UID",
      "service": "LibreChat",
      "description": "Runs the container as root. The image pins itself to the `node` user, and Railway attaches volumes owned by root — without this the app boots and then cannot write a single upload.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "REGISTER_WINDOW",
      "service": "LibreChat",
      "description": "Registration window, in minutes.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "DEEPSEEK_API_KEY",
      "service": "LibreChat",
      "description": "DeepSeek, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "LIMIT_MESSAGE_IP",
      "service": "LibreChat",
      "description": "Rate-limits messages per address.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "MEILI_MASTER_KEY",
      "service": "LibreChat",
      "description": "Shared secret between this service and Meilisearch — the same value has to appear on both, which the reference on the Meilisearch side takes care of.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "MESSAGE_USER_MAX",
      "service": "LibreChat",
      "description": "Messages per account, when the limit above is on.",
      "secret": false,
      "strategy": "default",
      "default": "40"
    },
    {
      "key": "ALLOW_EMAIL_LOGIN",
      "service": "LibreChat",
      "description": "Email and password sign-in.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "ANTHROPIC_API_KEY",
      "service": "LibreChat",
      "description": "Claude models. Replace with a real key to provide them for everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "FIREWORKS_API_KEY",
      "service": "LibreChat",
      "description": "Fireworks AI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "HUGGINGFACE_TOKEN",
      "service": "LibreChat",
      "description": "Hugging Face, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "MESSAGE_IP_WINDOW",
      "service": "LibreChat",
      "description": "That window, in minutes.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "OPENAI_MODERATION",
      "service": "LibreChat",
      "description": "Runs messages through OpenAI's moderation endpoint first. Needs OPENAI_MODERATION_API_KEY.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "SAMBANOVA_API_KEY",
      "service": "LibreChat",
      "description": "SambaNova, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "ALLOW_REGISTRATION",
      "service": "LibreChat",
      "description": "Open sign-up. The first account created becomes the administrator — so leave this on, register, then set it to false. The domain is public and the API keys are yours.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "ALLOW_SOCIAL_LOGIN",
      "service": "LibreChat",
      "description": "OAuth sign-in. Needs the client id and secret of a provider (Google, GitHub, Discord, OpenID) added as variables.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "ASSISTANTS_API_KEY",
      "service": "LibreChat",
      "description": "OpenAI Assistants API. Replace with a real key to provide it for everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "HYPERBOLIC_API_KEY",
      "service": "LibreChat",
      "description": "Hyperbolic, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "JWT_REFRESH_SECRET",
      "service": "LibreChat",
      "description": "Signs refresh tokens. Changing it logs everyone out.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "LIMIT_MESSAGE_USER",
      "service": "LibreChat",
      "description": "Rate-limits messages per account as well. Off, because the per-address limit already covers the usual case.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "MEILI_NO_ANALYTICS",
      "service": "LibreChat",
      "description": "Stops Meilisearch from sending usage data home.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "PERPLEXITY_API_KEY",
      "service": "LibreChat",
      "description": "Perplexity, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "TOGETHERAI_API_KEY",
      "service": "LibreChat",
      "description": "Together.ai, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
      "secret": true,
      "strategy": "default",
      "default": "user_provided"
    },
    {
      "key": "MESSAGE_USER_WINDOW",
      "service": "LibreChat",
      "description": "That window, in minutes.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "ALLOW_PASSWORD_RESET",
      "service": "LibreChat",
      "description": "Password reset by email. Off because it needs an SMTP server configured; without one the reset link is never sent.",
      "secret": true,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "REFRESH_TOKEN_EXPIRY",
      "service": "LibreChat",
      "description": "Refresh token lifetime in milliseconds (7 days) — how long a browser stays signed in.",
      "secret": true,
      "strategy": "default",
      "default": "604800000"
    },
    {
      "key": "LOGIN_VIOLATION_SCORE",
      "service": "LibreChat",
      "description": "Score added per failed login.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "CONCURRENT_MESSAGE_MAX",
      "service": "LibreChat",
      "description": "That cap.",
      "secret": false,
      "strategy": "default",
      "default": "2"
    },
    {
      "key": "MESSAGE_VIOLATION_SCORE",
      "service": "LibreChat",
      "description": "Score added for exceeding a message rate limit.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "ALLOW_SOCIAL_REGISTRATION",
      "service": "LibreChat",
      "description": "Lets an OAuth sign-in create an account that does not exist yet.",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "LIMIT_CONCURRENT_MESSAGES",
      "service": "LibreChat",
      "description": "Caps how many answers one user can have in flight.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "CONCURRENT_VIOLATION_SCORE",
      "service": "LibreChat",
      "description": "Score added for exceeding the concurrent-message limit.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    },
    {
      "key": "CONSOLE_JSON_STRING_LENGTH",
      "service": "LibreChat",
      "description": "Where the JSON transport cuts a long log string. Raise it if a truncated line hides something you need.",
      "secret": false,
      "strategy": "default",
      "default": "500"
    },
    {
      "key": "NON_BROWSER_VIOLATION_SCORE",
      "service": "LibreChat",
      "description": "Score added for a request without a browser user-agent — on its own enough to trigger a ban. Set to 0 if you intend to drive the REST API from scripts.",
      "secret": false,
      "strategy": "default",
      "default": "20"
    },
    {
      "key": "REGISTRATION_VIOLATION_SCORE",
      "service": "LibreChat",
      "description": "Score added per rejected registration.",
      "secret": false,
      "strategy": "default",
      "default": "1"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "librechat-ai-chat"
      }
    },
    "cli": "railway deploy --template librechat-ai-chat",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "54b8c06a-7e2e-4e74-9ffc-364d9fa0892e",
            "serializedConfig": {
              "buckets": {},
              "services": {
                "02646c39-a4b7-4dfb-8c01-d227c56b5dca": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/librechat.svg",
                  "name": "RAG",
                  "deploy": {
                    "startCommand": null,
                    "healthcheckPath": null,
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/danny-avila/librechat-rag-api-dev-lite:v0.9.0"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port Railway associates with this service. The application itself reads RAG_PORT and never looks at PORT — both are set, to the same number.",
                      "defaultValue": "8000"
                    },
                    "DB_HOST": {
                      "isOptional": false,
                      "description": "PostgreSQL host, over the private network.",
                      "defaultValue": "${{ VectorDB.RAILWAY_PRIVATE_DOMAIN }}"
                    },
                    "DB_PORT": {
                      "isOptional": false,
                      "description": "PostgreSQL port.",
                      "defaultValue": "5432"
                    },
                    "RAG_HOST": {
                      "isOptional": false,
                      "description": "Bind address, and the single setting that decides whether file search works. Railway's private network is IPv6, and this Python server defaults to IPv4 only — left alone it is unreachable from the chat service, which is why other deployments give it a public domain instead. There is no healthcheck on this service for the same reason: Railway probes over IPv4.",
                      "defaultValue": "::"
                    },
                    "RAG_PORT": {
                      "isOptional": false,
                      "description": "Port the application actually binds.",
                      "defaultValue": "8000"
                    },
                    "JWT_SECRET": {
                      "isOptional": false,
                      "description": "Must match the chat service's secret — it is what authenticates the requests between them.",
                      "defaultValue": "${{ LibreChat.JWT_SECRET }}"
                    },
                    "POSTGRES_DB": {
                      "isOptional": false,
                      "description": "Database holding the embeddings.",
                      "defaultValue": "${{ VectorDB.POSTGRES_DB }}"
                    },
                    "DEBUG_RAG_API": {
                      "isOptional": false,
                      "description": "Verbose logging plus a set of extra debugging routes.",
                      "defaultValue": "false"
                    },
                    "POSTGRES_USER": {
                      "isOptional": false,
                      "description": "PostgreSQL role.",
                      "defaultValue": "${{ VectorDB.POSTGRES_USER }}"
                    },
                    "VECTOR_DB_TYPE": {
                      "isOptional": false,
                      "description": "Vector store backend. pgvector keeps the embeddings in the PostgreSQL service next door.",
                      "defaultValue": "pgvector"
                    },
                    "EMBEDDINGS_MODEL": {
                      "isOptional": false,
                      "description": "Embedding model. Changing it after documents are indexed makes the existing vectors incomparable.",
                      "defaultValue": "text-embedding-3-small"
                    },
                    "POSTGRES_PASSWORD": {
                      "isOptional": false,
                      "description": "PostgreSQL password, referenced from the database service.",
                      "defaultValue": "${{ VectorDB.POSTGRES_PASSWORD }}"
                    },
                    "RAG_OPENAI_API_KEY": {
                      "isOptional": false,
                      "description": "Replace with a real OpenAI key to turn on file search. It is a placeholder rather than an empty value on purpose: the embeddings client is built when the process starts and an empty key crashes it in a loop, so the service would be down instead of merely feature-less.",
                      "defaultValue": "sk-placeholder-set-a-real-openai-key-to-enable-rag"
                    },
                    "SCARF_NO_ANALYTICS": {
                      "isOptional": false,
                      "description": "Stops the document-parsing library from phoning home on startup.",
                      "defaultValue": "true"
                    },
                    "EMBEDDINGS_PROVIDER": {
                      "isOptional": false,
                      "description": "Who computes the embeddings. This is the lightweight image, so remote providers only; the local-model build is several gigabytes.",
                      "defaultValue": "openai"
                    }
                  }
                },
                "2c21a8a8-efe1-4c7a-8d94-eb1ee81acdbe": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/mongodb.svg",
                  "name": "MongoDB",
                  "deploy": {
                    "startCommand": "docker-entrypoint.sh mongod --ipv6 --bind_ip ::,0.0.0.0",
                    "healthcheckPath": null,
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 2
                  },
                  "source": {
                    "image": "mongo:8.0.29"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port MongoDB listens on inside the private network. There is no public proxy pointed at it.",
                      "defaultValue": "27017"
                    },
                    "MONGO_INITDB_ROOT_PASSWORD": {
                      "isOptional": false,
                      "description": "Generated once on deploy. Changing it here does not change it inside the database. The alphabet is restricted on purpose — a `/` or `@` would break the connection string.",
                      "defaultValue": "{{MONGO_INITDB_ROOT_PASSWORD}}"
                    },
                    "MONGO_INITDB_ROOT_USERNAME": {
                      "isOptional": false,
                      "description": "Root user created on first boot.",
                      "defaultValue": "mongo"
                    }
                  },
                  "volumeMounts": {
                    "2c21a8a8-efe1-4c7a-8d94-eb1ee81acdbe": {
                      "mountPath": "/data/db"
                    }
                  }
                },
                "81f25daf-5584-4ca2-b1af-5e5fc79b946a": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/postgresql.svg",
                  "name": "VectorDB",
                  "deploy": {
                    "startCommand": null,
                    "healthcheckPath": null,
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 2
                  },
                  "source": {
                    "image": "pgvector/pgvector:0.8.6-pg17-trixie"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port PostgreSQL listens on inside the private network.",
                      "defaultValue": "5432"
                    },
                    "PGDATA": {
                      "isOptional": false,
                      "description": "Data directory, deliberately below the volume's mount point: PostgreSQL refuses to initialise into a directory that already contains the volume's `lost+found`.",
                      "defaultValue": "/var/lib/postgresql/data/pgdata"
                    },
                    "POSTGRES_DB": {
                      "isOptional": false,
                      "description": "Database created on first boot, holding the document embeddings.",
                      "defaultValue": "vectordb"
                    },
                    "POSTGRES_USER": {
                      "isOptional": false,
                      "description": "Superuser role created on first boot.",
                      "defaultValue": "postgres"
                    },
                    "POSTGRES_PASSWORD": {
                      "isOptional": false,
                      "description": "Generated once on deploy. Changing it here does not change it inside the database.",
                      "defaultValue": "{{POSTGRES_PASSWORD}}"
                    }
                  },
                  "volumeMounts": {
                    "81f25daf-5584-4ca2-b1af-5e5fc79b946a": {
                      "mountPath": "/var/lib/postgresql"
                    }
                  }
                },
                "8f42b2e7-2d82-4589-b348-0d8512242f9c": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/meilisearch.svg",
                  "name": "Meilisearch",
                  "deploy": {
                    "startCommand": null,
                    "healthcheckPath": null,
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 2
                  },
                  "source": {
                    "image": "getmeili/meilisearch:v1.53.2"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "Port Meilisearch listens on inside the private network.",
                      "defaultValue": "7700"
                    },
                    "MEILI_ENV": {
                      "isOptional": false,
                      "description": "Production mode. The `development` value exposes an unauthenticated web interface.",
                      "defaultValue": "production"
                    },
                    "MEILI_DB_PATH": {
                      "isOptional": false,
                      "description": "Index location, on the volume.",
                      "defaultValue": "/meili_data/data.ms"
                    },
                    "MEILI_HTTP_ADDR": {
                      "isOptional": false,
                      "description": "Bind address. Railway's private network is IPv6, and this socket stays dual-stack, so both sides are covered.",
                      "defaultValue": "[::]:7700"
                    },
                    "MEILI_MASTER_KEY": {
                      "isOptional": false,
                      "description": "Shared with the chat service — nothing else may query the index.",
                      "defaultValue": "${{ LibreChat.MEILI_MASTER_KEY }}"
                    },
                    "MEILI_NO_ANALYTICS": {
                      "isOptional": false,
                      "description": "Stops Meilisearch from sending usage data home.",
                      "defaultValue": "true"
                    }
                  },
                  "volumeMounts": {
                    "8f42b2e7-2d82-4589-b348-0d8512242f9c": {
                      "mountPath": "/meili_data"
                    }
                  }
                },
                "93e38250-fc85-4858-8ce0-f88ff0218b71": {
                  "icon": "https://cdn.jsdelivr.net/gh/homarr-labs/dashboard-icons/svg/librechat.svg",
                  "name": "LibreChat",
                  "deploy": {
                    "startCommand": "sh -c 'mkdir -p /data/uploads /data/images && rm -rf /app/uploads /app/client/public/images && ln -sfn /data/uploads /app/uploads && ln -sfn /data/images /app/client/public/images && exec npm run backend'",
                    "healthcheckPath": "/health",
                    "restartPolicyType": "ON_FAILURE",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/danny-avila/librechat:v0.8.7"
                  },
                  "variables": {
                    "HOST": {
                      "isOptional": false,
                      "description": "Bind address. Nothing needs to reach this service over the private network, so IPv4 is enough and is what upstream documents.",
                      "defaultValue": "0.0.0.0"
                    },
                    "PORT": {
                      "isOptional": false,
                      "description": "Port the app listens on and Railway routes public traffic and healthchecks to.",
                      "defaultValue": "3080"
                    },
                    "SEARCH": {
                      "isOptional": false,
                      "description": "Full-text search over conversations, served by the Meilisearch service. Turning it off makes that service dead weight.",
                      "defaultValue": "true"
                    },
                    "CREDS_IV": {
                      "isOptional": false,
                      "description": "Initialisation vector for the same encryption. Exactly 32 hexadecimal characters, same warning as CREDS_KEY.",
                      "defaultValue": "{{CREDS_IV}}"
                    },
                    "NO_INDEX": {
                      "isOptional": false,
                      "description": "Asks search engines not to index this deployment.",
                      "defaultValue": "true"
                    },
                    "APP_TITLE": {
                      "isOptional": false,
                      "description": "Name shown in the browser tab and on the login screen.",
                      "defaultValue": "LibreChat"
                    },
                    "CREDS_KEY": {
                      "isOptional": false,
                      "description": "AES-256 key encrypting the API keys users paste in. Exactly 64 hexadecimal characters. Nothing validates it at startup, so a malformed value surfaces much later as an unrelated-looking decryption error — and changing it makes every stored key unreadable.",
                      "defaultValue": "{{CREDS_KEY}}"
                    },
                    "ENDPOINTS": {
                      "isOptional": false,
                      "description": "Which built-in endpoints appear. `custom` is what enables the two dozen providers from the config file above.",
                      "defaultValue": "openAI,agents,assistants,google,anthropic,custom"
                    },
                    "LOGIN_MAX": {
                      "isOptional": false,
                      "description": "Failed logins allowed inside the window below.",
                      "defaultValue": "7"
                    },
                    "MONGO_URI": {
                      "isOptional": false,
                      "description": "Connection string. The database name (`/LibreChat`) and `authSource=admin` belong together: the root user lives in the `admin` database, so naming a database without the auth source breaks the login, and omitting both puts every conversation in mongo's default `test` database.",
                      "defaultValue": "mongodb://mongo:${{ MongoDB.MONGO_INITDB_ROOT_PASSWORD }}@${{ MongoDB.RAILWAY_PRIVATE_DOMAIN }}:27017/LibreChat?authSource=admin"
                    },
                    "GOOGLE_KEY": {
                      "isOptional": false,
                      "description": "Gemini models. Replace with a real key to provide them for everyone.",
                      "defaultValue": "user_provided"
                    },
                    "JWT_SECRET": {
                      "isOptional": false,
                      "description": "Signs session tokens, and signs the requests to the file-search service — which is why that service carries the same value.",
                      "defaultValue": "{{JWT_SECRET}}"
                    },
                    "MEILI_HOST": {
                      "isOptional": false,
                      "description": "Meilisearch address on the private network. There is no public port pointed at it.",
                      "defaultValue": "http://${{ Meilisearch.RAILWAY_PRIVATE_DOMAIN }}:7700"
                    },
                    "CONFIG_PATH": {
                      "isOptional": false,
                      "description": "The endpoint and model catalogue, maintained by the LibreChat organisation and regenerated daily — which is why it points at a branch rather than a pinned commit: pin it and the model lists stop at the day you deployed. Replace with your own URL or file path to take control of it.",
                      "defaultValue": "https://raw.githubusercontent.com/LibreChat-AI/librechat-config-yaml/main/librechat-env-l.yaml"
                    },
                    "LOG_TO_FILE": {
                      "isOptional": false,
                      "description": "Writes rotating log files inside the container. Railway already captures stdout, so this only consumes disk.",
                      "defaultValue": "false"
                    },
                    "RAG_API_URL": {
                      "isOptional": false,
                      "description": "The file-search sidecar, over the private network. It answers there because RAG_HOST on that service is set to `::` — a Python server left on its default binds IPv4 only and is unreachable inside Railway, which is why other deployments end up giving it a public domain.",
                      "defaultValue": "http://${{ RAG.RAILWAY_PRIVATE_DOMAIN }}:8000"
                    },
                    "TITLE_CONVO": {
                      "isOptional": false,
                      "description": "Lets the model name each conversation. Costs one short extra request per conversation.",
                      "defaultValue": "true"
                    },
                    "TRUST_PROXY": {
                      "isOptional": false,
                      "description": "Number of proxies in front of the app. Railway terminates TLS one hop away; without this the rate limiter and the login ban see the proxy instead of the visitor and treat everyone as one person.",
                      "defaultValue": "1"
                    },
                    "XAI_API_KEY": {
                      "isOptional": false,
                      "description": "xAI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "BAN_DURATION": {
                      "isOptional": false,
                      "description": "How long a ban lasts, in milliseconds (2 hours).",
                      "defaultValue": "7200000"
                    },
                    "BAN_INTERVAL": {
                      "isOptional": false,
                      "description": "Score at which a ban is issued.",
                      "defaultValue": "20"
                    },
                    "CONSOLE_JSON": {
                      "isOptional": false,
                      "description": "Structured JSON logs. This is not cosmetic: the app prints its whole resolved config at info level on every boot, which the model catalogue makes about 114 KB, and the JSON transport is the only one that truncates it. Without this a single restart floods the log rate limit.",
                      "defaultValue": "true"
                    },
                    "GITHUB_TOKEN": {
                      "isOptional": false,
                      "description": "GitHub Models, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "GROQ_API_KEY": {
                      "isOptional": false,
                      "description": "Groq, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "LOGIN_WINDOW": {
                      "isOptional": false,
                      "description": "Login window, in minutes.",
                      "defaultValue": "5"
                    },
                    "REGISTER_MAX": {
                      "isOptional": false,
                      "description": "Registrations allowed from one address inside the window below.",
                      "defaultValue": "5"
                    },
                    "AI302_API_KEY": {
                      "isOptional": false,
                      "description": "302AI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "DEBUG_CONSOLE": {
                      "isOptional": false,
                      "description": "Debug-level console output, even noisier than the above.",
                      "defaultValue": "false"
                    },
                    "DEBUG_LOGGING": {
                      "isOptional": false,
                      "description": "Verbose application logging. Upstream ships it on; on Railway that hits the per-deployment log rate limit and drops the lines a failed boot needs.",
                      "defaultValue": "false"
                    },
                    "DOMAIN_CLIENT": {
                      "isOptional": false,
                      "description": "Public address of the app. It is also a hard requirement: an empty or malformed value stops the server from starting at all.",
                      "defaultValue": "https://${{ RAILWAY_PUBLIC_DOMAIN }}"
                    },
                    "DOMAIN_SERVER": {
                      "isOptional": false,
                      "description": "Public address the API advertises, used for OAuth callbacks and links in emails.",
                      "defaultValue": "https://${{ RAILWAY_PUBLIC_DOMAIN }}"
                    },
                    "UNIFY_API_KEY": {
                      "isOptional": false,
                      "description": "Unify, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "APIPIE_API_KEY": {
                      "isOptional": false,
                      "description": "APIpie, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "BAN_VIOLATIONS": {
                      "isOptional": false,
                      "description": "Temporarily bans an account or address that trips the scores below. Worth knowing before you script against the API: a request with no browser user-agent scores 20 and bans on the spot, and the ban is stored in MongoDB, so restarting does not clear it.",
                      "defaultValue": "true"
                    },
                    "COHERE_API_KEY": {
                      "isOptional": false,
                      "description": "Cohere, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "MESSAGE_IP_MAX": {
                      "isOptional": false,
                      "description": "Messages allowed per address inside the window below.",
                      "defaultValue": "40"
                    },
                    "NVIDIA_API_KEY": {
                      "isOptional": false,
                      "description": "Nvidia, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "OPENAI_API_KEY": {
                      "isOptional": false,
                      "description": "`user_provided` means each person enters their own key in the interface. Replace with a real key to serve the whole deployment from one account — the same applies to every provider key below.",
                      "defaultValue": "user_provided"
                    },
                    "OPENROUTER_KEY": {
                      "isOptional": false,
                      "description": "OpenRouter, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "SESSION_EXPIRY": {
                      "isOptional": false,
                      "description": "Access token lifetime in milliseconds (15 minutes).",
                      "defaultValue": "900000"
                    },
                    "KLUSTER_API_KEY": {
                      "isOptional": false,
                      "description": "Kluster, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "MISTRAL_API_KEY": {
                      "isOptional": false,
                      "description": "Mistral, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "NANOGPT_API_KEY": {
                      "isOptional": false,
                      "description": "NanoGPT, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "RAILWAY_RUN_UID": {
                      "isOptional": false,
                      "description": "Runs the container as root. The image pins itself to the `node` user, and Railway attaches volumes owned by root — without this the app boots and then cannot write a single upload.",
                      "defaultValue": "0"
                    },
                    "REGISTER_WINDOW": {
                      "isOptional": false,
                      "description": "Registration window, in minutes.",
                      "defaultValue": "60"
                    },
                    "DEEPSEEK_API_KEY": {
                      "isOptional": false,
                      "description": "DeepSeek, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "LIMIT_MESSAGE_IP": {
                      "isOptional": false,
                      "description": "Rate-limits messages per address.",
                      "defaultValue": "true"
                    },
                    "MEILI_MASTER_KEY": {
                      "isOptional": false,
                      "description": "Shared secret between this service and Meilisearch — the same value has to appear on both, which the reference on the Meilisearch side takes care of.",
                      "defaultValue": "{{MEILI_MASTER_KEY}}"
                    },
                    "MESSAGE_USER_MAX": {
                      "isOptional": false,
                      "description": "Messages per account, when the limit above is on.",
                      "defaultValue": "40"
                    },
                    "ALLOW_EMAIL_LOGIN": {
                      "isOptional": false,
                      "description": "Email and password sign-in.",
                      "defaultValue": "true"
                    },
                    "ANTHROPIC_API_KEY": {
                      "isOptional": false,
                      "description": "Claude models. Replace with a real key to provide them for everyone.",
                      "defaultValue": "user_provided"
                    },
                    "FIREWORKS_API_KEY": {
                      "isOptional": false,
                      "description": "Fireworks AI, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "HUGGINGFACE_TOKEN": {
                      "isOptional": false,
                      "description": "Hugging Face, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "MESSAGE_IP_WINDOW": {
                      "isOptional": false,
                      "description": "That window, in minutes.",
                      "defaultValue": "1"
                    },
                    "OPENAI_MODERATION": {
                      "isOptional": false,
                      "description": "Runs messages through OpenAI's moderation endpoint first. Needs OPENAI_MODERATION_API_KEY.",
                      "defaultValue": "false"
                    },
                    "SAMBANOVA_API_KEY": {
                      "isOptional": false,
                      "description": "SambaNova, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "ALLOW_REGISTRATION": {
                      "isOptional": false,
                      "description": "Open sign-up. The first account created becomes the administrator — so leave this on, register, then set it to false. The domain is public and the API keys are yours.",
                      "defaultValue": "true"
                    },
                    "ALLOW_SOCIAL_LOGIN": {
                      "isOptional": false,
                      "description": "OAuth sign-in. Needs the client id and secret of a provider (Google, GitHub, Discord, OpenID) added as variables.",
                      "defaultValue": "false"
                    },
                    "ASSISTANTS_API_KEY": {
                      "isOptional": false,
                      "description": "OpenAI Assistants API. Replace with a real key to provide it for everyone.",
                      "defaultValue": "user_provided"
                    },
                    "HYPERBOLIC_API_KEY": {
                      "isOptional": false,
                      "description": "Hyperbolic, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "JWT_REFRESH_SECRET": {
                      "isOptional": false,
                      "description": "Signs refresh tokens. Changing it logs everyone out.",
                      "defaultValue": "{{JWT_REFRESH_SECRET}}"
                    },
                    "LIMIT_MESSAGE_USER": {
                      "isOptional": false,
                      "description": "Rate-limits messages per account as well. Off, because the per-address limit already covers the usual case.",
                      "defaultValue": "false"
                    },
                    "MEILI_NO_ANALYTICS": {
                      "isOptional": false,
                      "description": "Stops Meilisearch from sending usage data home.",
                      "defaultValue": "true"
                    },
                    "PERPLEXITY_API_KEY": {
                      "isOptional": false,
                      "description": "Perplexity, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "TOGETHERAI_API_KEY": {
                      "isOptional": false,
                      "description": "Together.ai, one of the providers defined in the config file. `user_provided` asks each person for their own key; a real key here serves everyone.",
                      "defaultValue": "user_provided"
                    },
                    "MESSAGE_USER_WINDOW": {
                      "isOptional": false,
                      "description": "That window, in minutes.",
                      "defaultValue": "1"
                    },
                    "ALLOW_PASSWORD_RESET": {
                      "isOptional": false,
                      "description": "Password reset by email. Off because it needs an SMTP server configured; without one the reset link is never sent.",
                      "defaultValue": "false"
                    },
                    "REFRESH_TOKEN_EXPIRY": {
                      "isOptional": false,
                      "description": "Refresh token lifetime in milliseconds (7 days) — how long a browser stays signed in.",
                      "defaultValue": "604800000"
                    },
                    "LOGIN_VIOLATION_SCORE": {
                      "isOptional": false,
                      "description": "Score added per failed login.",
                      "defaultValue": "1"
                    },
                    "CONCURRENT_MESSAGE_MAX": {
                      "isOptional": false,
                      "description": "That cap.",
                      "defaultValue": "2"
                    },
                    "MESSAGE_VIOLATION_SCORE": {
                      "isOptional": false,
                      "description": "Score added for exceeding a message rate limit.",
                      "defaultValue": "1"
                    },
                    "ALLOW_SOCIAL_REGISTRATION": {
                      "isOptional": false,
                      "description": "Lets an OAuth sign-in create an account that does not exist yet.",
                      "defaultValue": "false"
                    },
                    "LIMIT_CONCURRENT_MESSAGES": {
                      "isOptional": false,
                      "description": "Caps how many answers one user can have in flight.",
                      "defaultValue": "true"
                    },
                    "CONCURRENT_VIOLATION_SCORE": {
                      "isOptional": false,
                      "description": "Score added for exceeding the concurrent-message limit.",
                      "defaultValue": "1"
                    },
                    "CONSOLE_JSON_STRING_LENGTH": {
                      "isOptional": false,
                      "description": "Where the JSON transport cuts a long log string. Raise it if a truncated line hides something you need.",
                      "defaultValue": "500"
                    },
                    "NON_BROWSER_VIOLATION_SCORE": {
                      "isOptional": false,
                      "description": "Score added for a request without a browser user-agent — on its own enough to trigger a ban. Set to 0 if you intend to drive the REST API from scripts.",
                      "defaultValue": "20"
                    },
                    "REGISTRATION_VIOLATION_SCORE": {
                      "isOptional": false,
                      "description": "Score added per rejected registration.",
                      "defaultValue": "1"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:3080": {
                        "port": 3080
                      }
                    }
                  },
                  "volumeMounts": {
                    "93e38250-fc85-4858-8ce0-f88ff0218b71": {
                      "mountPath": "/data"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "LibreChat",
      "method": "GET",
      "path": "/health",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 5,
    "needs_volume": true
  },
  "generated_at": "2026-09-19T22:14:40.433Z",
  "generator_version": "0.1.0",
  "status": "validated",
  "validated_at": "2026-09-19T19:30:12.550Z",
  "success_rate_30d": 1,
  "validation": {
    "last_run_id": "run_048a6f6538344cdebfd8",
    "checks": [
      {
        "name": "workflow_completed",
        "passed": true
      },
      {
        "name": "all_services_deployed",
        "passed": true
      },
      {
        "name": "healthcheck",
        "passed": true
      },
      {
        "name": "stays_up",
        "passed": true
      }
    ],
    "typical_ready_seconds": 113,
    "typical_build_seconds": 0,
    "typical_start_seconds": 40,
    "slowest_service": "LibreChat"
  }
}
