# Self-host Infrastructure

Deployment prerequisites, verification, and recovery for Infrastructure.

Source: https://railgrid.ai/docs/self-hosting/providers/infrastructure/


The infrastructure provider turns templates into running workloads on a runtime cluster through [kro](https://kro.run/), and publishes applications and App Studio previews under a wildcard domain through a Gateway API `Gateway`. Railgrid runs it in **operator mode**: the chart installs a small operator and one `InfrastructureProvider` resource, and the operator installs kro, bootstraps the provider workspace, and manages the serving Deployment.

## Prerequisites

- A Kubernetes runtime you administer, Helm, and permission to onboard this provider. Use the [common provider flow](https://railgrid.ai/docs/self-hosting/providers/) to obtain the workspace kubeconfig.
- A Gateway API `Gateway` whose HTTPS listener accepts routes from **all namespaces**; the provider creates one `HTTPRoute` per published app in each tenant's runtime namespace. See [Gateway API on GKE](https://railgrid.ai/docs/self-hosting/hub/gateway-api/) or [Cloudflare Tunnel](https://railgrid.ai/docs/self-hosting/hub/cloudflare-tunnel/).
- An apps domain with a wildcard DNS record and a wildcard certificate at your edge, for example `*.apps.example.com`. Each app gets `<name>-<tenant hash>.apps.example.com`. Below a Cloudflare zone apex, Universal SSL does not cover a second label; use a zone apex or an Advanced Certificate wildcard.
- `hub.portalFrameSources` on the hub listing `https://*.apps.example.com`, or the console blocks preview iframes.
- A CNI that enforces `NetworkPolicy` if you enable tenant network isolation.

## Install kro once, as cluster-admin

The operator installs kro with `helm upgrade --install`, but kro's `ClusterRole` grants `*/*/*`, and Kubernetes lets a subject create a role only with permissions it already holds. The operator's enumerated role does not hold them, so on a cluster without kro the `InfrastructureProvider` sticks at `KroReleased=False (HelmFailed) ... attempting to grant RBAC permissions not currently held`, the serving Deployment is never created, and the provider never appears in the console.

Install kro once yourself with the operator's exact arguments; the operator's later identical upgrades then leave the role untouched and succeed. Prefer this to `operator.clusterAdmin: true`, which would also bind cluster-admin to the ServiceAccount that handles tenant requests.

```bash
helm upgrade --install kro oci://registry.k8s.io/kro/charts/kro \
  --version 0.9.3 --namespace kro-system --create-namespace --reset-values \
  --set image.repository=registry.k8s.io/kro/kro --wait
```

Keep `--version` equal to `operator.kro.version` below, and repeat this when a new kro version changes its `ClusterRole`. If the operator is already retrying, `another operation is in progress` means you raced it; run the command again.

## Install

```bash
kubectl create namespace railgrid-infrastructure-operator
kubectl --namespace railgrid-infrastructure-operator create secret generic infrastructure-provider-kubeconfig \
  --from-file=kubeconfig=./infrastructure.kubeconfig
```

`infrastructure-values.yaml`:

```yaml
hub:
  url: https://railgrid-hub.railgrid-system.svc.cluster.local:9443
  tokenSecretRef:
    name: ""
operator:
  enabled: true
  providerKubeconfigSecret:
    name: infrastructure-provider-kubeconfig
    key: kubeconfig
  kro:
    version: 0.9.3
    image:
      repository: registry.k8s.io/kro/kro
  application:
    # Zone apps are served under. Required to enable app exposure.
    baseDomain: apps.example.com
    gateway:
      name: railgrid
      namespace: gateway
      # GKE only: a TCP HealthCheckPolicy per route backend. Without it the
      # load balancer's default "GET /" check never passes the access gate.
      backendHealthCheck: gke-tcp
  publishing:
    # Pin the access gate image; it fronts every published app.
    accessProxyImage: ghcr.io/railgrid/railgrid-access-proxy:v0.2.1
    # Browser-reachable hub origin for sign-in redirects.
    hubPublicURL: https://console.example.com
    insecure: true
```

```bash
helm upgrade --install infrastructure oci://ghcr.io/railgrid/charts/railgrid-infrastructure-provider \
  --version CHART_VERSION --namespace railgrid-infrastructure-operator --values infrastructure-values.yaml
```

The operator creates the serving Deployment in its own namespace, `railgrid-infrastructure-provider`, on the runtime cluster, and reports progress on the `InfrastructureProvider` resource.

## Provider requirements

**One Gateway, every namespace.** Published apps are `HTTPRoute`s in tenant namespaces. The Gateway's listener must allow routes from all namespaces, and its wildcard certificate must cover the apps domain.

**The access gate.** Every published app's traffic enters through `railgrid-access-proxy`, which answers `421` to a request whose `Host` is not the app's and redirects private apps to the hub for sign-in. Health checks that expect `200` on `/` therefore fail; on GKE set `backendHealthCheck: gke-tcp` (chart 0.2.34 and later). A freshly published app answers 404 for a few minutes while a cloud load balancer programs its route.

**Runtime kubeconfig.** In operator mode on the hub's cluster, the operator uses its own ServiceAccount for the runtime; set `operator.runtimeKubeconfigSecret` only to target a different cluster. The non-operator chart form takes `centralKro.kubeconfigSecretRef` instead; prefer a Secret reference over inline kubeconfigs in production.

**Tenant isolation.** `tenantLimitRange` stamps default container resources into every tenant namespace (on by default). `tenantNetworkPolicy.enabled` adds an ingress `NetworkPolicy` per tenant namespace; list your Gateway proxies' namespace and the kube-apiserver source range in `allowedNamespaces` / `allowedCIDRs`, or dev-sandbox sync and logs time out.

**Development sandboxes run tenant code.** Pin `development.agentImage` and any `development.images.*` by digest in production, and set `sandbox.runtimeClassName` (`gvisor` or `kata`) before exposing App Studio to untrusted users.

## Verify

```bash
kubectl -n railgrid-infrastructure-operator get infrastructureprovider \
  -o jsonpath='{range .items[*].status.conditions[*]}{.type}={.status}{"\n"}{end}'   # all True
kubectl -n railgrid-infrastructure-provider get pods                                 # the serving Deployment
kubectl -n kro-system get pods
```

A fresh provider stays unready until a workspace enables it; see [the common flow](https://railgrid.ai/docs/self-hosting/providers/#a-fresh-provider-is-unready-until-a-workspace-binds-it). Enable Infrastructure in a test workspace, list the Template catalog, and create one disposable `Instance` using the [Infrastructure quickstart](https://railgrid.ai/docs/use/infrastructure/quickstart/). Then publish one app and open its URL: a redirect to the console sign-in is the expected answer for a private app.

## Recover

| Symptom | Check | Fix |
|---|---|---|
| `KroReleased=False`, `permissions not currently held` | kro not pre-installed | Install kro as cluster-admin (above) |
| No pods in `railgrid-infrastructure-provider` | `InfrastructureProvider` conditions | Fix the failing condition; the operator retries about every 12 seconds |
| Published app: `no healthy upstream` | Load-balancer health check vs the access gate | `backendHealthCheck: gke-tcp`, or an equivalent TCP check |
| Published app: 404 with a valid certificate | Route not yet programmed, or the Gateway rejects routes from the tenant namespace | Wait a few minutes; check `allowedRoutes.namespaces` |
| Published app: TLS failure | No wildcard certificate for the apps domain | Add the wildcard at the edge |
| Preview iframe blocked in the console | Hub `portalFrameSources` | Add `https://*.<apps domain>` |

For a failed upgrade, preserve both hub control-plane resources and runtime objects. Inspect kro and provider events, restore the provider kubeconfig Secret, then verify the Template catalog and one disposable Instance before resuming user workloads.

## Configuration reference

[Chart values](https://github.com/railgrid/railgrid/blob/main/providers/infrastructure/deploy/chart/values.yaml) and the [chart README](https://github.com/railgrid/railgrid/blob/main/providers/infrastructure/deploy/chart/README.md) are authoritative for the deployed version. Keep the deployed image, chart, kro, and kcp versions recorded together: the provider and kcp [move in lockstep](https://railgrid.ai/docs/self-hosting/hub/external-kcp/#versions-move-in-lockstep).
