Skip to main content

Resource Model

Code-Derived Reference

This page is rebuilt from the current CRD types and runtime code in api/..., controllers/..., internal/materializer, internal/render, internal/resolver, internal/routingprovider, and internal/sbclient. It describes what the current code does, including fields that are accepted but not yet acted on by the in-cluster controllers.

SandboxMesh currently ships four CRDs:

CRDAPI versionWhat it does
Sandboxcontrol.sandbox.mesh.dev/v1alpha1Local runtime intent CR. In SaaS deployments, the agent writes it from hosted assignments and the operator materializes it into SandboxGroup and optionally SandboxRoute.
SandboxGroupsandbox.mesh.dev/v1alpha1Freezes source workload snapshots and renders sandbox Deployment and Service objects.
SandboxRouterouting.mesh.dev/v1alpha1Attaches traffic selection using Gateway API, Istio, or the built-in proxy provider.
DevSessioncontrol.sandbox.mesh.dev/v1alpha1Creates a PVC-backed development pod for one sandbox workload and gives the sb CLI a stable target for sync and port-forwarding.

How the resources fit together

  1. In SaaS deployments, the agent usually writes a local Sandbox from a hosted assignment. In low-level runtime workflows, you can also author one yourself for testing.
  2. The Sandbox controller validates it, defaults spec.visibility to private, and materializes a same-name SandboxGroup.
  3. If spec.routing is present, the Sandbox controller also materializes a same-name SandboxRoute.
  4. The SandboxGroup controller resolves source workloads, freezes a snapshot, and renders sandbox Deployment and Service objects.
  5. The SandboxRoute controller resolves component backends and attaches a provider-specific child resource such as HTTPRoute, VirtualService, or a proxy Deployment.
  6. When you start local development with sb up, the CLI creates a DevSession for the target sandbox workload. The operator scales the normal sandbox deployment to 0, brings up a PVC-backed dev deployment, and retargets the sandbox service at it.

Shared Runtime Behavior

  • Supported source kinds are Deployment, StatefulSet, and Argo Rollout.
  • Rendered sandbox workloads are always Kubernetes Deployment objects in the current implementation, even when the source is a StatefulSet or Rollout.
  • A SandboxGroup freezes the first successful source snapshot. After that, the controller reuses status.components[].snapshot until the sourceRef changes or the resource is recreated.
  • Each sandbox group gets a generated sandbox ID with the form sbx-xxxxxxxx. That value is also used as the default routing key.
  • Rendered component names follow fixed conventions:
    • deployment: <sandbox-group>-<component>-sbx
    • service: <sandbox-group>-<component>-svc
  • Routing defaults to the baggage header when no selector header is specified.
  • When the selector header is baggage, providers look for a sandbox=<id> entry inside the header value. For any other header name, providers expect an exact header value match.

Sandbox

Complete Example

apiVersion: control.sandbox.mesh.dev/v1alpha1
kind: Sandbox
metadata:
name: storefront-preview
namespace: storefront
spec:
projectRef:
id: storefront
clusterRef:
name: dev-cluster-usw2
displayName: Storefront Preview
owner:
type: user
id: user_123
displayName: Jane Developer
visibility: private
workloads:
- name: frontend
type: inherit
inherit:
sourceRef:
apiVersion: apps/v1
kind: Deployment
name: storefront
overrides:
replicas: 2
deploymentLabels:
preview: "true"
deploymentAnnotations:
reloader.stakater.com/auto: "true"
templateLabels:
tier: web
templateAnnotations:
sidecar.istio.io/inject: "false"
containers:
- name: app
image: ghcr.io/acme/storefront:pr-421
command: ["npm", "run", "start:preview"]
args: ["--port", "8080"]
env:
- name: API_BASE_URL
value: http://storefront-preview-api-svc:8080
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
podTemplatePatch:
- op: add
path: /spec/nodeSelector
value:
workload-tier: preview
service:
type: ClusterIP
labels:
expose: "true"
annotations:
cloud.google.com/neg: '{"ingress": true}'
ports:
- name: http
port: 8080
targetPort: 8080
- name: api
type: inherit
inherit:
sourceRef:
apiVersion: apps/v1
kind: StatefulSet
name: api
service:
ports:
- name: http
port: 8080
targetPort: 8080
routing:
provider: gateway
key:
headerName: x-sandbox-id
gateway:
parentRefs:
- name: edge
namespace: infra
hostnames:
- preview.example.com
interceptions:
- name: frontend
targetService:
name: storefront
matches:
- path:
type: PathPrefix
value: /
routeTo:
workload: frontend
port: 8080
- name: api
targetService:
name: storefront-api
matches:
- path:
type: PathPrefix
value: /api
routeTo:
workload: api
port: http
delivery:
provider: argocd
environment: shared-dev
lifecycle:
sleepAfterUnused: 30m
deleteAfterUnused: 24h
minReplicas: 1
metadata:
tags:
team: checkout
ticket: ENG-421

