{
  "manifest_version": "1.0.0",
  "template": {
    "id": "75f8d9a6-f5b7-40d7-bc49-783a32634ff2",
    "slug": "kaneo-v2-self-hosted-project-management-",
    "name": "Kaneo-v2: Project management - Jira, Linear alternative",
    "description": "Open source Kanban with time tracking. File uploads work out of the box.",
    "url": "https://railway.com/deploy/kaneo-v2-self-hosted-project-management-",
    "upstream": {
      "image": "chrislusf/seaweedfs:4.46"
    }
  },
  "services": [
    {
      "name": "Postgres",
      "source": {
        "image": "ghcr.io/railwayapp-templates/postgres-ssl:18"
      },
      "needs_volume": true,
      "volume_mount_path": "/var/lib/postgresql/data",
      "http": false
    },
    {
      "name": "SeaweedFS",
      "source": {
        "image": "chrislusf/seaweedfs:4.46"
      },
      "needs_volume": true,
      "volume_mount_path": "/data",
      "http": true
    },
    {
      "name": "Kaneo",
      "source": {
        "image": "ghcr.io/usekaneo/kaneo:latest"
      },
      "needs_volume": false,
      "http": true
    }
  ],
  "required_inputs": [
    {
      "key": "PGDATA",
      "service": "Postgres",
      "description": "[Do not change] Data directory, a subdirectory of the volume because Railway volumes contain a lost+found entry that initdb refuses.",
      "secret": false,
      "strategy": "default",
      "default": "/var/lib/postgresql/data/pgdata"
    },
    {
      "key": "PGHOST",
      "service": "Postgres",
      "description": "Private network hostname of this database.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "PGPORT",
      "service": "Postgres",
      "description": "[Do not change] Port PostgreSQL listens on inside the private network; DATABASE_URL hardcodes it.",
      "secret": false,
      "strategy": "default",
      "default": "5432"
    },
    {
      "key": "PGUSER",
      "service": "Postgres",
      "description": "Mirrors POSTGRES_USER, for psql and other tools that read the PG* variables.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PGDATABASE",
      "service": "Postgres",
      "description": "Mirrors POSTGRES_DB.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "PGPASSWORD",
      "service": "Postgres",
      "description": "Mirrors POSTGRES_PASSWORD, for psql and other PG* tools.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "POSTGRES_DB",
      "service": "Postgres",
      "description": "Name of the database created on first boot. Kaneo creates its own tables inside it.",
      "secret": false,
      "strategy": "default",
      "default": "railway"
    },
    {
      "key": "DATABASE_URL",
      "service": "Postgres",
      "description": "[Do not change] Private network connection string. Kaneo reads this through a reference.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_private_domain"
    },
    {
      "key": "POSTGRES_USER",
      "service": "Postgres",
      "description": "Database superuser, created on first boot.",
      "secret": false,
      "strategy": "default",
      "default": "postgres"
    },
    {
      "key": "SSL_CERT_DAYS",
      "service": "Postgres",
      "description": "Validity in days of the self-signed TLS certificate this image generates on first boot.",
      "secret": false,
      "strategy": "default",
      "default": "820"
    },
    {
      "key": "POSTGRES_PASSWORD",
      "service": "Postgres",
      "description": "Database password, generated uniquely for your deployment. Letters only, so it is safe inside a connection URL.",
      "secret": true,
      "strategy": "generate",
      "generate": "strong_password"
    },
    {
      "key": "RAILWAY_DEPLOYMENT_DRAINING_SECONDS",
      "service": "Postgres",
      "description": "Grace period PostgreSQL gets to finish in-flight queries and shut down cleanly on a redeploy.",
      "secret": false,
      "strategy": "default",
      "default": "60"
    },
    {
      "key": "PORT",
      "service": "SeaweedFS",
      "description": "[Do not change] Port the S3 API listens on. Railway's router uses it.",
      "secret": false,
      "strategy": "default",
      "default": "8333"
    },
    {
      "key": "S3_BUCKET",
      "service": "SeaweedFS",
      "description": "Bucket created automatically on first start. Must match S3_BUCKET on the Kaneo service.",
      "secret": false,
      "strategy": "default",
      "default": "kaneo"
    },
    {
      "key": "RAILWAY_RUN_UID",
      "service": "SeaweedFS",
      "description": "[Do not change] Runs the container as root so it can write the attached volume.",
      "secret": false,
      "strategy": "default",
      "default": "0"
    },
    {
      "key": "S3_ACCESS_KEY_ID",
      "service": "SeaweedFS",
      "description": "[Do not change] S3 access key, generated per deployment. Kaneo reads it by reference.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "S3_IDENTITIES_JSON",
      "service": "SeaweedFS",
      "description": "[Do not change] Identity file the start command writes for SeaweedFS, built from the two keys above. Unset or broken, SeaweedFS serves S3 anonymously.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "S3_SECRET_ACCESS_KEY",
      "service": "SeaweedFS",
      "description": "[Do not change] S3 secret key, generated per deployment. Kaneo reads it by reference.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "PORT",
      "service": "Kaneo",
      "description": "[Do not change] Port Kaneo's bundled nginx listens on. Railway's router and healthcheck use it.",
      "secret": false,
      "strategy": "default",
      "default": "5173"
    },
    {
      "key": "S3_BUCKET",
      "service": "Kaneo",
      "description": "Bucket where uploads are stored. Must match S3_BUCKET on the SeaweedFS service.",
      "secret": false,
      "strategy": "default",
      "default": "kaneo"
    },
    {
      "key": "S3_REGION",
      "service": "Kaneo",
      "description": "Region label used to sign S3 requests. Any valid value works for SeaweedFS; set the real bucket region if you switch to AWS S3.",
      "secret": false,
      "strategy": "default",
      "default": "us-east-1"
    },
    {
      "key": "AUTH_SECRET",
      "service": "Kaneo",
      "description": "Signs user sessions. Generated once when you deploy. Changing it later signs everyone out.",
      "secret": true,
      "strategy": "generate",
      "generate": "random_base64_32"
    },
    {
      "key": "S3_ENDPOINT",
      "service": "Kaneo",
      "description": "URL of the bundled SeaweedFS service where uploaded files are stored. Point at your own S3-compatible bucket to replace it.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "DATABASE_URL",
      "service": "Kaneo",
      "description": "[Do not change] PostgreSQL connection string, wired to the Postgres service in this template.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "KANEO_CLIENT_URL",
      "service": "Kaneo",
      "description": "Public URL of this Kaneo instance.",
      "secret": false,
      "strategy": "railway_provided",
      "railway_source": "railway_domain"
    },
    {
      "key": "S3_ACCESS_KEY_ID",
      "service": "Kaneo",
      "description": "[Do not change] Access key for the storage service, read by reference.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    },
    {
      "key": "S3_FORCE_PATH_STYLE",
      "service": "Kaneo",
      "description": "SeaweedFS uses path-style URLs. Set false only for virtual-hosted providers such as AWS S3 or R2.",
      "secret": false,
      "strategy": "default",
      "default": "true"
    },
    {
      "key": "DISABLE_REGISTRATION",
      "service": "Kaneo",
      "description": "Set to true once your team has signed up (this closes public signup, existing accounts keep working).",
      "secret": false,
      "strategy": "default",
      "default": "false"
    },
    {
      "key": "S3_SECRET_ACCESS_KEY",
      "service": "Kaneo",
      "description": "[Do not change] Secret key for the storage service, read by reference.",
      "secret": true,
      "strategy": "railway_provided",
      "railway_source": "reference_variable"
    }
  ],
  "deploy": {
    "mcp": {
      "server": "railway",
      "tool": "deploy_template",
      "args": {
        "template_code": "kaneo-v2-self-hosted-project-management-"
      }
    },
    "cli": "railway deploy --template kaneo-v2-self-hosted-project-management-",
    "api": {
      "method": "POST",
      "path": "/graphql/v2",
      "body": {
        "query": "mutation templateDeploy($input: TemplateDeployV2Input!) { templateDeployV2(input: $input) { projectId workflowId } }",
        "variables": {
          "input": {
            "templateId": "75f8d9a6-f5b7-40d7-bc49-783a32634ff2",
            "serializedConfig": {
              "buckets": {},
              "services": {
                "36f3a1df-3717-4279-b5d2-66cdce6879e6": {
                  "icon": "https://devicons.railway.app/i/postgresql.svg",
                  "name": "Postgres",
                  "deploy": {
                    "startCommand": null,
                    "healthcheckPath": null,
                    "restartPolicyType": "ALWAYS",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/railwayapp-templates/postgres-ssl:18"
                  },
                  "variables": {
                    "PGDATA": {
                      "isOptional": false,
                      "description": "[Do not change] Data directory, a subdirectory of the volume because Railway volumes contain a lost+found entry that initdb refuses.",
                      "defaultValue": "/var/lib/postgresql/data/pgdata"
                    },
                    "PGHOST": {
                      "isOptional": false,
                      "description": "Private network hostname of this database.",
                      "defaultValue": "${{RAILWAY_PRIVATE_DOMAIN}}"
                    },
                    "PGPORT": {
                      "isOptional": false,
                      "description": "[Do not change] Port PostgreSQL listens on inside the private network; DATABASE_URL hardcodes it.",
                      "defaultValue": "5432"
                    },
                    "PGUSER": {
                      "isOptional": false,
                      "description": "Mirrors POSTGRES_USER, for psql and other tools that read the PG* variables.",
                      "defaultValue": "${{POSTGRES_USER}}"
                    },
                    "PGDATABASE": {
                      "isOptional": false,
                      "description": "Mirrors POSTGRES_DB.",
                      "defaultValue": "${{POSTGRES_DB}}"
                    },
                    "PGPASSWORD": {
                      "isOptional": false,
                      "description": "Mirrors POSTGRES_PASSWORD, for psql and other PG* tools.",
                      "defaultValue": "${{POSTGRES_PASSWORD}}"
                    },
                    "POSTGRES_DB": {
                      "isOptional": false,
                      "description": "Name of the database created on first boot. Kaneo creates its own tables inside it.",
                      "defaultValue": "railway"
                    },
                    "DATABASE_URL": {
                      "isOptional": false,
                      "description": "[Do not change] Private network connection string. Kaneo reads this through a reference.",
                      "defaultValue": "postgresql://${{PGUSER}}:${{POSTGRES_PASSWORD}}@${{RAILWAY_PRIVATE_DOMAIN}}:5432/${{PGDATABASE}}"
                    },
                    "POSTGRES_USER": {
                      "isOptional": false,
                      "description": "Database superuser, created on first boot.",
                      "defaultValue": "postgres"
                    },
                    "SSL_CERT_DAYS": {
                      "isOptional": false,
                      "description": "Validity in days of the self-signed TLS certificate this image generates on first boot.",
                      "defaultValue": "820"
                    },
                    "POSTGRES_PASSWORD": {
                      "isOptional": false,
                      "description": "Database password, generated uniquely for your deployment. Letters only, so it is safe inside a connection URL.",
                      "defaultValue": "{{POSTGRES_PASSWORD}}"
                    },
                    "RAILWAY_DEPLOYMENT_DRAINING_SECONDS": {
                      "isOptional": false,
                      "description": "Grace period PostgreSQL gets to finish in-flight queries and shut down cleanly on a redeploy.",
                      "defaultValue": "60"
                    }
                  },
                  "volumeMounts": {
                    "36f3a1df-3717-4279-b5d2-66cdce6879e6": {
                      "mountPath": "/var/lib/postgresql/data"
                    }
                  },
                  "haTemplateCode": "postgres-ha",
                  "haConversionConfig": {
                    "edge": {
                      "label": "Reverse Proxy",
                      "options": [
                        2,
                        3,
                        4,
                        5
                      ],
                      "nodeLabel": "HAProxy",
                      "description": "Routes traffic to the primary",
                      "defaultValue": 3
                    },
                    "replica": {
                      "label": "Replicas",
                      "options": [
                        2,
                        3,
                        4,
                        5,
                        6,
                        7
                      ],
                      "nodeLabel": "Postgres",
                      "description": "Streaming replicas for automatic failover",
                      "defaultValue": 2
                    },
                    "internal": {
                      "label": "Coordinator Nodes",
                      "options": [
                        3,
                        5,
                        7,
                        9
                      ],
                      "nodeLabel": "etcd",
                      "defaultValue": 3
                    },
                    "description": "This will convert your Postgres database to an HA cluster with Patroni failover and etcd consensus."
                  }
                },
                "4182005b-53b4-438e-8bcc-da81077d70bc": {
                  "icon": "https://github.com/seaweedfs.png",
                  "name": "SeaweedFS",
                  "deploy": {
                    "startCommand": "sh -c 'printf \"%s\" \"$S3_IDENTITIES_JSON\" > /data/s3-identities.json && exec /usr/bin/weed -logtostderr=true mini -dir=/data -s3.config=/data/s3-identities.json -bucket=$S3_BUCKET -s3.port=$PORT'",
                    "healthcheckPath": null,
                    "restartPolicyType": "ALWAYS",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "chrislusf/seaweedfs:4.46"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "[Do not change] Port the S3 API listens on. Railway's router uses it.",
                      "defaultValue": "8333"
                    },
                    "S3_BUCKET": {
                      "isOptional": false,
                      "description": "Bucket created automatically on first start. Must match S3_BUCKET on the Kaneo service.",
                      "defaultValue": "kaneo"
                    },
                    "RAILWAY_RUN_UID": {
                      "isOptional": false,
                      "description": "[Do not change] Runs the container as root so it can write the attached volume.",
                      "defaultValue": "0"
                    },
                    "S3_ACCESS_KEY_ID": {
                      "isOptional": false,
                      "description": "[Do not change] S3 access key, generated per deployment. Kaneo reads it by reference.",
                      "defaultValue": "{{S3_ACCESS_KEY_ID}}"
                    },
                    "S3_IDENTITIES_JSON": {
                      "isOptional": false,
                      "description": "[Do not change] Identity file the start command writes for SeaweedFS, built from the two keys above. Unset or broken, SeaweedFS serves S3 anonymously.",
                      "defaultValue": "{\"identities\":[{\"name\":\"kaneo\",\"credentials\":[{\"accessKey\":\"${{S3_ACCESS_KEY_ID}}\",\"secretKey\":\"${{S3_SECRET_ACCESS_KEY}}\"}],\"actions\":[\"Admin\",\"Read\",\"List\",\"Tagging\",\"Write\"]}]}"
                    },
                    "S3_SECRET_ACCESS_KEY": {
                      "isOptional": false,
                      "description": "[Do not change] S3 secret key, generated per deployment. Kaneo reads it by reference.",
                      "defaultValue": "{{S3_SECRET_ACCESS_KEY}}"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:8333": {
                        "port": 8333
                      }
                    }
                  },
                  "volumeMounts": {
                    "4182005b-53b4-438e-8bcc-da81077d70bc": {
                      "mountPath": "/data"
                    }
                  }
                },
                "97cd698a-f4cb-44c1-a410-d38e02bb7212": {
                  "icon": "https://github.com/usekaneo.png",
                  "name": "Kaneo",
                  "deploy": {
                    "startCommand": null,
                    "healthcheckPath": "/api/health",
                    "restartPolicyType": "ALWAYS",
                    "restartPolicyMaxRetries": 10
                  },
                  "source": {
                    "image": "ghcr.io/usekaneo/kaneo:latest"
                  },
                  "variables": {
                    "PORT": {
                      "isOptional": false,
                      "description": "[Do not change] Port Kaneo's bundled nginx listens on. Railway's router and healthcheck use it.",
                      "defaultValue": "5173"
                    },
                    "S3_BUCKET": {
                      "isOptional": false,
                      "description": "Bucket where uploads are stored. Must match S3_BUCKET on the SeaweedFS service.",
                      "defaultValue": "kaneo"
                    },
                    "S3_REGION": {
                      "isOptional": true,
                      "description": "Region label used to sign S3 requests. Any valid value works for SeaweedFS; set the real bucket region if you switch to AWS S3.",
                      "defaultValue": "us-east-1"
                    },
                    "AUTH_SECRET": {
                      "isOptional": false,
                      "description": "Signs user sessions. Generated once when you deploy. Changing it later signs everyone out.",
                      "defaultValue": "{{AUTH_SECRET}}"
                    },
                    "S3_ENDPOINT": {
                      "isOptional": false,
                      "description": "URL of the bundled SeaweedFS service where uploaded files are stored. Point at your own S3-compatible bucket to replace it.",
                      "defaultValue": "https://${{SeaweedFS.RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "DATABASE_URL": {
                      "isOptional": false,
                      "description": "[Do not change] PostgreSQL connection string, wired to the Postgres service in this template.",
                      "defaultValue": "${{Postgres.DATABASE_URL}}"
                    },
                    "KANEO_CLIENT_URL": {
                      "isOptional": false,
                      "description": "Public URL of this Kaneo instance.",
                      "defaultValue": "https://${{RAILWAY_PUBLIC_DOMAIN}}"
                    },
                    "S3_ACCESS_KEY_ID": {
                      "isOptional": false,
                      "description": "[Do not change] Access key for the storage service, read by reference.",
                      "defaultValue": "${{SeaweedFS.S3_ACCESS_KEY_ID}}"
                    },
                    "S3_FORCE_PATH_STYLE": {
                      "isOptional": true,
                      "description": "SeaweedFS uses path-style URLs. Set false only for virtual-hosted providers such as AWS S3 or R2.",
                      "defaultValue": "true"
                    },
                    "DISABLE_REGISTRATION": {
                      "isOptional": true,
                      "description": "Set to true once your team has signed up (this closes public signup, existing accounts keep working).",
                      "defaultValue": "false"
                    },
                    "S3_SECRET_ACCESS_KEY": {
                      "isOptional": false,
                      "description": "[Do not change] Secret key for the storage service, read by reference.",
                      "defaultValue": "${{SeaweedFS.S3_SECRET_ACCESS_KEY}}"
                    }
                  },
                  "networking": {
                    "serviceDomains": {
                      "<hasDomain>:5173": {
                        "port": 5173
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "post_deploy": {
    "healthcheck": {
      "service": "Kaneo",
      "method": "GET",
      "path": "/api/health",
      "expect_status": 200
    }
  },
  "resources": {
    "expected_services": 3,
    "needs_volume": true
  },
  "generated_at": "2026-09-21T22:14:44.200Z",
  "generator_version": "0.1.0",
  "status": "validated",
  "validated_at": "2026-09-19T20:42:16.905Z",
  "success_rate_30d": 1,
  "validation": {
    "last_run_id": "run_2781b0e893334b06af43",
    "checks": [
      {
        "name": "workflow_completed",
        "passed": true
      },
      {
        "name": "all_services_deployed",
        "passed": true
      },
      {
        "name": "healthcheck",
        "passed": true
      },
      {
        "name": "stays_up",
        "passed": true
      }
    ],
    "typical_ready_seconds": 87,
    "typical_build_seconds": 0,
    "typical_start_seconds": 20,
    "slowest_service": "Kaneo"
  }
}
