{
  "$defs": {
    "AccessClass": {
      "description": "Access class configuration for ingress authentication",
      "properties": {
        "access_requirement": {
          "$ref": "#/$defs/AccessRequirement",
          "description": "Access requirement level"
        },
        "custom_annotations": {
          "additionalProperties": {
            "type": "string"
          },
          "default": {},
          "description": "Optional custom nginx annotations",
          "type": "object"
        },
        "description": {
          "description": "Description for UI",
          "type": "string"
        },
        "display_name": {
          "description": "Display name for UI (e.g., \"Public\")",
          "type": "string"
        },
        "ingress_class": {
          "description": "Ingress class to use",
          "type": "string"
        }
      },
      "required": [
        "display_name",
        "description",
        "ingress_class",
        "access_requirement"
      ],
      "type": "object"
    },
    "AccessRequirement": {
      "description": "Access requirement level for ingress authentication.\n\nThis is the internal/wire representation: the `access_requirement` of a\nserver-configured access class, the per-route requirement carried on a\ndeployment's route side-data, and the `access` query param the reconciler\nstamps into the auth-url. Spelled PascalCase to match the access-class\nsettings. The `.rise.toml` surface uses [`RouteAccess`] instead, whose\n`public` reads more naturally on a route than `None`.",
      "oneOf": [
        {
          "const": "None",
          "description": "No authentication required - fully public access",
          "type": "string"
        },
        {
          "const": "Authenticated",
          "description": "Must be authenticated, but no project membership required",
          "type": "string"
        },
        {
          "const": "Member",
          "description": "Must be authenticated AND have project membership (owner or team member)",
          "type": "string"
        }
      ]
    },
    "ActiveSyncSource": {
      "description": "Supported active sync sources for pulling users and groups",
      "oneOf": [
        {
          "const": "Entra",
          "description": "Microsoft Entra ID (Azure AD) - uses Microsoft Graph API to pull\nusers and groups assigned to the configured app registration.",
          "type": "string"
        }
      ]
    },
    "AuthSettings": {
      "properties": {
        "active_sync_interval_secs": {
          "default": 300,
          "description": "Interval in seconds for active sync polling (default: 300 = 5 minutes)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "active_sync_source": {
          "anyOf": [
            {
              "$ref": "#/$defs/ActiveSyncSource"
            },
            {
              "type": "null"
            }
          ],
          "description": "Optional active sync source for pulling users and groups from an external IdP.\nWhen configured, Rise will periodically query the IdP for users and groups\nassigned to the app and sync them as Rise teams."
        },
        "admin_users": {
          "default": [],
          "description": "List of admin user emails for the default organization. Admins do NOT\nimplicitly receive the Operator role — list the email in\n`operator_users` separately if needed.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "allow_list_all_teams": {
          "default": false,
          "description": "Allow all users to list all teams (default: false).\nWhen false, non-admin users only see teams they are members of.",
          "type": "boolean"
        },
        "allow_raw_external_tokens": {
          "default": true,
          "description": "Accept raw external OIDC tokens directly at request time (default: true).\n\nWhen `true`, a CI service account may present its external OIDC token\ndirectly to project-scoped endpoints, and Rise resolves the service\naccount per request (the legacy two-phase path). When `false`, callers\nmust first exchange their token at `POST /api/v1/auth/token` for a Rise\naccess token.\n\nLeaving this `true` keeps the legacy attack surface and forfeits the\nsecurity benefits of the exchange (no service-account DB lookups in the\nrequest hot path, and an authenticated `platform/capabilities`). While it\nis `true`, every *accepted* raw-token request emits one\n`rise::deprecation` `tracing` event carrying the validated `issuer`/`sub`,\nso operators can aggregate it in their log pipeline to see which workload\nidentities still need migrating. Defaults to `false` starting in 0.25.0;\nmigrate CI to pre-exchange before upgrading.",
          "type": "boolean"
        },
        "allow_team_creation": {
          "default": true,
          "description": "Allow regular users to create teams (default: true).\nWhen false, only admin users can create teams.",
          "type": "boolean"
        },
        "authorize_url": {
          "default": null,
          "description": "Optional custom authorize endpoint URL\nIf not set, will be discovered from issuer's .well-known/openid-configuration\nor default to {issuer}/authorize",
          "type": [
            "string",
            "null"
          ]
        },
        "client_id": {
          "type": "string"
        },
        "client_secret": {
          "type": "string"
        },
        "controllers": {
          "default": [],
          "description": "Trusted external controller identities. Each entry binds a stable\ncontroller ID to an OIDC issuer plus required claim constraints.\nUsed by generic-resource controller endpoints.",
          "items": {
            "$ref": "#/$defs/ControllerIdentity"
          },
          "type": "array"
        },
        "idp_group_sync_enabled": {
          "default": true,
          "description": "Enable IdP group synchronization (default: true)\nWhen enabled, user team memberships are automatically synced from IdP groups claim on login",
          "type": "boolean"
        },
        "issuer": {
          "type": "string"
        },
        "operator_users": {
          "default": [],
          "description": "List of Operator user emails. Operators have full access to generic\nresource storage and built-in resource management. This is a separate\nrole from `admin_users`. Matching is case-insensitive. Blank entries are\nfiltered out so an unset `${VAR}` default collapses to an empty list.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "platform_access": {
          "$ref": "#/$defs/PlatformAccessConfig",
          "description": "Platform access control configuration"
        },
        "scopes": {
          "default": [
            "openid",
            "email",
            "profile"
          ],
          "description": "OAuth2 scopes requested during login flows.\nDefaults to `[\"openid\", \"email\", \"profile\"]`. `offline_access` is not\nrequested by default (the CLI does not use refresh tokens, and the scope\nis rejected by some providers such as Google); add it here if needed.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "token_url": {
          "default": null,
          "description": "Optional custom token endpoint URL\nIf not set, will be discovered from issuer's .well-known/openid-configuration\nor default to {issuer}/token",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "issuer",
        "client_id",
        "client_secret"
      ],
      "type": "object"
    },
    "ControllerIdentity": {
      "description": "A trusted external controller identity configured under `auth.controllers`.\n\nEach entry binds a stable controller ID (the key used under\n`status.controllers`) to an OIDC issuer plus required claim constraints.\n`claims.aud` is mandatory. Wildcards follow [`validate_custom_claims`]\nglob rules (`*` matches any sequence of characters).",
      "properties": {
        "claims": {
          "additionalProperties": {
            "type": "string"
          },
          "default": {},
          "description": "Expected string-valued claim constraints. `aud` is required and may\nmatch either a string JWT `aud` or one member of an array JWT `aud`.\nPut `sub` here when a subject constraint is needed. Glob `*` supported.",
          "type": "object"
        },
        "id": {
          "description": "Stable controller ID written under `status.controllers`. Must be a\nDNS subdomain with an optional single `/name` suffix, e.g.\n`controller.example.com` or `controller.example.com/my-ctrl`.",
          "type": "string"
        },
        "issuer": {
          "description": "OIDC issuer URL. Used for JWKS discovery and `iss` validation.",
          "type": "string"
        }
      },
      "required": [
        "id",
        "issuer"
      ],
      "type": "object"
    },
    "CustomDomainTlsMode": {
      "description": "TLS mode for custom domains",
      "oneOf": [
        {
          "const": "shared",
          "description": "All hosts (primary + custom domains) share the same TLS secret",
          "type": "string"
        },
        {
          "const": "per-domain",
          "description": "Each custom domain gets its own tls-{domain} secret (cert-manager integration)",
          "type": "string"
        }
      ]
    },
    "DatabaseSettings": {
      "properties": {
        "url": {
          "default": "",
          "type": "string"
        }
      },
      "type": "object"
    },
    "DefaultOrganizationSettings": {
      "description": "Configuration for the bootstrapped default Organization resource.\n\nThe `name` and `display_name` fields populate the Organization's metadata\nand spec respectively. `kubernetes_namespace_prefix`, when set, is written\nto the `kubernetes.rise.dev/namespace-prefix` annotation. Unrelated\nannotations on an existing default Organization are preserved across\nbootstrap runs.",
      "properties": {
        "annotations": {
          "additionalProperties": {
            "type": "string"
          },
          "default": {},
          "description": "Extra annotations to apply to the Organization's metadata. The\n`kubernetes.rise.dev/namespace-prefix` annotation is managed\nseparately by `kubernetes_namespace_prefix` and overrides any entry\nwith the same key set here.",
          "type": "object"
        },
        "display_name": {
          "default": "Default",
          "description": "Human-readable display name stored in `spec.displayName`. Default:\n`Default`.",
          "type": "string"
        },
        "kubernetes_namespace_prefix": {
          "default": null,
          "description": "Value for the `kubernetes.rise.dev/namespace-prefix` annotation on the\ndefault Organization. When set, bootstrap stamps the annotation with\nthis value; when `None`, the annotation is left unset and the\nKubernetes controller falls back to `org-{discriminator}-` (see\n[`crate::server::bootstrap::DefaultOrganizationView::resolved_namespace_prefix`]).\n\nUpgrade note: installs that previously relied on the historic\n`rise-{project_name}` namespace naming must set this to `\"rise-\"`\nexplicitly before deploying — otherwise the controller will switch\nto `org-{discriminator}-{project_name}` and orphan the legacy\nnamespaces.",
          "type": [
            "string",
            "null"
          ]
        },
        "name": {
          "default": "default",
          "description": "Resource name of the default Organization (DNS-label form). Default:\n`default`.",
          "type": "string"
        }
      },
      "type": "object"
    },
    "DeploymentConstraints": {
      "description": "Platform-level constraints for deployment resources.\nPer-environment overrides (stored in the database) can narrow these ranges.",
      "properties": {
        "max_cpu": {
          "default": "2",
          "description": "Maximum CPU allowed (default: \"2\")",
          "type": "string"
        },
        "max_memory": {
          "default": "2Gi",
          "description": "Maximum memory allowed (default: \"2Gi\")",
          "type": "string"
        },
        "max_replicas": {
          "default": 1,
          "description": "Maximum replicas allowed (default: 1). Accepts a number or a numeric\nstring so it can be env-driven (e.g. `\"${RISE_MAX_REPLICAS:-10}\"`).",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "min_cpu": {
          "default": "100m",
          "description": "Minimum CPU allowed (default: \"100m\")",
          "type": "string"
        },
        "min_memory": {
          "default": "64Mi",
          "description": "Minimum memory allowed (default: \"64Mi\")",
          "type": "string"
        },
        "min_replicas": {
          "default": 1,
          "description": "Minimum replicas allowed (default: 1)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        }
      },
      "type": "object"
    },
    "DeploymentControllerSettings": {
      "description": "Deployment controller configuration",
      "oneOf": [
        {
          "description": "Kubernetes deployment controller",
          "properties": {
            "access_classes": {
              "additionalProperties": {
                "anyOf": [
                  {
                    "$ref": "#/$defs/AccessClass"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "description": "Access classes defining ingress authentication levels\nKey: access class identifier (e.g., \"public\", \"private\")\nValue: access class configuration (display info, ingress settings)\nUse `null` in YAML to remove an inherited access class from parent configs",
              "type": "object"
            },
            "auth_backend_url": {
              "description": "Backend URL for Nginx auth subrequests (internal cluster URL)\nExample: \"http://rise-backend.default.svc.cluster.local:3000\"\nThis is the URL Nginx will use internally within the cluster to validate authentication.\nFor Minikube development, use the Docker bridge IP to reach host (e.g., \"http://host.minikube.internal:3000\").",
              "type": "string"
            },
            "auth_signin_url": {
              "description": "Public backend URL for browser redirects during authentication\nExample: \"https://rise.dev\"\nThis must be the public URL where the backend is accessible via Ingress.\nThe domain should share a parent with app domains for cookie sharing (see struct docs).",
              "type": "string"
            },
            "controller_class_name": {
              "default": "default",
              "description": "Stable identifier for this deployment controller, written to the\ndefault Organization's `spec.deploymentControllerClass` at startup.\nThe Kubernetes controller only reconciles projects whose\nOrganization's `spec.deploymentControllerClass` matches this value.\nStamped as a value of the `rise.dev/controller-class` label on\neach `RiseProject` CR, so it must be a valid Kubernetes label\nvalue (alphanumeric / `-` / `_` / `.`, no `/`). Defaults to\n`default`.",
              "type": "string"
            },
            "crd_upsert_interval_ms": {
              "default": 1000,
              "description": "Interval in milliseconds between consecutive RiseProject CRD upserts\nduring the startup backfill. The backfill runs as a background task,\ntouching every active project's CRD at this rate so that version-label\nchanges (introduced by an upgrade) are propagated to Metacontroller\ngradually rather than all at once. Defaults to 1000 (1 per second).",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "custom_domain_ingress_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Annotations to apply ONLY to custom domain ingresses (not primary ingresses)\nUse this for cert-manager integration or other custom domain-specific configuration\nExample: {\"cert-manager.io/cluster-issuer\": \"letsencrypt-prod\"}",
              "type": "object"
            },
            "custom_domain_tls_mode": {
              "$ref": "#/$defs/CustomDomainTlsMode",
              "default": "per-domain",
              "description": "TLS mode for custom domains\n- \"shared\": All custom domains share ingress_tls_secret_name (requires it to be set)\n- \"per-domain\": Each custom domain gets its own tls-{domain} secret\n  (works with cert-manager when custom_domain_ingress_annotations are configured)\n\nDefaults to \"per-domain\""
            },
            "deployment_constraints": {
              "$ref": "#/$defs/DeploymentConstraints",
              "description": "Platform-level constraints for deployment resources"
            },
            "deployment_defaults": {
              "$ref": "#/$defs/DeploymentDefaults",
              "description": "Default resource values for new deployments when not specified by the user"
            },
            "environment_ingress_url_template": {
              "default": null,
              "description": "Ingress URL template for named environments (e.g., staging, dev)\nOnly used for non-production environments whose primary deployment group matches\nthe deployment's group. Production environments use `production_ingress_url_template`.\nSupports both subdomain and sub-path routing:\n  Subdomain: \"{environment}--{project_name}.apps.rise.dev\"\n  Sub-path: \"rise.dev/{project_name}/{environment}\"\nMust contain both {project_name} and {environment} placeholders\nIf not set, environment-specific URLs are not generated.",
              "type": [
                "string",
                "null"
              ]
            },
            "extra_service_token_audiences": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Extra projected service account tokens to mount into every deployed app pod.\nKey becomes the in-pod filename under /var/run/secrets/rise/tokens/, value is the audience.\nExample: {\"vault\": \"https://vault.example.com\"}",
              "type": "object"
            },
            "health_probes": {
              "anyOf": [
                {
                  "$ref": "#/$defs/HealthProbeConfig"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Health probe configuration\nIf not set, uses defaults (HTTP probes on app port at \"/\" path)"
            },
            "host_aliases": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Host aliases to inject into pod specs (hostname -> IP address)\nMaps hostnames to IP addresses, injected as Kubernetes hostAliases.\nUseful for local development where pods need to resolve custom hostnames.\nExample: {\"rise.local\": \"192.168.49.1\"}",
              "type": "object"
            },
            "identity_token_ttl_seconds": {
              "default": 3600,
              "description": "Lifetime in seconds of workload identity tokens auto-minted by the controller\nand mounted into deployment pods. The controller re-mints tokens when they\nare older than half this value. Default: 3600 (1 hour).",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "image_pull_secret_name": {
              "default": null,
              "description": "Optional name of an existing imagePullSecret to use for deployments\n\nIf not specified:\n  - With a registry provider (e.g., ECR): The controller creates and manages the secret\n  - Without a registry provider: No image pull secret is used\n\nIf specified:\n  - The named secret must exist in each project namespace\n  - The controller will NOT create or manage the secret\n  - Useful for static registries where credentials are managed externally\n\nExample: \"my-registry-secret\"",
              "type": [
                "string",
                "null"
              ]
            },
            "ingress_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Ingress annotations to apply to all deployed application ingresses\nThese apply to both primary ingresses and custom domain ingresses\nFor annotations specific to custom domains only, use custom_domain_ingress_annotations\nExample: {\"nginx.ingress.kubernetes.io/proxy-body-size\": \"10m\"}",
              "type": "object"
            },
            "ingress_port": {
              "default": null,
              "description": "Optional port number to append to all generated ingress URLs\nUsed for development environments with port-forwarding (e.g., kubectl port-forward)\nExample: 8080 → \"https://myapp.apps.rise.local:8080\"\nIf not set, URLs use standard ports (80 for HTTP, 443 for HTTPS)",
              "format": "uint16",
              "maximum": 65535,
              "minimum": 0,
              "type": [
                "integer",
                "null"
              ]
            },
            "ingress_schema": {
              "default": "https",
              "description": "URL scheme for generated ingress URLs\nUsed to specify whether URLs should use \"http\" or \"https\"\nExample: \"http\" → \"http://myapp.apps.rise.local\"\nDefaults to \"https\"",
              "type": "string"
            },
            "ingress_tls_secret_name": {
              "default": null,
              "description": "TLS secret name for primary ingress certificates\nIf set, enables TLS on primary ingresses with this secret\nFor custom domain TLS, see custom_domain_tls_mode and custom_domain_ingress_annotations\nExample: \"rise-apps-tls\" (secret must exist in each namespace)",
              "type": [
                "string",
                "null"
              ]
            },
            "kubeconfig": {
              "default": null,
              "description": "Optional kubeconfig path (defaults to in-cluster or ~/.kube/config)",
              "type": [
                "string",
                "null"
              ]
            },
            "metacontroller_pod_label_selector": {
              "default": null,
              "description": "Label selector used to find metacontroller pods.\nDefaults to \"app.kubernetes.io/name=metacontroller-operator\".",
              "type": [
                "string",
                "null"
              ]
            },
            "metacontroller_pod_namespace": {
              "default": "",
              "description": "Kubernetes namespace where metacontroller pods run.\nWhen set, Rise validates that webhook requests originate from a live\nmetacontroller pod IP. Absent in development (IP validation disabled).",
              "type": "string"
            },
            "metacontroller_webhook_port": {
              "default": 3001,
              "description": "Port for the internal metacontroller webhook listener.\nWebhook endpoints are served on this separate port instead of the main HTTP port.\nDefaults to 3001.",
              "format": "uint16",
              "maximum": 65535,
              "minimum": 0,
              "type": "integer"
            },
            "namespace_annotations": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Annotations to apply to all managed namespaces\nExample: {\"company.com/team\": \"platform\", \"cost-center\": \"engineering\"}",
              "type": "object"
            },
            "namespace_labels": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {},
              "description": "Labels to apply to all managed namespaces\nExample: {\"environment\": \"production\", \"team\": \"platform\"}",
              "type": "object"
            },
            "network_policy": {
              "$ref": "#/$defs/NetworkPolicyConfig",
              "description": "NetworkPolicy configuration for deployed apps"
            },
            "node_selector": {
              "additionalProperties": {
                "type": "string"
              },
              "default": {
                "kubernetes.io/arch": "amd64"
              },
              "description": "Node selector for pod placement (controls which nodes pods can run on)\nDefault: {\"kubernetes.io/arch\": \"amd64\"}\nExample: {\"kubernetes.io/arch\": \"amd64\", \"node-type\": \"compute\"}",
              "type": "object"
            },
            "pod_security_enabled": {
              "default": true,
              "description": "Pod security settings (enabled by default)\nSet to false to disable security context enforcement",
              "type": "boolean"
            },
            "production_ingress_url_template": {
              "description": "Ingress URL template for production (default) deployment group\nSupports both subdomain and sub-path routing:\n  Subdomain: \"{project_name}.apps.rise.dev\"\n  Sub-path: \"rise.dev/{project_name}\"\nMust contain {project_name} placeholder",
              "type": "string"
            },
            "staging_ingress_url_template": {
              "default": null,
              "description": "Ingress URL template for staging (non-default) deployment groups\nSupports both subdomain and sub-path routing:\n  Subdomain: \"{project_name}-{deployment_group}.preview.rise.dev\"\n  Sub-path: \"rise.dev/{project_name}/{deployment_group}\"\nMust contain both {project_name} and {deployment_group} placeholders\nIf not set, falls back to inserting \"-{deployment_group}\" before first dot",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "kubernetes",
              "type": "string"
            },
            "use_default_service_account_for_production": {
              "default": true,
              "description": "When true, deployments in the production environment use the namespace's\n`default` ServiceAccount instead of creating `env-{name}`.\nUseful for backwards compatibility when existing IAM bindings (e.g., IRSA)\nare configured on the `default` SA.\nDefaults to true.",
              "type": "boolean"
            }
          },
          "required": [
            "type",
            "production_ingress_url_template",
            "auth_backend_url",
            "auth_signin_url",
            "access_classes",
            "network_policy"
          ],
          "type": "object"
        },
        {
          "description": "Docker deployment controller.\n\nDeploys app containers to a single Docker host and lets Traefik's Docker\nprovider route to them via container labels Rise stamps. No Kubernetes,\nno Metacontroller — reconciliation runs in-process (see\n`controller::docker::DockerReconciler`).",
          "properties": {
            "access_classes": {
              "additionalProperties": {
                "anyOf": [
                  {
                    "$ref": "#/$defs/AccessClass"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "default": {
                "public": {
                  "access_requirement": "None",
                  "custom_annotations": {},
                  "description": "Fully public — no authentication required.",
                  "display_name": "Public",
                  "ingress_class": "traefik"
                }
              },
              "description": "Access classes defining ingress authentication levels, keyed by\nidentifier (e.g. \"public\", \"private\"). Mirrors the Kubernetes variant\nso the typed project API can validate a project's access class. For\naccess classes whose `access_requirement` is `Authenticated`/`Member`,\nthe Docker controller stamps Traefik forwardAuth middleware labels\n(requires `auth_backend_url` to be set). A permissive \"public\" class\nis provided by default; operators may override.\nUse `null` in YAML to remove an inherited access class.",
              "type": "object"
            },
            "app_backend_host_aliases": {
              "default": [],
              "description": "**LOCAL-DEV ONLY.** Hostname(s) to alias to the Rise backend's IP on\nthe shared `traefik_network`, injected as `extra_hosts` on every\nmanaged app container the controller creates.\n\nApps that validate the `rise_jwt` cookie or perform OIDC discovery\nagainst the public issuer host (e.g. `rise.localhost`) must be able\nto reach the Rise backend at that host. In a local stack the public\nhost resolves to the container's own loopback, not the backend, so\nthe controller stamps `HostConfig.extra_hosts` mapping each alias to\nthe backend's reachable IP (resolved at reconcile time from the\n`auth_backend_url` host via Docker DNS).\n\n**Leave empty in production.** Production relies on public DNS +\nTraefik for the issuer host (with correct TLS termination); injecting\nan `extra_hosts` override there would wrongly bypass Traefik and break\nTLS. Empty/absent (the default) → no injection.\n\nMirrors the Kubernetes `host_aliases` mechanism (see\n`resource_builder.rs`, which injects `rise.local → host IP` into pods).\n\nBlank entries are filtered out at load time, so an env-driven\nsingle-element list (`[\"${RISE_APP_BACKEND_HOST_ALIAS:-}\"]`) collapses\nto an empty list — and no injection — when the env var is unset\n(production).",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "app_backend_ip": {
              "default": null,
              "description": "**LOCAL-DEV ONLY.** Explicit IP/value to alias the\n`app_backend_host_aliases` hosts to (injected as `extra_hosts` on\nmanaged app containers), used VERBATIM instead of resolving the\n`auth_backend_url` host via DNS.\n\nSet to Docker's special `host-gateway` so apps reach a host-run\nbackend (`mise br docker`) on both Docker Desktop and Linux — Docker\nreplaces `host-gateway` with the host gateway per container at create\ntime. Empty/absent (the default) → fall back to DNS resolution of the\n`auth_backend_url` host. **Leave unset in production** (the\ncontainerized backend is resolved by Docker DNS).",
              "type": [
                "string",
                "null"
              ]
            },
            "auth_backend_url": {
              "default": "",
              "description": "Internal URL Traefik uses to reach the Rise backend for the\nforwardAuth subrequest (e.g. `http://rise:3000` on the compose\nnetwork). Used to build the `forwardauth.address` middleware label\npointing at `/api/v1/auth/ingress`. Required when any configured\naccess class has a non-`None` access requirement\n(`Authenticated`/`Member`); the backend refuses to start otherwise,\nto avoid serving those projects publicly with no auth enforced.",
              "type": "string"
            },
            "auth_signin_url": {
              "default": "",
              "description": "Browser-facing base URL for the login redirect (e.g. the public URL\n`http://localhost:3000`). Traefik forwardAuth has no nginx-style\n`auth-signin`, so the `ingress_auth` handler 302-redirects\nunauthenticated browsers to `{auth_signin_url}/api/v1/auth/signin`.\nIf empty, falls back to the server `public_url`.",
              "type": "string"
            },
            "container_prefix": {
              "default": "rise",
              "description": "Prefix for generated container names (`<prefix>_<project>_...`).\nDefaults to `rise`.",
              "type": "string"
            },
            "controller_class_name": {
              "default": "default",
              "description": "Stable identifier for this deployment controller. The reconciler only\nreconciles projects matching this class. Defaults to `default`.",
              "type": "string"
            },
            "deployment_constraints": {
              "$ref": "#/$defs/DeploymentConstraints",
              "description": "Platform-level constraints for deployment resources."
            },
            "deployment_defaults": {
              "$ref": "#/$defs/DeploymentDefaults",
              "description": "Default resource values for new deployments when not specified."
            },
            "docker_host": {
              "default": null,
              "description": "Docker daemon connection. `None` uses bollard's local defaults\n(unix socket / npipe, or `DOCKER_HOST`). May be a unix socket path\n(`unix:///var/run/docker.sock`) or a TCP URL (`tcp://host:2375`).",
              "type": [
                "string",
                "null"
              ]
            },
            "environment_ingress_url_template": {
              "default": null,
              "description": "Ingress URL template for named environments.\nMust contain both `{project_name}` and `{environment}`.",
              "type": [
                "string",
                "null"
              ]
            },
            "health_probes": {
              "anyOf": [
                {
                  "$ref": "#/$defs/HealthProbeConfig"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Health probe configuration. The reconciler probes routable\ncontainers over the shared network using these settings."
            },
            "identity_token_ttl_seconds": {
              "default": 3600,
              "description": "Lifetime in seconds of workload identity tokens minted for\ndeployments. Default: 3600 (1 hour).",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "ingress_port": {
              "default": null,
              "description": "Optional port appended to all generated ingress URLs (e.g. `8080`).",
              "format": "uint16",
              "maximum": 65535,
              "minimum": 0,
              "type": [
                "integer",
                "null"
              ]
            },
            "ingress_schema": {
              "default": "https",
              "description": "URL scheme for generated ingress URLs (`http` or `https`).\nDefaults to `https`.",
              "type": "string"
            },
            "label_namespace": {
              "default": "rise.dev",
              "description": "Label namespace prefix for Rise bookkeeping labels\n(e.g. `rise.dev/managed-by`). Defaults to `rise.dev`.",
              "type": "string"
            },
            "production_ingress_url_template": {
              "description": "Ingress URL template for the production (default) deployment group.\nSame semantics as the Kubernetes variant. Must contain\n`{project_name}`. Drives both the computed app URLs and the Traefik\n`Host(...)` router rules.",
              "type": "string"
            },
            "publish_app_ports": {
              "default": false,
              "description": "**Dev-only.** Publish each app container's HTTP port to a random\n`127.0.0.1` host port so a host-run backend (`mise br docker` /\nDocker Desktop, where container bridge IPs aren't routable from the\nhost) can health-probe the app directly. Leave off in production —\nthe containerized backend reaches app containers over the\n`rise_default` network.",
              "type": "boolean"
            },
            "reconcile_interval_secs": {
              "default": 5,
              "description": "Interval in seconds between reconcile ticks. Defaults to 5.",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "staging_ingress_url_template": {
              "default": null,
              "description": "Ingress URL template for staging (non-default) deployment groups.\nMust contain both `{project_name}` and `{deployment_group}`.",
              "type": [
                "string",
                "null"
              ]
            },
            "traefik_api_url": {
              "default": null,
              "description": "Base URL of Traefik's API (e.g. `http://rise-traefik:8080` in-network\nor `http://localhost:8090` for a host-run dev backend). Read to learn\nper-server health status (the top-level `serverStatus` map) from\nTraefik so the old deployment is retired only once the new servers are\nactually in Traefik's rotation. Optional basic-auth may be embedded in\nthe URL (userinfo). **Required for projects that set a `health_check`**:\nTraefik's `serverStatus` is the authoritative readiness signal (no\nfallback), so without a reachable API a health-checked deployment never\nbecomes Healthy. May be left empty only when no project uses health\nchecks (ready-when-running gates on run-state alone).",
              "type": [
                "string",
                "null"
              ]
            },
            "traefik_certresolver": {
              "default": null,
              "description": "Optional Traefik certresolver name. When set, routers get\n`tls=true` + `tls.certresolver=<name>` labels for automatic TLS.",
              "type": [
                "string",
                "null"
              ]
            },
            "traefik_entrypoint": {
              "default": "web",
              "description": "Traefik entrypoint name routers bind to (e.g. `web`, `websecure`).\nDefaults to `web`.",
              "type": "string"
            },
            "traefik_network": {
              "description": "Docker network shared with Traefik. App containers are attached to\nthis network so Traefik can reach them, and it is emitted as the\n`traefik.docker.network` label.",
              "type": "string"
            },
            "type": {
              "const": "docker",
              "type": "string"
            }
          },
          "required": [
            "type",
            "production_ingress_url_template",
            "traefik_network"
          ],
          "type": "object"
        }
      ]
    },
    "DeploymentDefaults": {
      "description": "Default resource values for new deployments when not specified by the user",
      "properties": {
        "cpu": {
          "default": "500m",
          "description": "Default CPU allocation (default: \"500m\") — sets both K8s request and limit",
          "type": "string"
        },
        "memory": {
          "default": "256Mi",
          "description": "Default memory allocation (default: \"256Mi\") — sets both K8s request and limit",
          "type": "string"
        },
        "replicas": {
          "default": 1,
          "description": "Default number of replicas (default: 1)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        }
      },
      "type": "object"
    },
    "DeploymentLogsSettings": {
      "oneOf": [
        {
          "properties": {
            "max_tail_lines": {
              "default": 100000,
              "description": "Upper bound on the number of lines the backend will ever request from\nthe kubelet in a single call. The frontend pages backward by widening\n`tail_lines`; once `tail_lines + skip_recent` reaches this ceiling,\npaging stops yielding new lines — the same outcome as when the\nkubelet's own ring buffer is exhausted. Default: 100000.",
              "format": "int64",
              "type": "integer"
            },
            "type": {
              "const": "kubernetes",
              "type": "string"
            }
          },
          "required": [
            "type"
          ],
          "type": "object"
        },
        {
          "description": "Stream logs directly from the Docker daemon (`docker logs`). Used with\nthe Docker deployment controller. Requires no kube client — the bollard\nclient is shared from the Docker deployment controller, so this variant\ncarries no connection fields of its own.",
          "properties": {
            "type": {
              "const": "docker",
              "type": "string"
            }
          },
          "required": [
            "type"
          ],
          "type": "object"
        },
        {
          "properties": {
            "bearer_token_env": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "labels": {
              "$ref": "#/$defs/LokiLabels",
              "description": "Override the Loki stream label names. Useful when pointing Rise\nat an operator-managed Loki/Alloy stack that labels logs\ndifferently than the bundled chart."
            },
            "retention_hint": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "tenant_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "timeout_secs": {
              "default": 10,
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "type": {
              "const": "loki",
              "type": "string"
            },
            "url": {
              "type": "string"
            }
          },
          "required": [
            "type",
            "url"
          ],
          "type": "object"
        }
      ]
    },
    "EncryptionSettings": {
      "description": "Encryption provider configuration",
      "oneOf": [
        {
          "description": "Local AES-256-GCM encryption using a symmetric key",
          "properties": {
            "key": {
              "description": "Base64-encoded 32-byte encryption key\nGenerate with: openssl rand -base64 32",
              "type": "string"
            },
            "type": {
              "const": "aes-gcm-256",
              "type": "string"
            }
          },
          "required": [
            "type",
            "key"
          ],
          "type": "object"
        },
        {
          "description": "AWS KMS encryption",
          "properties": {
            "access_key_id": {
              "default": null,
              "description": "Optional static credentials (development only)",
              "type": [
                "string",
                "null"
              ]
            },
            "key_id": {
              "description": "KMS key ID or ARN",
              "type": "string"
            },
            "region": {
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "description": "Optional static credentials (development only)",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "aws-kms",
              "type": "string"
            }
          },
          "required": [
            "type",
            "region",
            "key_id"
          ],
          "type": "object"
        }
      ]
    },
    "ExtensionProviderConfig": {
      "description": "Extension provider configuration",
      "oneOf": [
        {
          "properties": {
            "access_key_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "backup_retention_days": {
              "default": 7,
              "description": "Backup retention period in days (1-35, default: 7)",
              "format": "int32",
              "type": "integer"
            },
            "backup_window": {
              "default": null,
              "description": "Preferred backup window in UTC (e.g., \"03:00-04:00\")",
              "type": [
                "string",
                "null"
              ]
            },
            "db_subnet_group_name": {
              "default": null,
              "description": "DB subnet group name for VPC placement",
              "type": [
                "string",
                "null"
              ]
            },
            "default_engine_version": {
              "default": "18.2",
              "description": "Default engine version to use if not specified in project extension spec\nUse AWS CLI to find versions: aws rds describe-db-engine-versions --engine postgres --query \"DBEngineVersions[*].EngineVersion\"",
              "type": "string"
            },
            "disk_size": {
              "format": "int32",
              "type": "integer"
            },
            "instance_id_prefix": {
              "default": "rise",
              "description": "Prefix for RDS instance identifiers\nMust match the IAM policy prefix configured in your Terraform infrastructure\nDefault: \"rise\"",
              "type": "string"
            },
            "instance_id_template": {
              "default": "{prefix}-{project_name}-{extension_name}",
              "description": "Template for RDS instance identifiers\nAvailable placeholders: {prefix}, {project_name}, {extension_name}\nDefault: \"{prefix}-{project_name}-{extension_name}\"",
              "type": "string"
            },
            "instance_size": {
              "type": "string"
            },
            "maintenance_window": {
              "default": null,
              "description": "Preferred maintenance window (e.g., \"sun:04:00-sun:05:00\")",
              "type": [
                "string",
                "null"
              ]
            },
            "region": {
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "aws-rds-provisioner",
              "type": "string"
            },
            "vpc_security_group_ids": {
              "default": null,
              "description": "VPC security group IDs for the RDS instance",
              "items": {
                "type": "string"
              },
              "type": [
                "array",
                "null"
              ]
            }
          },
          "required": [
            "type",
            "region",
            "instance_size",
            "disk_size"
          ],
          "type": "object"
        },
        {
          "properties": {
            "access_key_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "bucket_name_template": {
              "default": "{prefix}-{project_name}-{extension_name}",
              "description": "Template for bucket names.\nPlaceholders: {prefix}, {project_name}, {extension_name}\nDefault: \"{prefix}-{project_name}-{extension_name}\"",
              "type": "string"
            },
            "bucket_prefix": {
              "default": "rise",
              "description": "Prefix for S3 bucket and IAM user names. Must match the Terraform IAM policy prefix.\nDefault: \"rise\"",
              "type": "string"
            },
            "region": {
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "aws-s3-bucket",
              "type": "string"
            },
            "user_permissions_boundary_arn": {
              "description": "ARN of the IAM permissions boundary policy (output `s3_user_permissions_boundary_arn`\nfrom the `rise-aws` Terraform module). All IAM users created by this extension will\nhave this boundary attached, preventing privilege escalation.",
              "type": "string"
            }
          },
          "required": [
            "type",
            "region",
            "user_permissions_boundary_arn"
          ],
          "type": "object"
        },
        {
          "description": "Snowflake authentication configuration",
          "oneOf": [
            {
              "properties": {
                "auth_type": {
                  "const": "password",
                  "type": "string"
                },
                "password": {
                  "type": "string"
                }
              },
              "required": [
                "auth_type",
                "password"
              ],
              "type": "object"
            },
            {
              "anyOf": [
                {
                  "properties": {
                    "private_key_path": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "private_key_path"
                  ],
                  "type": "object"
                },
                {
                  "properties": {
                    "private_key": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "private_key"
                  ],
                  "type": "object"
                }
              ],
              "description": "Private key source (path or inline PEM)",
              "properties": {
                "auth_type": {
                  "const": "private_key",
                  "type": "string"
                },
                "private_key_password": {
                  "default": null,
                  "type": [
                    "string",
                    "null"
                  ]
                }
              },
              "required": [
                "auth_type"
              ],
              "type": "object"
            }
          ],
          "properties": {
            "account": {
              "description": "Snowflake account identifier (e.g., \"myorg.us-east-1\")",
              "type": "string"
            },
            "default_blocked_roles": {
              "default": [
                "ACCOUNTADMIN",
                "ORGADMIN",
                "SECURITYADMIN"
              ],
              "description": "Default blocked roles for OAuth (default: [\"ACCOUNTADMIN\", \"ORGADMIN\", \"SECURITYADMIN\"])",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "default_scopes": {
              "default": [
                "refresh_token"
              ],
              "description": "Default OAuth scopes (default: [\"refresh_token\"])",
              "items": {
                "type": "string"
              },
              "type": "array"
            },
            "integration_name_prefix": {
              "default": "rise",
              "description": "Prefix for SECURITY INTEGRATION names (default: \"rise\")",
              "type": "string"
            },
            "refresh_token_validity_seconds": {
              "default": 7776000,
              "description": "Refresh token validity in seconds (default: 7776000 = 90 days)",
              "format": "int64",
              "type": "integer"
            },
            "role": {
              "default": null,
              "description": "Snowflake role to use (must have CREATE INTEGRATION ON ACCOUNT privilege)\nTypically ACCOUNTADMIN or a custom role with appropriate grants",
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "snowflake-oauth-provisioner",
              "type": "string"
            },
            "user": {
              "description": "Snowflake user with CREATE INTEGRATION privilege",
              "type": "string"
            },
            "verify_interval_seconds": {
              "default": 3600,
              "description": "How often (in seconds) to re-verify that the SECURITY INTEGRATION still\nexists in Snowflake once the extension is `Available`. The provisioner\nruns an inner 5s tick for transitional states, but `Available`\nextensions are only re-verified after this interval elapses. Default:\n3600 (1 hour). The metadata query used for the check (`SHOW\nINTEGRATIONS`) does not activate the configured warehouse, so a\nhealthy steady state lets Snowflake auto-suspend the warehouse.",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "warehouse": {
              "default": null,
              "description": "Snowflake warehouse to use for queries\nRequired for executing SQL statements",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "type",
            "account",
            "user"
          ],
          "type": "object"
        }
      ]
    },
    "ExtensionsSettings": {
      "description": "Extensions configuration",
      "properties": {
        "providers": {
          "items": {
            "$ref": "#/$defs/ExtensionProviderConfig"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "HealthProbeConfig": {
      "description": "Health probe configuration",
      "properties": {
        "failure_threshold": {
          "default": 3,
          "description": "Failure threshold (default: 3)",
          "format": "int32",
          "type": "integer"
        },
        "initial_delay_seconds": {
          "default": 10,
          "description": "Initial delay in seconds (default: 10)",
          "format": "int32",
          "type": "integer"
        },
        "liveness_enabled": {
          "default": true,
          "description": "Enable liveness probes (default: true)",
          "type": "boolean"
        },
        "path": {
          "default": "/",
          "description": "Path for HTTP probes (default: \"/\")",
          "type": "string"
        },
        "period_seconds": {
          "default": 10,
          "description": "Period in seconds (default: 10)",
          "format": "int32",
          "type": "integer"
        },
        "readiness_enabled": {
          "default": true,
          "description": "Enable readiness probes (default: true)",
          "type": "boolean"
        },
        "timeout_seconds": {
          "default": 5,
          "description": "Timeout in seconds (default: 5)",
          "format": "int32",
          "type": "integer"
        }
      },
      "type": "object"
    },
    "JfrogTokenProviderSettings": {
      "description": "Token provider configuration for JFrog registry",
      "oneOf": [
        {
          "description": "HashiCorp Vault with vault-plugin-secrets-artifactory",
          "properties": {
            "scope_override": {
              "default": true,
              "description": "When true (default), Rise sends per-operation scopes to Vault, overriding\nthe role's default scope. With the Rise fork of vault-plugin-secrets-artifactory,\nconfigure admin `allow_scope_override=\"opt-in\"` and role\n`allow_scope_override=true` with narrow `allowed_scopes`.\nWhen false, Rise omits the scope parameter and the role's configured scope is used.",
              "type": "boolean"
            },
            "type": {
              "const": "vault",
              "type": "string"
            },
            "vault_addr": {
              "default": null,
              "description": "Vault server address (fallback: VAULT_ADDR env var)",
              "type": [
                "string",
                "null"
              ]
            },
            "vault_mount_path": {
              "default": "artifactory",
              "description": "Vault secrets engine mount path (default: \"artifactory\")",
              "type": "string"
            },
            "vault_role": {
              "description": "Vault role name",
              "type": "string"
            },
            "vault_token": {
              "default": null,
              "description": "Vault token (fallback: VAULT_TOKEN env var)",
              "type": [
                "string",
                "null"
              ]
            },
            "vault_token_file": {
              "default": null,
              "description": "Path to a file containing the Vault token (fallback: VAULT_TOKEN_FILE env var).\nRe-read on each request to support token rotation.",
              "type": [
                "string",
                "null"
              ]
            }
          },
          "required": [
            "type",
            "vault_role"
          ],
          "type": "object"
        },
        {
          "description": "JFrog's own access token API with an admin token",
          "properties": {
            "admin_token": {
              "description": "Admin access token for minting scoped tokens",
              "type": "string"
            },
            "jfrog_url": {
              "description": "JFrog platform URL (e.g., \"http://rise-jfrog:8082\")",
              "type": "string"
            },
            "type": {
              "const": "direct",
              "type": "string"
            }
          },
          "required": [
            "type",
            "jfrog_url",
            "admin_token"
          ],
          "type": "object"
        }
      ]
    },
    "LokiLabels": {
      "properties": {
        "deployment_id": {
          "default": "rise_deployment_id",
          "description": "Loki stream label that carries the Rise deployment id (e.g.\n\"20241205-1234\"). Together with `project`, this must uniquely\nidentify a deployment's log stream.",
          "type": "string"
        },
        "project": {
          "default": "rise_project",
          "description": "Loki stream label that carries the Rise project name.",
          "type": "string"
        }
      },
      "type": "object"
    },
    "NetworkPolicyConfig": {
      "description": "NetworkPolicy configuration for deployed apps\n\nUses Kubernetes NetworkPolicy types directly. Egress semantics:\n- null: policyTypes is [\"Ingress\"] only, Kubernetes does not restrict egress\n- Empty list: policyTypes includes \"Egress\" with no rules = deny all egress\n- Non-empty list: explicit egress rules enforced",
      "properties": {
        "egress": {
          "description": "Egress rules (null = unrestricted egress)",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyEgressRule"
          },
          "type": [
            "array",
            "null"
          ]
        },
        "ingress": {
          "description": "Ingress rules",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyIngressRule"
          },
          "type": "array"
        }
      },
      "required": [
        "ingress"
      ],
      "type": "object"
    },
    "OAuthRateLimitSettings": {
      "description": "Rate limiting configuration for OAuth endpoints (authorize, callback, token).\n\nFour independent limits are enforced:\n- **Per-project**: keyed by project name — limits traffic to each project independently\n  so one busy project cannot starve others.\n- **Per-IP**: keyed by client IP — limits traffic from a single source address.\n- **Per-session**: keyed by a hash of the `rise_jwt` cookie — limits per-user traffic.\n- **Global**: shared across all requests — caps total throughput.\n\nSetting any `*_max` value to `0` effectively blocks all OAuth traffic for that tier\n(every request will exceed the limit immediately).",
      "properties": {
        "global_max": {
          "default": 2000,
          "description": "Maximum total OAuth requests per window across all clients (default: 2000)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "global_window_secs": {
          "default": 60,
          "description": "Window in seconds for the global limit (default: 60)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "per_ip_max": {
          "default": 50,
          "description": "Maximum requests per client IP per window (default: 50)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "per_ip_window_secs": {
          "default": 60,
          "description": "Window in seconds for per-IP limit (default: 60)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "per_project_max": {
          "default": 500,
          "description": "Maximum requests per project per window (default: 500)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "per_project_window_secs": {
          "default": 60,
          "description": "Window in seconds for per-project limit (default: 60)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "per_session_max": {
          "default": 20,
          "description": "Maximum requests per session (rise_jwt cookie) per window (default: 20)",
          "format": "uint32",
          "minimum": 0,
          "type": "integer"
        },
        "per_session_window_secs": {
          "default": 60,
          "description": "Window in seconds for per-session limit (default: 60)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        }
      },
      "type": "object"
    },
    "PlatformAccessConfig": {
      "description": "Platform access control configuration",
      "properties": {
        "allowed_idp_groups": {
          "default": [],
          "description": "IdP groups whose members get platform access",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "allowed_user_emails": {
          "default": [],
          "description": "User emails explicitly granted platform access",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "policy": {
          "$ref": "#/$defs/PlatformAccessPolicy",
          "description": "Policy: \"allow_all\" (default) or \"restrictive\""
        }
      },
      "type": "object"
    },
    "PlatformAccessPolicy": {
      "description": "Platform access policy enum",
      "enum": [
        "allow_all",
        "restrictive"
      ],
      "type": "string"
    },
    "QuickstartSettings": {
      "description": "Catalog of quickstart templates exposed via `GET /api/v1/quickstart-templates`.",
      "properties": {
        "templates": {
          "items": {
            "$ref": "#/$defs/QuickstartTemplateConfig"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "QuickstartTemplateConfig": {
      "description": "A single quickstart catalog entry. Mirrors the API shape on the wire, with\none difference: `icon` accepts either a built-in icon name (resolved to\n`/assets/quickstart/<name>.svg`) or an absolute URL/path (must start with\n`http://`, `https://`, or `/`).",
      "properties": {
        "description": {
          "description": "Longer description shown in the deploy dialog.",
          "type": "string"
        },
        "display_name": {
          "description": "Short, human-friendly name shown in cards and dialogs.",
          "type": "string"
        },
        "http_port": {
          "description": "Port the container listens on.",
          "format": "uint16",
          "maximum": 65535,
          "minimum": 0,
          "type": "integer"
        },
        "icon": {
          "description": "Built-in icon name (e.g. `welcome`, `whoami`, `httpbin`, `excalidraw`)\nresolved against `/assets/quickstart/<name>.svg`, OR an absolute URL or\nstatic path (`http://...`, `https://...`, `/some/path.svg`).",
          "type": "string"
        },
        "id": {
          "description": "Stable kebab-case identifier used in URLs and project records.",
          "type": "string"
        },
        "image": {
          "description": "Fully-qualified container image. Pin a tag when one is published so the\ncatalog is reproducible; floating tags (`:latest`) work but defeat the\n\"Redeploy from template\" drift detector.",
          "type": "string"
        },
        "learn_more_url": {
          "description": "Upstream link for users who want to learn more.",
          "type": "string"
        },
        "tagline": {
          "description": "One-line description for the card.",
          "type": "string"
        },
        "tags": {
          "default": [],
          "description": "Free-form tags for filtering / categorisation.",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "warning": {
          "default": null,
          "description": "Optional caveat surfaced in the deploy modal — used when the image\nviolates a platform invariant (root user, privileged port) but is kept\nin the catalog for clusters that allow it.",
          "type": [
            "string",
            "null"
          ]
        }
      },
      "required": [
        "id",
        "display_name",
        "tagline",
        "description",
        "icon",
        "image",
        "http_port",
        "learn_more_url"
      ],
      "type": "object"
    },
    "RegistrySettings": {
      "description": "Registry provider configuration",
      "oneOf": [
        {
          "properties": {
            "access_key_id": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "account_id": {
              "type": "string"
            },
            "auto_remove": {
              "default": false,
              "description": "Whether to automatically delete ECR repos when projects are deleted",
              "type": "boolean"
            },
            "push_role_arn": {
              "description": "IAM role ARN for push operations (assumed to generate scoped credentials)",
              "type": "string"
            },
            "region": {
              "type": "string"
            },
            "repo_prefix": {
              "default": "rise/",
              "description": "Literal prefix for ECR repository names (e.g., \"rise/\" → repos named \"rise/{project}\")",
              "type": "string"
            },
            "secret_access_key": {
              "default": null,
              "type": [
                "string",
                "null"
              ]
            },
            "type": {
              "const": "ecr",
              "type": "string"
            }
          },
          "required": [
            "type",
            "region",
            "account_id",
            "push_role_arn"
          ],
          "type": "object"
        },
        {
          "properties": {
            "client_registry_url": {
              "default": null,
              "description": "Optional client-facing registry URL for CLI push operations\nIf not specified, defaults to registry_url",
              "type": [
                "string",
                "null"
              ]
            },
            "namespace": {
              "default": "",
              "type": "string"
            },
            "registry_url": {
              "type": "string"
            },
            "type": {
              "const": "oci-client-auth",
              "type": "string"
            }
          },
          "required": [
            "type",
            "registry_url"
          ],
          "type": "object"
        },
        {
          "description": "GitLab container registry — mints scoped JWTs per deployment",
          "properties": {
            "client_registry_url": {
              "default": null,
              "description": "Optional client-facing registry URL override",
              "type": [
                "string",
                "null"
              ]
            },
            "gitlab_url": {
              "description": "GitLab instance URL (e.g., \"https://gitlab.com\")",
              "type": "string"
            },
            "mint_pull_secrets": {
              "default": false,
              "description": "When true, the Kubernetes controller creates and manages image pull secrets\nin each project namespace. Set to false if the cluster has its own pull mechanism.",
              "type": "boolean"
            },
            "namespace": {
              "description": "Full image path prefix within the registry\n(e.g., \"my-org/my-project\" or \"my-org/my-project/rise-apps\")",
              "type": "string"
            },
            "registry_url": {
              "description": "Registry URL (e.g., \"registry.gitlab.com\")",
              "type": "string"
            },
            "token": {
              "description": "Personal Access Token or Deploy Token",
              "type": "string"
            },
            "type": {
              "const": "gitlab",
              "type": "string"
            },
            "username": {
              "description": "GitLab username for JWT auth endpoint",
              "type": "string"
            }
          },
          "required": [
            "type",
            "gitlab_url",
            "registry_url",
            "namespace",
            "username",
            "token"
          ],
          "type": "object"
        },
        {
          "description": "JFrog Artifactory Docker registry — mints scoped access tokens per operation",
          "properties": {
            "client_registry_url": {
              "default": null,
              "description": "Optional client-facing registry host override for CLI push",
              "type": [
                "string",
                "null"
              ]
            },
            "docker_repo_key": {
              "description": "Docker repository key in JFrog (e.g., \"rise-docker-local\")",
              "type": "string"
            },
            "mint_pull_secrets": {
              "default": true,
              "description": "Whether K8s controller creates image pull secrets (default: true)",
              "type": "boolean"
            },
            "pull_permissions": {
              "default": "r",
              "description": "Artifact permission letters for pull tokens (default: \"r\")",
              "type": "string"
            },
            "pull_token_ttl": {
              "default": 86400,
              "description": "TTL in seconds for pull tokens used by K8s image pull secrets (default: 86400)\nFor Vault mode, the Vault role's max_ttl must be >= this value.",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "push_permissions": {
              "default": "r,w",
              "description": "Artifact permission letters for push tokens (default: \"r,w\")",
              "type": "string"
            },
            "push_token_ttl": {
              "default": 600,
              "description": "TTL in seconds for push tokens (default: 600)",
              "format": "uint64",
              "minimum": 0,
              "type": "integer"
            },
            "registry_host": {
              "description": "Docker registry host:port (e.g., \"localhost:3082\")",
              "type": "string"
            },
            "token_provider": {
              "$ref": "#/$defs/JfrogTokenProviderSettings",
              "description": "Token provider configuration"
            },
            "type": {
              "const": "jfrog",
              "type": "string"
            }
          },
          "required": [
            "type",
            "registry_host",
            "docker_repo_key",
            "token_provider"
          ],
          "type": "object"
        }
      ]
    },
    "ResourceGcSettings": {
      "description": "Background garbage-collection worker for the generic resource store.\n\nPolls `list_pending_collection` and drives `try_collect` per row. Each tick\nprocesses at most `batch_size × max_batches_per_tick` rows, capping the\neffective throughput at `batch_size × max_batches_per_tick / interval_secs`\nrows per second. Rows whose `deletion_timestamp` is older than\n`stuck_threshold_secs` are flagged with a `warn!` log per sweep.",
      "properties": {
        "batch_size": {
          "default": 50,
          "description": "Rows per `list_pending_collection` call. Must be >= 1. Default: 50.",
          "format": "int64",
          "minimum": 1,
          "type": "integer"
        },
        "interval_secs": {
          "default": 10,
          "description": "Sweep cadence in seconds. Must be >= 1 (`tokio::time::interval` panics\non zero). Default: 10.",
          "format": "uint64",
          "minimum": 1,
          "type": "integer"
        },
        "max_batches_per_tick": {
          "default": 4,
          "description": "Maximum number of consecutive full batches processed in a single tick.\nLets a deep backlog drain faster without monopolizing the worker. Must\nbe >= 1. Default: 4.",
          "format": "uint32",
          "minimum": 1,
          "type": "integer"
        },
        "stuck_threshold_secs": {
          "default": 3600,
          "description": "Seconds after which a tombstoned row is logged as `stuck` on each\nsweep. Logs-only; does not change collection behavior. Default: 3600.",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        }
      },
      "type": "object"
    },
    "ServerSettings": {
      "properties": {
        "auth_token_max_ttl_seconds": {
          "default": 600,
          "description": "Lifetime in seconds of Rise access tokens minted by the auth\ntoken-exchange endpoint (`POST /api/v1/auth/token`). Kept short because an\nexchanged token cannot be revoked mid-life. Default: 600 (10 minutes).",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "cookie_domain": {
          "default": null,
          "description": "Cookie domain for migration from deployments that previously used domain-scoped cookies.\nWhen configured, responses that set a new host-only `rise_jwt` cookie also include a\n`Max-Age=0` Set-Cookie with this Domain attribute to expire any stale domain-scoped\ncookies browsers may still be sending. Remove once the migration window has passed.",
          "type": [
            "string",
            "null"
          ]
        },
        "cookie_secure": {
          "default": true,
          "description": "Whether to set Secure flag on cookies (true for HTTPS, false for HTTP development)",
          "type": "boolean"
        },
        "docs_dir": {
          "default": null,
          "description": "Directory containing built static documentation files (e.g., \"/var/rise/docs\")\nDefaults to the RISE_DOCS_DIR environment variable.",
          "type": [
            "string",
            "null"
          ]
        },
        "frontend_dev_proxy_url": {
          "default": null,
          "description": "Development-only frontend proxy target (for Vite), e.g. \"http://localhost:5173\"\nWhen set, non-API frontend routes are proxied to this URL instead of serving embedded assets.\nAn empty/whitespace value (e.g. an unset `${VAR:-}` env default) is treated as `None`\nso a production config can env-drive this without accidentally proxying to \"\".",
          "type": [
            "string",
            "null"
          ]
        },
        "host": {
          "type": "string"
        },
        "jwt_claims": {
          "default": [
            "sub",
            "email",
            "name"
          ],
          "description": "JWT claims to include from IdP token when issuing Rise JWTs\nDefault: [\"sub\", \"email\", \"name\"]",
          "items": {
            "type": "string"
          },
          "type": "array"
        },
        "jwt_expiry_seconds": {
          "default": 86400,
          "description": "JWT token expiry duration in seconds\nDefault: 86400 (24 hours)",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        },
        "jwt_signing_secret": {
          "description": "JWT signing secret for ingress authentication (base64-encoded, minimum 32 bytes)\nGenerate with: openssl rand -base64 32\nRequired for ingress authentication",
          "type": "string"
        },
        "oauth_rate_limit": {
          "$ref": "#/$defs/OAuthRateLimitSettings",
          "description": "OAuth endpoint rate limiting configuration."
        },
        "port": {
          "format": "uint16",
          "maximum": 65535,
          "minimum": 0,
          "type": "integer"
        },
        "public_url": {
          "type": "string"
        },
        "rs256_private_key_pem": {
          "default": null,
          "description": "Optional RS256 private key in PEM format for JWT signing\nIf not provided, a new key pair will be generated on startup (tokens will be invalidated on restart)\nTo persist keys across restarts, generate with: openssl genrsa -out rs256.key 2048",
          "type": [
            "string",
            "null"
          ]
        },
        "rs256_public_key_pem": {
          "default": null,
          "description": "Optional RS256 public key in PEM format for JWT verification\nIf not provided, will be derived from rs256_private_key_pem or generated\nGenerate from private key with: openssl rsa -in rs256.key -pubout -out rs256.pub",
          "type": [
            "string",
            "null"
          ]
        },
        "ssrf": {
          "$ref": "#/$defs/SsrfConfig",
          "description": "SSRF validation configuration."
        },
        "static_dir": {
          "default": null,
          "description": "Directory containing static assets (Tera templates, SVGs, Vite build output).\nDefaults to the RISE_STATIC_DIR environment variable.",
          "type": [
            "string",
            "null"
          ]
        },
        "workload_token_max_ttl_seconds": {
          "default": 900,
          "description": "Maximum TTL in seconds for workload identity tokens issued via the token-exchange endpoint.\nRequests that specify a higher TTL are silently capped to this value.\nDefault: 900 (15 minutes).",
          "format": "uint64",
          "minimum": 0,
          "type": "integer"
        }
      },
      "required": [
        "host",
        "port",
        "public_url",
        "jwt_signing_secret"
      ],
      "type": "object"
    },
    "SsrfConfig": {
      "description": "SSRF validation configuration.\n\nControls how Rise validates URLs before making server-side requests.\nAll fields default to the most restrictive settings (HTTPS required,\nprivate networks blocked, no trusted hosts).",
      "properties": {
        "allow_http": {
          "default": false,
          "description": "Allow HTTP (non-TLS) URLs in SSRF-validated requests.\nWARNING: Only enable for local development. Never enable in production.",
          "type": "boolean"
        },
        "allow_private_networks": {
          "default": false,
          "description": "Allow private/loopback IPs in SSRF-validated URLs.\nWARNING: Only enable for local development. Never enable in production.",
          "type": "boolean"
        },
        "trusted_hosts": {
          "default": [],
          "description": "Hostnames that are allowed to resolve to private/internal IP addresses.\nUse this to permit SSRF-validated requests to trusted internal services\n(e.g., an internal Keycloak or OIDC provider) without enabling\n`allow_private_networks`.",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.IPBlock": {
      "description": "IPBlock describes a particular CIDR (Ex. \"192.168.1.0/24\",\"2001:db8::/64\") that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The except entry describes CIDRs that should not be included within this rule.",
      "properties": {
        "cidr": {
          "description": "cidr is a string representing the IPBlock Valid examples are \"192.168.1.0/24\" or \"2001:db8::/64\"",
          "type": "string"
        },
        "except": {
          "description": "except is a slice of CIDRs that should not be included within an IPBlock Valid examples are \"192.168.1.0/24\" or \"2001:db8::/64\" Except values will be rejected if they are outside the cidr range",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "cidr"
      ],
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyEgressRule": {
      "description": "NetworkPolicyEgressRule describes a particular set of traffic that is allowed out of pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and to. This type is beta-level in 1.8",
      "properties": {
        "ports": {
          "description": "ports is a list of destination ports for outgoing traffic. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPort"
          },
          "type": "array"
        },
        "to": {
          "description": "to is a list of destinations for outgoing traffic of pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all destinations (traffic not restricted by destination). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the to list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPeer"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyIngressRule": {
      "description": "NetworkPolicyIngressRule describes a particular set of traffic that is allowed to the pods matched by a NetworkPolicySpec's podSelector. The traffic must match both ports and from.",
      "properties": {
        "from": {
          "description": "from is a list of sources which should be able to access the pods selected for this rule. Items in this list are combined using a logical OR operation. If this field is empty or missing, this rule matches all sources (traffic not restricted by source). If this field is present and contains at least one item, this rule allows traffic only if the traffic matches at least one item in the from list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPeer"
          },
          "type": "array"
        },
        "ports": {
          "description": "ports is a list of ports which should be made accessible on the pods selected for this rule. Each item in this list is combined using a logical OR. If this field is empty or missing, this rule matches all ports (traffic not restricted by port). If this field is present and contains at least one item, then this rule allows traffic only if the traffic matches at least one port in the list.",
          "items": {
            "$ref": "#/$defs/io.k8s.api.networking.v1.NetworkPolicyPort"
          },
          "type": "array"
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyPeer": {
      "description": "NetworkPolicyPeer describes a peer to allow traffic to/from. Only certain combinations of fields are allowed",
      "properties": {
        "ipBlock": {
          "$ref": "#/$defs/io.k8s.api.networking.v1.IPBlock",
          "description": "ipBlock defines policy on a particular IPBlock. If this field is set then neither of the other fields can be."
        },
        "namespaceSelector": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
          "description": "namespaceSelector selects namespaces using cluster-scoped labels. This field follows standard label selector semantics; if present but empty, it selects all namespaces.\n\nIf podSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the namespaces selected by namespaceSelector. Otherwise it selects all pods in the namespaces selected by namespaceSelector."
        },
        "podSelector": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector",
          "description": "podSelector is a label selector which selects pods. This field follows standard label selector semantics; if present but empty, it selects all pods.\n\nIf namespaceSelector is also set, then the NetworkPolicyPeer as a whole selects the pods matching podSelector in the Namespaces selected by NamespaceSelector. Otherwise it selects the pods matching podSelector in the policy's own namespace."
        }
      },
      "type": "object"
    },
    "io.k8s.api.networking.v1.NetworkPolicyPort": {
      "description": "NetworkPolicyPort describes a port to allow traffic on",
      "properties": {
        "endPort": {
          "description": "endPort indicates that the range of ports from port to endPort if set, inclusive, should be allowed by the policy. This field cannot be defined if the port field is not defined or if the port field is defined as a named (string) port. The endPort must be equal or greater than port.",
          "format": "int32",
          "type": "integer"
        },
        "port": {
          "$ref": "#/$defs/io.k8s.apimachinery.pkg.util.intstr.IntOrString",
          "description": "port represents the port on the given protocol. This can either be a numerical or named port on a pod. If this field is not provided, this matches all port names and numbers. If present, only traffic on the specified protocol AND port will be matched."
        },
        "protocol": {
          "description": "protocol represents the protocol (TCP, UDP, or SCTP) which traffic must match. If not specified, this field defaults to TCP.",
          "type": "string"
        }
      },
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector": {
      "description": "A label selector is a label query over a set of resources. The result of matchLabels and matchExpressions are ANDed. An empty label selector matches all objects. A null label selector matches no objects.",
      "properties": {
        "matchExpressions": {
          "description": "matchExpressions is a list of label selector requirements. The requirements are ANDed.",
          "items": {
            "$ref": "#/$defs/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement"
          },
          "type": "array"
        },
        "matchLabels": {
          "additionalProperties": {
            "type": "string"
          },
          "description": "matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels map is equivalent to an element of matchExpressions, whose key field is \"key\", the operator is \"In\", and the values array contains only \"value\". The requirements are ANDed.",
          "type": "object"
        }
      },
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelectorRequirement": {
      "description": "A label selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
      "properties": {
        "key": {
          "description": "key is the label key that the selector applies to.",
          "type": "string"
        },
        "operator": {
          "description": "operator represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists and DoesNotExist.",
          "type": "string"
        },
        "values": {
          "description": "values is an array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. This array is replaced during a strategic merge patch.",
          "items": {
            "type": "string"
          },
          "type": "array"
        }
      },
      "required": [
        "key",
        "operator"
      ],
      "type": "object"
    },
    "io.k8s.apimachinery.pkg.util.intstr.IntOrString": {
      "description": "IntOrString is a type that can hold an int32 or a string.  When used in JSON or YAML marshalling and unmarshalling, it produces or consumes the inner type.  This allows you to have, for example, a JSON field that can accept a name or number.",
      "x-kubernetes-int-or-string": true
    }
  },
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "properties": {
    "auth": {
      "$ref": "#/$defs/AuthSettings"
    },
    "database": {
      "$ref": "#/$defs/DatabaseSettings"
    },
    "default_organization": {
      "$ref": "#/$defs/DefaultOrganizationSettings",
      "description": "Default Organization that owns existing typed data (users, teams,\nprojects) until org-aware multi-tenancy lands. Bootstrap creates this\nOrganization (under an advisory lock) before any backfill runs, so it\nis guaranteed to exist by the time controllers begin processing\nprojects. Omitting the section accepts the documented defaults:\nname=\"default\", display name=\"Default\", no namespace prefix\nconfigured — the controller then synthesizes `org-{discriminator}-`\nas the per-Org namespace prefix. Set\n`default_organization.kubernetes_namespace_prefix` explicitly (e.g.\n`\"rise-\"`) to preserve historic naming on existing installs."
    },
    "deployment_controller": {
      "anyOf": [
        {
          "$ref": "#/$defs/DeploymentControllerSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "deployment_logs": {
      "$ref": "#/$defs/DeploymentLogsSettings"
    },
    "encryption": {
      "anyOf": [
        {
          "$ref": "#/$defs/EncryptionSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "extensions": {
      "anyOf": [
        {
          "$ref": "#/$defs/ExtensionsSettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "quickstart": {
      "anyOf": [
        {
          "$ref": "#/$defs/QuickstartSettings"
        },
        {
          "type": "null"
        }
      ],
      "description": "Curated catalog of stateless container images surfaced as one-click\n\"quickstart\" deploys in the UI. The catalog is layered: `default.yaml`\nships the built-in selection and any `quickstart.templates` list in a\nlater layer (run-mode or `local.yaml`) *replaces* it entirely. Operators\nwho want to add to the defaults must re-include them."
    },
    "registry": {
      "anyOf": [
        {
          "$ref": "#/$defs/RegistrySettings"
        },
        {
          "type": "null"
        }
      ]
    },
    "resource_gc": {
      "anyOf": [
        {
          "$ref": "#/$defs/ResourceGcSettings"
        },
        {
          "type": "null"
        }
      ],
      "description": "Background garbage-collection worker for the generic resource store.\nDrains tombstoned resource rows once their controller finalizers clear.\nOmitting the section keeps the worker running with the documented defaults;\nthe worker is essential to draining cascading deletes and is therefore\nalways on when the `backend` feature is enabled."
    },
    "server": {
      "$ref": "#/$defs/ServerSettings"
    }
  },
  "required": [
    "server",
    "auth"
  ],
  "title": "Settings",
  "type": "object"
}