Top-Level Fields

FieldRequiredWhat the code does
metadata.nameYesBecomes the materialized SandboxGroup name and SandboxRoute name. Also becomes part of rendered deployment and service names.
metadata.namespaceYesNamespace for the Sandbox, its materialized resources, and any defaulted source or route references.
spec.projectRef.idYesRequired by validation. Copied onto materialized SandboxGroup and SandboxRoute labels as control.sandbox.mesh.dev/project. Also used by Argo CD companion-application templates.
spec.clusterRefNoStored on the CRD, usually written by the hosted agent to record cluster intent. The local Sandbox reconciler does not use it when materializing resources.
spec.displayNameNoUser-facing label only. The in-cluster renderers do not read it.
spec.ownerNoCopied into status.createdBy. Not used for rendering or routing decisions in the in-cluster controller.
spec.visibilityNoDefaults to private when empty. No current in-cluster behavior difference between private and project.
spec.workloads[]YesRequired and must be non-empty. Materializes into SandboxGroup.spec.components[]. Workload names must be unique.
spec.routingNoWhen present, materializes a SandboxRoute. When absent, no route resource is created.
spec.deliveryNoControls optional GitOps/Argo CD integration. Empty provider means direct, which skips companion Argo application management.
spec.lifecycleNoAccepted and stored, but not currently enforced by the in-cluster controllers.
spec.metadata.tagsNoAccepted and stored, but not currently used by the in-cluster controllers.
Accepted Versus Acted On

spec.clusterRef, spec.visibility, spec.lifecycle, and spec.metadata are part of the public shape, but the local Sandbox, SandboxGroup, and SandboxRoute reconcilers do not currently change runtime behavior based on them. They are primarily useful to the hosted control plane, the agent-written runtime contract, or future lifecycle logic.

spec.workloads[]

Only one workload type is currently supported: type: inherit.

FieldRequiredWhat the code does
nameYesRequired, unique per sandbox, and used as the SandboxGroup component name. Routing references this name from routeTo.workload.
typeYesMust be inherit. Any other value is rejected by validation.
inheritWhen type=inheritRequired for the current implementation. Defines how to clone the source workload.

spec.workloads[].inherit

FieldRequiredWhat the code does
sourceRef.apiVersionYesRequired. Parsed into a GVK and resolved against the supported source kinds.
sourceRef.kindYesRequired. Must currently be Deployment, StatefulSet, or Argo Rollout.
sourceRef.nameYesRequired. Name of the source workload to clone.
sourceRef.namespaceNoDefaults to the sandbox namespace during source resolution.
overrides.replicasNoOverrides the source replica count for the rendered sandbox deployment.
overrides.deploymentLabelsNoMerged onto the rendered Deployment labels.
overrides.deploymentAnnotationsNoReplaces and manages the rendered Deployment annotations.
overrides.templateLabelsNoMerged onto the rendered pod template labels.
overrides.templateAnnotationsNoMerged onto the rendered pod template annotations.
overrides.containers[]NoApplied by container name after the source template is resolved. A missing source container name causes rendering to fail.
overrides.containers[].imageNoReplaces the source container image.
overrides.containers[].commandNoReplaces the source container command when provided.
overrides.containers[].argsNoReplaces the source container args when provided.
overrides.containers[].env[]NoMerged by env var name onto the source container env list.
overrides.containers[].resourcesNoReplaces the source container resource requirements.
podTemplatePatch[]NoRFC 6902 JSON patch operations applied after container overrides. Invalid patches fail rendering.
serviceNoOverrides the service rendered for this component. If omitted, the controller infers ports from the source pod template.

