How an infrastructure Template exposes the things its instances can configure —
container images, versions, sizes, ports — and where platform-global values come
from. The guiding rule:
Configurable inputs are
spec.schemafields with sane defaults. They are never injected via${kedge.*}environment-substitution tokens.
A template must produce a valid workload out of the box, with no deployment-time env required. A missing env variable must never be able to bake an empty or invalid field into a materialized resource.
| Kind | How to express it | Example |
|---|---|---|
| Per-instance, configurable (image, version, size, replicas) | spec.schema field with a default; the resource references ${schema.spec.<field>} |
simple-webapp.spec.port (default: 8080); database.spec.version |
| Fixed sidecar / tooling image (not user-facing) | hardcoded literal in the resource | the control-token bitnami/kubectl job (database, redis, application); quay.io/oauth2-proxy/oauth2-proxy:v7.6.0 |
| Platform-global, no universal default | a reserved ${kedge.*} substitution token, resolved by the kro backend from env |
the exposure Gateway parent ${kedge.gatewayName} / ${kedge.gatewayNamespace}; the dev-overlay images ${kedge.devImage.<toolchain>} / ${kedge.devAgentImage}; the exposure-URL port suffix ${kedge.appPublicPort} (empty in prod, :10443 on local kind) |
They were tried for the sandbox runner and removed. The failure modes:
- Empty → invalid. An unset
KEDGE_SANDBOX_RUNNER_IMAGEsubstitutes to"", and the kro backend bakesimage: ""into the Deployment/Job — which the API server rejects (spec.template.spec.containers[0].image: Required value). A schemadefaultcannot be empty. - Substitution-type traps. A token substitutes a string into the JSON, so
an integer field (
backendRefs[].port) becomes"8080"and kro rejects the graph (expected integer type … got string); and a value meant to be a CEL expression (includeWhen) becomes a bare literal kro won't accept. Schema refs (${schema.spec.…}) carry their type from the schema and dodge all of this. - Inconsistency. Every other template uses schema fields + hardcoded sidecar images; an env-token outlier is one more thing to wire (chart env, operator passthrough, dev Makefile) and one more thing to forget.
${kedge.*} tokens are reserved for the handful of genuinely platform-wide
values that have no sane universal default and are referenced identically across
apps: the exposure Gateway (${kedge.gatewayName} / ${kedge.gatewayNamespace},
every HTTPRoute), the dev-overlay images (${kedge.devImage.<toolchain>} /
${kedge.devAgentImage}, injected only in development mode), and the exposure-URL
port suffix (${kedge.appPublicPort}, empty in production). All of these are
platform config, never per-tenant inputs — which is exactly why they are env
tokens rather than schema fields. A per-instance value (an app's own image,
version, size) is never a ${kedge.*} token.
- Each container image is either a
spec.schemafield with a sanedefault(user-overridable) or a hardcoded literal (fixed tooling). Never an env token. - Integer/boolean fields are schema fields (so their type is carried), not string substitutions.
- The template renders a valid graph with zero deployment env set —
verify with the
backend/kroseed-template tests (buildRGD+ “no unsubstituted${kedge.*}”), and against a real kro cluster (GraphAccepted=True). - A per-deployment platform value with no universal default? Reconsider —
if it truly has none, raise it for a new reserved
${kedge.*}token rather than reaching for an env override in one template.