{
  "manifest_version": "1.0.0",
  "template": {
    "id": "4285f5bf-042e-4955-944f-ccf71593c945",
    "slug": "libredesk",
    "name": "libredesk",
    "description": "Modern, open source, self-hosted omnichannel customer support desk.",
    "url": "https://railway.com/deploy/libredesk",
    "upstream": {
      "image": "libredesk/libredesk:latest"
    }
  },
  "services": [
    {
      "name": "Postgres",
      "source": {
        "image": "postgres:17-alpine"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/postgresql/data/pgdata ",
      "http": false
    },
    {
      "name": "libredesk",
      "source": {
        "image": "libredesk/libredesk:latest"
      },
      "needs_volume": true,
      "volume_mount_path": "/libredesk/uploads",
      "http": true
    },
    {
      "name": "Redis",
      "source": {
        "image": "redis:8.2.1"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "tcp_ports": [
        6379
      ],
      "http": false
    }
  ],
  "required_inputs": [
    {
      "key": "PGDATA",
      "service": "Postgres",
      "description": "Provide a value for PGDATA.",
      "secret": false,
      "strategy": "default",
      "default": "/var/lib/postgresql/data/pgdata"
    },
    {
      "key": "POSTGRES_DB",
      "service": "Postgres",
      "description": "Provide a value for POSTGRES_DB.",
      "secret": false,
      "strategy": "default",
      "default": "libredesk"
    },
    {
      "key": "POSTGRES_USER",
      "service": "Postgres",
      "description": "Provide a value for POSTGRES_USER.",
      "secret": false,
      "strategy": "default",
      "default": "libredesk"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "Postgres",
      "description": "Provide a value for POSTGRES_PASSWORD.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "PORT",
      "service": "libredesk",
      "description": "Libredesk http server port",
      "secret": false,
      "strategy": "default",
      "default": "9000"
    },
    {
      "key": "LIBREDESK_APP__ENV",
      "service": "libredesk",
      "description": "Environment either prod / dev",
      "secret": false,
      "strategy": "default",
      "default": "prod"
    },
    {
      "key": "LIBREDESK_DB__HOST",
      "service": "libredesk",
      "description": "Hostname for database",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "LIBREDESK_DB__PORT",
      "service": "libredesk",
      "description": "Database port",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "LIBREDESK_DB__USER",
      "service": "libredesk",
      "description": "DB user",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LIBREDESK_REDIS__URL",
      "service": "libredesk",
      "description": "Redis connection url (DSN)",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LIBREDESK_DB__DATABASE",
      "service": "libredesk",
      "description": "Database name",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LIBREDESK_DB__PASSWORD",
      "service": "libredesk",
      "description": "Database password",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "LIBREDESK_DB__SSL_MODE",
      "service": "libredesk",
      "description": "SSL mode for database connection",
      "secret": false,
      "strategy": "default",
      "default": "disable"
    },
    {
      "key": "LIBREDESK_APP__LOG_LEVEL",
      "service": "libredesk",
      "description": "Log level: info, debug, warn, error, fatal",
      "secret": false,
      "strategy": "default",
      "default": "debug"
    },
    {
      "key": "LIBREDESK_UPLOAD__PROVIDER",
      "service": "libredesk",
      "description": "File upload provider to use, either `fs` or `s3`",
      "secret": false,
      "strategy": "default",
      "default": "fs"
    },
    {
      "key": "LIBREDESK_WEBHOOK__TIMEOUT",
      "service": "libredesk",
      "description": "HTTP timeout for webhook requests",
      "secret": false,
      "strategy": "default",
      "default": "15s"
    },
    {
      "key": "LIBREDESK_WEBHOOK__WORKERS",
      "service": "libredesk",
      "description": "Number of webhook delivery workers",
      "secret": false,
      "strategy": "default",
      "default": "5"
    },
    {
      "key": "LIBREDESK_APP__CHECK_UPDATES",
      "service": "libredesk",
      "description": "Whether to automatically check for application updates on start up, app updates are shown as a banner in the admin panel",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "LIBREDESK_UPLOAD__FS__EXPIRY",
      "service": "libredesk",
      "description": "Signed URL expiry duration (e.g., \"30m\", \"1h\"). Defaults to 1h if unset",
      "secret": false,
      "strategy": "default",
      "default": "1h"
    },
    {
      "key": "LIBREDESK_APP__ENCRYPTION_KEY",
      "service": "libredesk",
      "description": "Libredesk encryption key",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "LIBREDESK_WEBHOOK__QUEUE_SIZE",
      "service": "libredesk",
      "description": "Maximum number of webhook deliveries that can be queued",
      "secret": false,
      "strategy": "default",
      "default": "10000"
    },
    {
      "key": "LIBREDESK_APP__SERVER__ADDRESS",
      "service": "libredesk",
      "description": "Address on which the libredesk http server listens to",
      "secret": false,
      "strategy": "default",
      "default": "0.0.0.0:9000"
    },
    {
      "key": "LIBREDESK_SYSTEM_USER_PASSWORD",
      "service": "libredesk",
      "description": "Set a password with at least 10 characters. It must include an uppercase letter, a lowercase letter, a number, and a special character. For example: MyPassword@123. Save this password somewhere safe before deploying. Once your deployment is live, open your deployment URL and log in using the email `System` and the password you set here.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "LIBREDESK_UPLOAD__FS__UPLOAD_PATH",
      "service": "libredesk",
      "description": "Directory where uploaded files are stored, make sure this directory exists and is writable by the application",
      "secret": false,
      "strategy": "default",
      "default": "uploads"
    },
    {
      "key": "LIBREDESK_AUTOMATION__WORKER_COUNT",
      "service": "libredesk",
      "description": "Number of workers processing automation rules",
      "secret": false,
      "strategy": "default",
      "default": "10"
    },
    {
      "key": "LIBREDESK_NOTIFICATION__QUEUE_SIZE",
      "service": "libredesk",
      "description": "Maximum number of notifications that can be queued",
      "secret": false,
      "strategy": "default",
      "default": "2000"
    },
    {
      "key": "LIBREDESK_SLA__EVALUATION_INTERVAL",
      "service": "libredesk",
      "description": "How often to evaluate SLA compliance for conversations",
      "secret": false,
      "strategy": "default",
      "default": "5m"
    },
    {
      "key": "LIBREDESK_APP__SERVER__READ_TIMEOUT",
      "service": "libredesk",
      "description": "Request read timeout",
      "secret": false,
      "strategy": "default",
      "default": "5s"
    },
    {
      "key": "LIBREDESK_NOTIFICATION__CONCURRENCY",
      "service": "libredesk",
      "description": "Number of concurrent notification workers",
      "secret": false,
      "strategy": "default",
      "default": "2"
    },
    {
      "key": "LIBREDESK_APP__SERVER__MAX_BODY_SIZE",
      "service": "libredesk",
      "description": "Maximum request body size in bytes (100MB). If you are using proxy, you may need to configure them to allow larger request bodies",
      "secret": false,
      "strategy": "default",
      "default": "104857600"
    },
    {
      "key": "LIBREDESK_APP__SERVER__WRITE_TIMEOUT",
      "service": "libredesk",
      "description": "Request write timeout",
      "secret": false,
      "strategy": "default",
      "default": "5s"
    },
    {
      "key": "LIBREDESK_MESSAGE__INCOMING_QUEUE_SIZE",
      "service": "libredesk",
      "description": "Maximum number of messages that can be queued for incoming processing",
      "secret": false,
      "strategy": "default",
      "default": "5000"
    },
    {
      "key": "LIBREDESK_MESSAGE__OUTGOING_QUEUE_SIZE",
      "service": "libredesk",
      "description": "Maximum number of messages that can be queued for outgoing processing",
      "secret": false,
      "strategy": "default",
      "default": "5000"
    },
    {
      "key": "LIBREDESK_APP__SERVER__READ_BUFFER_SIZE",
      "service": "libredesk",
      "description": "Size of the read buffer for incoming requests (also limits max header size)",
      "secret": false,
      "strategy": "default",
      "default": "65536"
    },
    {
      "key": "LIBREDESK_APP__SERVER__SESSION_LIFETIME",
      "service": "libredesk",
      "description": "Session lifetime duration",
      "secret": false,
      "strategy": "default",
      "default": "9h"
    },
    {
      "key": "LIBREDESK_APP__SERVER__KEEPALIVE_TIMEOUT",
      "service": "libredesk",
      "description": "Keepalive timeout",
      "secret": false,
      "strategy": "default",
      "default": "10s"
    },
    {
      "key": "LIBREDESK_CONVERSATION__UNSNOOZE_INTERVAL",
      "service": "libredesk",
      "description": "How often to check for conversations to unsnooze",
      "secret": false,
      "strategy": "default",
      "default": "5m"
    },
    {
      "key": "LIBREDESK_MESSAGE__INCOMING_QUEUE_WORKERS",
      "service": "libredesk",
      "description": "Number of workers processing incoming message queue",
      "secret": false,
      "strategy": "default",
      "default": "10"
    },
    {
      "key": "LIBREDESK_MESSAGE__OUTGOING_QUEUE_WORKERS",
      "service": "libredesk",
      "description": "Number of workers processing outgoing message queue",
      "secret": false,
      "strategy": "default",
      "default": "10"
    },
    {
      "key": "LIBREDESK_AUTOASSIGNER__AUTOASSIGN_INTERVAL",
      "service": "libredesk",
      "description": "How often to run automatic conversation assignment",
      "secret": false,
      "strategy": "default",
      "default": "5m"
    },
    {
      "key": "LIBREDESK_CONVERSATION__CONTINUITY_SCAN_INTERVAL",
      "service": "libredesk",
      "description": "How often to check for offline conversations in database to send continuity emails",
      "secret": false,
      "strategy": "default",
      "default": "5m"
    },
    {
      "key": "LIBREDESK_CONVERSATION__DRAFT_RETENTION_DURATION",
      "service": "libredesk",
      "description": "How long to keep drafts before deleting them from the database (e.g. \"360h\", \"48h\")",
      "secret": false,
      "strategy": "default",
      "default": "360h"
    },
    {
      "key": "LIBREDESK_MESSAGE__MESSAGE_OUTGOING_SCAN_INTERVAL",
      "service": "libredesk",
      "description": "How often to scan for outgoing messages to process, keep it low to process messages quickly",
      "secret": false,
      "strategy": "default",
      "default": "100ms"
    },
    {
      "key": "REDISHOST",
      "service": "Redis",
      "description": "Provide a value for REDISHOST.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "REDISPORT",
      "service": "Redis",
      "description": "Provide a value for REDISPORT.",
      "secret": false,
      "strategy": "default",
      "default": "6379"
    },
    {
      "key": "REDISUSER",
      "service": "Redis",
      "description": "Provide a value for REDISUSER.",
      "secret": false,
      "strategy": "default",
      "default": "default"
    },
    {
      "key": "REDIS_URL",
      "service": "Redis",
      "description": "Connection string for connecting to redis using the private network",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDISPASSWORD",
      "service": "Redis",
      "description": "Provide a value for REDISPASSWORD.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "REDIS_PASSWORD",
      "service": "Redis",
      "description": "Provide a value for REDIS_PASSWORD.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "REDIS_PUBLIC_URL",
      "service": "Redis",
      "description": "Connection string for connecting to redis externally",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "libredesk"
      }
    },
    "cli": "railway deploy --template libredesk",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "4285f5bf-042e-4955-944f-ccf71593c945",
            "serializedConfig": {
              "services": {
                "3bfc7548-83c8-46e2-a019-5a7b3d59c607": {
                  "icon": "https://devicons.railway.com/i/postgresql.svg",
                  "name": "Postgres",
                  "source": {
                    "image": "postgres:17-alpine"
                  },
                  "variables": {
                    "PGDATA": {
                      "defaultValue": "/var/lib/postgresql/data/pgdata"
                    },
                    "POSTGRES_DB": {
                      "defaultValue": "libredesk"
                    },
                    "POSTGRES_USER": {
                      "defaultValue": "libredesk"
                    },
                    "POSTGRES_PASSWORD": {
                      "defaultValue": "{{POSTGRES_PASSWORD}}"
                    }
                  },
                  "volumeMounts": {
                    "3bfc7548-83c8-46e2-a019-5a7b3d59c607": {
                      "mountPath": "/var/lib/postgresql/data/pgdata "
                    }
                  }
                },
                "675ca57b-43b4-4afa-b577-a623da5d08d8": {
                  "icon": "https://libredesk.io/favicon.ico",
                  "name": "libredesk",
                  "deploy": {
                    "startCommand": "/bin/sh -c \"(./libredesk --install --idempotent-install --yes || true) && (./libredesk --upgrade --yes || true) && exec ./libredesk\"",
                    "healthcheckPath": "/health",
                    "restartPolicyType": "ALWAYS"
                  },
                  "source": {
                    "image": "libredesk/libredesk:latest",
                    "autoUpdates": {
                      "type": "disabled"
                    }
                  },
                  "variables": {
                    "PORT": {
                      "description": "Libredesk http server port",
                      "defaultValue": "9000"
                    },
                    "LIBREDESK_APP__ENV": {
                      "description": "Environment either prod / dev",
                      "defaultValue": "prod"
                    },
                    "LIBREDESK_DB__HOST": {
                      "description": "Hostname for database",
                      "defaultValue": "${{Postgres.RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "LIBREDESK_DB__PORT": {
                      "description": "Database port",
                      "defaultValue": "5432"
                    },
                    "LIBREDESK_DB__USER": {
                      "description": "DB user",
                      "defaultValue": "${{Postgres.POSTGRES_USER}}"
                    },
                    "LIBREDESK_REDIS__URL": {
                      "description": "Redis connection url (DSN)",
                      "defaultValue": "${{Redis.REDIS_URL}}"
                    },
                    "LIBREDESK_DB__DATABASE": {
                      "description": "Database name",
                      "defaultValue": "${{Postgres.POSTGRES_DB}}"
                    },
                    "LIBREDESK_DB__PASSWORD": {
                      "description": "Database password",
                      "defaultValue": "${{Postgres.POSTGRES_PASSWORD}}"
                    },
                    "LIBREDESK_DB__SSL_MODE": {
                      "description": "SSL mode for database connection",
                      "defaultValue": "disable"
                    },
                    "LIBREDESK_APP__LOG_LEVEL": {
                      "description": "Log level: info, debug, warn, error, fatal",
                      "defaultValue": "debug"
                    },
                    "LIBREDESK_UPLOAD__PROVIDER": {
                      "description": "File upload provider to use, either `fs` or `s3`",
                      "defaultValue": "fs"
                    },
                    "LIBREDESK_WEBHOOK__TIMEOUT": {
                      "description": "HTTP timeout for webhook requests",
                      "defaultValue": "15s"
                    },
                    "LIBREDESK_WEBHOOK__WORKERS": {
                      "description": "Number of webhook delivery workers",
                      "defaultValue": "5"
                    },
                    "LIBREDESK_APP__CHECK_UPDATES": {
                      "description": "Whether to automatically check for application updates on start up, app updates are shown as a banner in the admin panel",
                      "defaultValue": "true"
                    },
                    "LIBREDESK_UPLOAD__FS__EXPIRY": {
                      "description": "Signed URL expiry duration (e.g., \"30m\", \"1h\"). Defaults to 1h if unset",
                      "defaultValue": "1h"
                    },
                    "LIBREDESK_APP__ENCRYPTION_KEY": {
                      "description": "Libredesk encryption key",
                      "defaultValue": "{{LIBREDESK_APP__ENCRYPTION_KEY}}"
                    },
                    "LIBREDESK_WEBHOOK__QUEUE_SIZE": {
                      "description": "Maximum number of webhook deliveries that can be queued",
                      "defaultValue": "10000"
                    },
                    "LIBREDESK_APP__SERVER__ADDRESS": {
                      "description": "Address on which the libredesk http server listens to",
                      "defaultValue": "0.0.0.0:9000"
                    },
                    "LIBREDESK_SYSTEM_USER_PASSWORD": {
                      "description": "Set a password with at least 10 characters. It must include an uppercase letter, a lowercase letter, a number, and a special character. For example: MyPassword@123. Save this password somewhere safe before deploying. Once your deployment is live, open your deployment URL and log in using the email `System` and the password you set here.",
                      "defaultValue": "{{LIBREDESK_SYSTEM_USER_PASSWORD}}"
                    },
                    "LIBREDESK_UPLOAD__FS__UPLOAD_PATH": {
                      "description": "Directory where uploaded files are stored, make sure this directory exists and is writable by the application",
                      "defaultValue": "uploads"
                    },
                    "LIBREDESK_AUTOMATION__WORKER_COUNT": {
                      "description": "Number of workers processing automation rules",
                      "defaultValue": "10"
                    },
                    "LIBREDESK_NOTIFICATION__QUEUE_SIZE": {
                      "description": "Maximum number of notifications that can be queued",
                      "defaultValue": "2000"
                    },
                    "LIBREDESK_SLA__EVALUATION_INTERVAL": {
                      "description": "How often to evaluate SLA compliance for conversations",
                      "defaultValue": "5m"
                    },
                    "LIBREDESK_APP__SERVER__READ_TIMEOUT": {
                      "description": "Request read timeout",
                      "defaultValue": "5s"
                    },
                    "LIBREDESK_NOTIFICATION__CONCURRENCY": {
                      "description": "Number of concurrent notification workers",
                      "defaultValue": "2"
                    },
                    "LIBREDESK_APP__SERVER__MAX_BODY_SIZE": {
                      "description": "Maximum request body size in bytes (100MB). If you are using proxy, you may need to configure them to allow larger request bodies",
                      "defaultValue": "104857600"
                    },
                    "LIBREDESK_APP__SERVER__WRITE_TIMEOUT": {
                      "description": "Request write timeout",
                      "defaultValue": "5s"
                    },
                    "LIBREDESK_MESSAGE__INCOMING_QUEUE_SIZE": {
                      "description": "Maximum number of messages that can be queued for incoming processing",
                      "defaultValue": "5000"
                    },
                    "LIBREDESK_MESSAGE__OUTGOING_QUEUE_SIZE": {
                      "description": "Maximum number of messages that can be queued for outgoing processing",
                      "defaultValue": "5000"
                    },
                    "LIBREDESK_APP__SERVER__READ_BUFFER_SIZE": {
                      "description": "Size of the read buffer for incoming requests (also limits max header size)",
                      "defaultValue": "65536"
                    },
                    "LIBREDESK_APP__SERVER__SESSION_LIFETIME": {
                      "description": "Session lifetime duration",
                      "defaultValue": "9h"
                    },
                    "LIBREDESK_APP__SERVER__KEEPALIVE_TIMEOUT": {
                      "description": "Keepalive timeout",
                      "defaultValue": "10s"
                    },
                    "LIBREDESK_CONVERSATION__UNSNOOZE_INTERVAL": {
                      "description": "How often to check for conversations to unsnooze",
                      "defaultValue": "5m"
                    },
                    "LIBREDESK_MESSAGE__INCOMING_QUEUE_WORKERS": {
                      "description": "Number of workers processing incoming message queue",
                      "defaultValue": "10"
                    },
                    "LIBREDESK_MESSAGE__OUTGOING_QUEUE_WORKERS": {
                      "description": "Number of workers processing outgoing message queue",
                      "defaultValue": "10"
                    },
                    "LIBREDESK_AUTOASSIGNER__AUTOASSIGN_INTERVAL": {
                      "description": "How often to run automatic conversation assignment",
                      "defaultValue": "5m"
                    },
                    "LIBREDESK_CONVERSATION__CONTINUITY_SCAN_INTERVAL": {
                      "description": "How often to check for offline conversations in database to send continuity emails",
                      "defaultValue": "5m"
                    },
                    "LIBREDESK_CONVERSATION__DRAFT_RETENTION_DURATION": {
                      "description": "How long to keep drafts before deleting them from the database (e.g. \"360h\", \"48h\")",
                      "defaultValue": "360h"
                    },
                    "LIBREDESK_MESSAGE__MESSAGE_OUTGOING_SCAN_INTERVAL": {
                      "description": "How often to scan for outgoing messages to process, keep it low to process messages quickly",
                      "defaultValue": "100ms"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:9000": {
                        "port": 9000
                      }
                    }
                  },
                  "volumeMounts": {
                    "675ca57b-43b4-4afa-b577-a623da5d08d8": {
                      "mountPath": "/libredesk/uploads"
                    }
                  }
                },
                "7099bc3b-e145-417e-885a-b080fbe7f073": {
                  "icon": "https://cdn.sanity.io/images/sy1jschh/production/0ce0bfdcfbdbf69662b1116671f97c2dd788b655-157x157.svg",
                  "name": "Redis",
                  "deploy": {
                    "startCommand": "/bin/sh -c \"rm -rf $RAILWAY_VOLUME_MOUNT_PATH/lost+found/ && exec docker-entrypoint.sh redis-server --requirepass $REDIS_PASSWORD --save 60 1 --dir $RAILWAY_VOLUME_MOUNT_PATH\""
                  },
                  "source": {
                    "image": "redis:8.2.1"
                  },
                  "variables": {
                    "REDISHOST": {
                      "isOptional": false,
                      "defaultValue": "${{RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "REDISPORT": {
                      "isOptional": false,
                      "defaultValue": "6379"
                    },
                    "REDISUSER": {
                      "isOptional": false,
                      "defaultValue": "default"
                    },
                    "REDIS_URL": {
                      "isOptional": false,
                      "description": "Connection string for connecting to redis using the private network",
                      "defaultValue": "redis://${{ REDISUSER }}:${{ REDIS_PASSWORD }}@${{ REDISHOST }}:${{ REDISPORT }}"
                    },
                    "REDISPASSWORD": {
                      "isOptional": false,
                      "defaultValue": "${{REDIS_PASSWORD}}"
                    },
                    "REDIS_PASSWORD": {
                      "isOptional": false,
                      "defaultValue": "{{REDIS_PASSWORD}}"
                    },
                    "REDIS_PUBLIC_URL": {
                      "description": "Connection string for connecting to redis externally",
                      "defaultValue": "redis://default:${{ REDIS_PASSWORD }}@${{ RAILWAY_TCP_PROXY_DOMAIN }}:${{ RAILWAY_TCP_PROXY_PORT }}"
                    }
                  },
                  "networking": {
                    "tcpProxies": {
                      "6379": {}
                    }
                  },
                  "volumeMounts": {
                    "7099bc3b-e145-417e-885a-b080fbe7f073": {
                      "mountPath": "/data"
                    }
                  },
                  "haTemplateCode": "redis-ha",
                  "haConversionConfig": {
                    "edge": {
                      "label": "Reverse Proxies",
                      "options": [
                        1,
                        2
                      ],
                      "nodeLabel": "HAProxy",
                      "description": "HAProxy instances routing writes to the primary",
                      "defaultValue": 2
                    },
                    "replica": {
                      "label": "Redis Replicas",
                      "options": [
                        2,
                        3,
                        4,
                        5
                      ],
                      "nodeLabel": "Redis",
                      "description": "Nodes running Redis + colocated Sentinel",
                      "defaultValue": 2
                    },
                    "internal": null,
                    "description": "This will convert your Redis instance to an HA cluster with Sentinel failover and HAProxy routing to the primary."
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "libredesk",
      "method": "GET",
      "path": "/health",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 3,
    "needs_volume": true
  },
  "generated_at": "2026-09-11T16:14:44.435Z",
  "generator_version": "0.1.0",
  "status": "degraded",
  "validated_at": "2026-09-11T13:27:11.358Z",
  "success_rate_30d": 0,
  "validation": {
    "last_run_id": "run_fb6f3e9219fa4585a6f1",
    "checks": [
      {
        "name": "workflow_completed",
        "passed": true
      },
      {
        "name": "all_services_deployed",
        "passed": false,
        "detail": "SUCCESS,FAILED,SUCCESS"
      }
    ]
  }
}