spec.workloads[].inherit.service

service.ports[] is a native Kubernetes ServicePort list.

FieldRequiredWhat the code does
typeNoDefaults to ClusterIP when empty.
labelsNoMerged onto the rendered service labels.
annotationsNoCopied to the rendered service annotations.
ports[]NoIf provided, these ports replace inferred source ports. If omitted, the controller uses the frozen snapshot ports from the source workload.

Service port handling has a few runtime defaults:

  • if a rendered port omits targetPort, SandboxMesh sets it to the same numeric value as port
  • if a rendered port omits protocol, SandboxMesh defaults it to TCP
  • if a rendered port omits name, SandboxMesh uses port-<number>
  • if no ports can be inferred and none are provided, the component fails to render

spec.routing

The Sandbox CRD exposes a simpler routing model than authoring SandboxRoute directly. Each interception becomes exactly one route rule with exactly one component backend.

FieldRequiredWhat the code does
providerYes, when routing is setMust be gateway, istio, or proxy. Empty is rejected.
key.headerNameNoDefaults to baggage. Controls which header the materialized route provider matches on.
gatewayWhen provider=gatewayRequired for the gateway provider. Copied through to SandboxRoute.spec.gateway.
istioWhen provider=istioRequired for the Istio provider. Copied through to SandboxRoute.spec.istio.
interceptions[]Yes, when routing is setRequired and materialized into SandboxRoute.spec.rules[].

Provider validation enforced by the controller:

  • provider: gateway requires gateway.parentRefs and forbids istio
  • provider: istio requires istio.gateways and istio.hosts, and forbids gateway
  • provider: proxy forbids both gateway and istio

spec.routing.interceptions[]

FieldRequiredWhat the code does
nameNoCopied to the generated route rule name.
targetService.nameYesRequired. The live service being intercepted.
targetService.namespaceNoDefaults to the sandbox namespace when materialized.
matches[]NoHTTP match list. Empty means catch-all. Each match entry is ORed; fields inside a single match are ANDed.
matches[].path.typeNoPassed through to the routing provider. Prefix behavior is the practical default when omitted.
matches[].path.valueNoThe matched path or regex. Also used to derive status.endpoints[].path on the parent Sandbox.
matches[].methodNoExact HTTP method match.
matches[].headers[]NoHeader matches. Header match type defaults to exact unless the provider applies its own baggage regex behavior for the selector header.
matches[].queryParams[]NoQuery parameter matches. Type defaults to exact.
routeTo.workloadYesRequired. Must name an existing sandbox workload.
routeTo.portYesRequired. Can be a number or a named component service port.

spec.delivery

FieldRequiredWhat the code does
providerNoEmpty or direct means the operator only materializes SandboxGroup and SandboxRoute. argocd additionally creates or updates a companion Argo Application for the Sandbox itself.
environmentNoSelects an Argo CD environment overlay from operator config when provider=argocd. Ignored for direct delivery.

Important Argo delivery behavior:

  • provider=argocd requires the operator to have Argo companion applications enabled in SANDBOX_MESH_DELIVERY_CONFIG
  • Argo mode still materializes SandboxGroup and SandboxRoute directly from the live Sandbox
  • the companion Application points at the configured Git source for the Sandbox manifest, not at rendered child resources
  • when using direct delivery, any previously managed Argo applications for the sandbox are deleted

status

These fields are controller-owned.

FieldWhat the code does
status.phaseThe local controller currently sets Materializing, Ready, Degraded, Failed, and Deleting. The enum also includes Pending, PendingCapacity, Sleeping, and Waking for shared/hosted flows, but the local controller does not emit those today.
status.observedGenerationLast generation processed by the Sandbox controller.
status.assignedClusterRefPart of the CRD shape for higher-level control-plane flows. The local Sandbox controller does not currently populate it.
status.leaseRefPart of the CRD shape for higher-level control-plane flows. The local Sandbox controller does not currently populate it.
status.routingKey.headerNameSet when the materialized SandboxGroup publishes a sandbox ID. Mirrors the effective selector header name.
status.routingKey.valueThe effective sandbox ID used for routing.
status.activity.lastActiveTimePresent in the API shape, but not set by the local Sandbox controller today.
status.endpoints[]Derived from routing hostnames plus each interception path. Only populated for gateway and Istio routes. Proxy routes do not produce endpoint URLs.
status.underlyingRefs.sandboxGroupNameName of the materialized SandboxGroup.
status.underlyingRefs.sandboxRouteNameName of the materialized SandboxRoute, if routing is enabled.
status.createdByCopy of spec.owner when owner data is present.
status.conditions[]Uses Applied, Rendered, Attached, and Ready with reasons like InvalidSpec, ResourcesPending, ApplySucceeded, and RouteAttached.

SandboxGroup

Complete Example

apiVersion: sandbox.mesh.dev/v1alpha1
kind: SandboxGroup
metadata:
name: storefront-preview
namespace: storefront
spec:
components:
- name: frontend
sourceRef:
apiVersion: apps/v1
kind: Deployment
name: storefront
overrides:
replicas: 2
deploymentLabels:
preview: "true"
deploymentAnnotations:
reloader.stakater.com/auto: "true"
templateLabels:
tier: web
templateAnnotations:
sidecar.istio.io/inject: "false"
containers:
- name: app
image: ghcr.io/acme/storefront:pr-421
command: ["npm", "run", "start:preview"]
args: ["--port", "8080"]
env:
- name: API_BASE_URL
value: http://storefront-preview-api-svc:8080
podTemplatePatch:
- op: add
path: /spec/nodeSelector
value:
workload-tier: preview
service:
type: ClusterIP
labels:
expose: "true"
annotations:
cloud.google.com/neg: '{"ingress": true}'
ports:
- name: http
port: 8080
targetPort: 8080
- name: api
sourceRef:
apiVersion: argoproj.io/v1alpha1
kind: Rollout
name: api
service:
ports:
- name: http
port: 8080
targetPort: 8080

spec.components[]

SandboxGroup is the lower-level form of a materialized sandbox workload. Its fields map almost one-for-one to the materialized output of Sandbox.spec.workloads[].

FieldRequiredWhat the code does
nameYesRequired component key. Used in rendered deployment and service names.
sourceRefYesDefines the workload to clone. Supports Deployment, StatefulSet, and Argo Rollout. Namespace defaults to the group namespace if omitted.
overridesNoSame semantics as Sandbox.spec.workloads[].inherit.overrides. Applied before JSON patching.
podTemplatePatch[]NoJSON patch list applied to the resolved pod template after container overrides.
serviceNoControls the rendered service. If omitted, ports come from the frozen source snapshot.

status

FieldWhat the code does
status.sandboxIDGenerated once, then reused. Also becomes the routing key value and the sandbox.mesh.dev/id label on rendered resources.
status.observedGenerationLast generation processed by the SandboxGroup controller.
status.components[].nameComponent name this status row belongs to.
status.components[].sourceRefDigestHash of the source reference plus namespace context. Used to decide whether an existing frozen snapshot can be reused.
status.components[].deploymentNameActual rendered deployment name, usually <group>-<component>-sbx.
status.components[].serviceNameActual rendered service name, usually <group>-<component>-svc.
status.components[].servicePorts[]Numeric service ports from the rendered service.
status.components[].readyTrue when the rendered deployment is readable and all replicas are ready.
status.components[].snapshot.replicasFrozen source replica count captured at first successful resolution.
status.components[].snapshot.templateFull frozen PodTemplateSpec captured from the source workload. This is why later source workload edits do not automatically change an existing sandbox.
status.components[].snapshot.servicePorts[]Frozen inferred service ports from the source template.
status.components[].conditions[]Uses SourceResolved, Rendered, Applied, and Ready.
status.conditions[]Group-wide rollup of component conditions.

Direct-Authoring Notes

  • If you edit the source workload after the first successful reconcile, the sandbox stays pinned to the saved snapshot.
  • To resnapshot from source, change the component sourceRef or recreate the SandboxGroup.
  • If a source template has no container ports and you do not provide service.ports, the component fails to render.

SandboxRoute

Complete Gateway Example

apiVersion: routing.mesh.dev/v1alpha1
kind: SandboxRoute
metadata:
name: storefront-preview
namespace: storefront
spec:
sandboxGroupRef:
name: storefront-preview
provider: gateway
gateway:
parentRefs:
- name: edge
namespace: infra
hostnames:
- preview.example.com
rules:
- name: storefront
matches:
- path:
type: PathPrefix
value: /
backends:
- component:
name: frontend
port: http
weight: 90
- service:
name: canary-frontend
namespace: storefront
port: 8080
weight: 10

Complete Istio Example

apiVersion: routing.mesh.dev/v1alpha1
kind: SandboxRoute
metadata:
name: storefront-preview-istio
namespace: storefront
spec:
sandboxGroupRef:
name: storefront-preview
provider: istio
selectorHeaderName: x-sandbox-id
istio:
gateways:
- istio-system/public
hosts:
- preview.example.com
rules:
- name: api
matches:
- path:
type: PathPrefix
value: /api
method: GET
backends:
- component:
name: api
port: http

Complete Proxy Example

apiVersion: routing.mesh.dev/v1alpha1
kind: SandboxRoute
metadata:
name: storefront-preview-proxy
namespace: storefront
spec:
sandboxGroupRef:
name: storefront-preview
provider: proxy
selectorHeaderName: baggage
rules:
- name: storefront
targetServiceRef:
name: storefront
matches:
- path:
type: PathPrefix
value: /
backends:
- component:
name: frontend
port: http

Top-Level Fields

FieldRequiredWhat the code does
metadata.nameYesName of the route and, for gateway or Istio, usually the name of the provider-owned child resource too.
metadata.namespaceYesNamespace used for defaulting component refs, explicit service refs, and provider-owned resources.
spec.sandboxGroupRefNoNeeded when you route to component backends. The controller reads component status from the referenced SandboxGroup to resolve service names and named ports.
spec.providerYesMust be gateway, istio, or proxy. Empty is rejected. Unsupported providers also fail status.
spec.selectorHeaderNameNoDefaults to baggage. baggage uses regex matching for sandbox=<id> entries. Any other header name uses exact value matching.
spec.gatewayWhen provider=gatewayRequired for the gateway provider. Converted into a Kubernetes HTTPRoute.
spec.istioWhen provider=istioRequired for the Istio provider. Converted into an Istio VirtualService.
spec.rules[]YesRequired. Each rule must define at least one backend.

spec.rules[]

FieldRequiredWhat the code does
nameNoOptional rule label. Used directly as the Istio HTTP route name and in error messages.
targetServiceRefRequired for provider=proxyRequired only for proxy routing. Identifies the live service whose selector will be redirected through the proxy deployment.
matches[]NoMatch list. Empty means catch-all. Entries are ORed. Within a single entry, path, method, headers, and query params are ANDed.
matches[].path.typeNoExact, PathPrefix, or RegularExpression. Gateway and Istio default to prefix semantics when omitted.
matches[].path.valueNoPath prefix, exact path, or regex depending on type.
matches[].methodNoExact HTTP method match.
matches[].headers[].nameYesHTTP header name to test.
matches[].headers[].valueYesExact or regex value depending on type.
matches[].headers[].typeNoDefaults to exact.
matches[].queryParams[].nameYesQuery parameter name to test.
matches[].queryParams[].valueYesExact or regex value depending on type.
matches[].queryParams[].typeNoDefaults to exact.
backends[]YesRequired. Each backend must set exactly one of component or service.
backends[].weightNoDefaults to 1. Used by all providers when more than one backend is present.
backends[].component.nameYes, for component backendsMust name a component in the referenced SandboxGroup.
backends[].component.portYes, for component backendsCan be numeric or named. Named ports resolve from the component snapshot service ports.
backends[].service.nameYes, for service backendsExplicit service backend without consulting a sandbox group.
backends[].service.namespaceNoDefaults to the route namespace.
backends[].service.portYes, for service backendsMust currently be numeric. Named service backend ports are not supported by the controller.

Provider-Specific Behavior

  • gateway
    • validates that spec.gateway.parentRefs is present
    • creates a Gateway API HTTPRoute
    • injects the selector header match into every rule before writing the route
  • istio
    • validates that spec.istio.gateways and spec.istio.hosts are present
    • creates an Istio VirtualService
    • lowercases matched header names when generating Istio header matches
  • proxy
    • requires every rule to set targetServiceRef
    • requires targetServiceRef.namespace to match the route namespace
    • creates a shadow baseline service plus a proxy deployment and service
    • rewrites the live target service selector so matching traffic flows through the proxy

status

FieldWhat the code does
status.sandboxIDCopied from the referenced SandboxGroup when one is present. If no group is referenced, the controller generates a new sandbox ID for the route.
status.observedGenerationLast generation processed by the SandboxRoute controller.
status.providerThe provider that actually reconciled the route.
status.attachedResourceReference to the provider-owned child resource such as HTTPRoute, VirtualService, or proxy Deployment.
status.conditions[]Uses Attached with reasons like InvalidSpec, ProviderUnavailable, ResolveFailed, AttachFailed, OwnershipConflict, and RouteAttached.

DevSession

Complete Example

apiVersion: control.sandbox.mesh.dev/v1alpha1
kind: DevSession
metadata:
name: storefront-preview-frontend-dev
namespace: storefront
spec:
sandboxRef:
name: storefront-preview
workload: frontend
targetPath: /app
containerName: app
image: ghcr.io/acme/storefront:dev
storage:
size: 10Gi
storageClassName: fast-ssd
accessModes:
- ReadWriteOnce

Top-Level Fields

FieldRequiredWhat the code does
metadata.nameYesSession name used by the CLI and by the operator-owned PVC and development deployment.
metadata.namespaceYesNamespace used for defaulting sandboxRef.namespace and for creating the dev deployment and PVC.
spec.sandboxRef.nameYesRequired. Names the parent Sandbox.
spec.sandboxRef.namespaceNoDefaults to the DevSession namespace.
spec.workloadYesRequired. Must match a workload name in the target sandbox.
spec.targetPathYesAbsolute path inside the selected container where the PVC-backed workspace is mounted.
spec.containerNameNoIf set, selects which app container receives the workspace mount and any optional image override. Defaults to the first container.
spec.imageNoOptional image override for the target container in the dev deployment.
spec.storageNoOptional PVC settings for the workspace volume.

Runtime Behavior

  • The controller resolves the sandbox and its materialized SandboxGroup.
  • The normal sandbox deployment for the selected workload is still the source of truth for the pod template.
  • A dedicated PVC-backed development deployment named <devsession>-dev is created.
  • A copy-up init container seeds the PVC from the image's existing targetPath contents when the volume is empty.
  • While a DevSession exists for a workload, the SandboxGroup controller scales the normal sandbox deployment to 0 and retargets the sandbox service at the dev deployment.
  • The sb up CLI creates or updates this resource, waits for it to become ready, then syncs a local directory into spec.targetPath.

status

FieldWhat the code does
status.observedGenerationLast generation processed by the DevSession controller.
status.phasePending, Ready, Failed, or Deleting.
status.deploymentNameName of the operator-managed dev deployment, usually <session>-dev.
status.pvcNameName of the operator-managed workspace PVC, usually <session>-workspace.
status.serviceNameSandbox service that now points at the dev deployment while the session is active.
status.containerNameEffective container selected for the workspace mount.
status.ports[]Service ports exposed by the workload for CLI port-forwarding.
status.conditions[]Uses Resolved and Ready. Ready=True means the dev deployment is serving traffic.

What To Author Directly

  • Use Sandbox when you want the higher-level API and are happy with one-backend-per-interception routing.
  • Use SandboxGroup directly when you want fixed frozen workload clones without the higher-level Sandbox API.
  • Use SandboxRoute directly when you need routing features the Sandbox CRD does not expose, such as multiple weighted backends in a single rule.
  • Use DevSession when you want a PVC-backed development pod for a specific sandbox workload and you need the sandbox service to shift over to that dev pod while you iterate locally.