Skip to content
RunDeploy

Local cluster

manifests render --env local produces the same Kubernetes shape the GitOps path deploys (workloads, Dapr annotations, Dapr Components) as one YAML stream on stdout, for a cluster on your own machine.

Start a local Kubernetes cluster and configure kubectl to use it before rendering. On Windows, enable Kubernetes in Docker Desktop. Run the commands from the same environment as the CLI. On Windows, use the same WSL distribution for intropy, kubectl, kustomize, and dapr, with Docker Desktop WSL 2 integration enabled. Keep the System workspace under ~/, not /mnt/c, so scaffolded temporary files can receive Unix permissions:

Terminal window
kubectl config current-context
kubectl get nodes

Continue when the current context is the local cluster you intend to use and its node is Ready.

Intropy runs the standalone kustomize executable. The Kustomize version bundled with kubectl is not used, so install kustomize separately and ensure it is on PATH. The Kustomize installation instructions include other platform options.

Terminal window
curl -s "https://raw.githubusercontent.com/kubernetes-sigs/kustomize/master/hack/install_kustomize.sh" | bash
sudo mv kustomize /usr/local/bin/
kustomize version

Continue when kustomize version prints a version.

Install the Dapr CLI in the same environment as the Intropy CLI. The CLI alone is not sufficient: install Dapr into the Kubernetes cluster as well. This adds the Dapr control plane and CRDs that the rendered Dapr Components require:

Terminal window
dapr init -k
dapr status -k

Continue when dapr status -k reports the Dapr control-plane services as running. Install the fixture services your System needs before applying the manifests, such as a RabbitMQ broker for pub/sub.

Rendering does not create a namespace. By default, resources render into a namespace named after the System. Create it before applying the manifests. For the order-flow example:

Terminal window
kubectl create namespace order-flow

For a different System, replace order-flow with its name. To deploy into an existing namespace instead, pass --namespace to the render command.

Terminal window
intropy manifests render --env local | kubectl apply -f -

Run it from the workspace that holds the System host (--system selects one in a multi-system workspace). Only YAML is written to stdout; progress, prompts, warnings, and errors go to stderr, and the complete render is buffered and validated before stdout is written, so a failed render cannot apply an incomplete prefix through the pipe. The command does not inspect the cluster.

A fixture is the local stand-in for the external system behind a Port. Choose the fixture that matches the Port’s adapter, not the deployment target.

Port adapter Local fixture Use when
File file The Component reads from or writes to a folder inside its own pod (an emptyDir mounted into the Dapr sidecar). Nothing outside the pod can reach it.
File nfs The Port is an NFS file share. The sidecar mounts the dev environment’s NFS export, and each Port is the directory /<port> on it.
SFTP sftp The Port connects to an SFTP server.
HTTP http The Port calls or receives from an HTTP endpoint.
Blob storage blob The Port reads from or writes to object storage.

The order-flow example uses file adapters for both ports. Select file for order-extractor-source and order-loader-destination; the local fixture maps each Port to a folder.

A topology port is a name; locally, each name must be told which fixture to resolve to. Without that, the render refuses:

error: local bindings are required for ports: order-extractor-source, order-loader-destination
pass one '--binding <port>=<fixture>' for each; available fixtures: sftp, http, file, nfs, blob

Pass one --binding <port>=<fixture> per port for a reproducible, non-interactive render:

Terminal window
intropy manifests render --env local \
--binding order-extractor-source=file \
--binding order-loader-destination=file | kubectl apply -f -

In an interactive terminal a selector asks for any missing choice instead; the selection applies to that render only and is never persisted. There is no saved binding state: the bindings are an argument to the render.

For the order-flow example (one extractor, one loader, both bound to file), the stream contains the workloads the deployment model implies and the Dapr Components that wire them:

  • A Deployment for the loader, with Dapr sidecar annotations (dapr.io/enabled, dapr.io/app-id: order-loader, dapr.io/app-port: "8080") and an OTLP exporter endpoint in its environment.

  • A CronJob for the extractor (extractors wake, pull, publish, and exit) with concurrencyPolicy: Forbid and its own Dapr annotations.

  • A Dapr Component per port, scoped to the component that uses it. The file fixture becomes bindings.localstorage with a rootPath under /data/:

    apiVersion: dapr.io/v1alpha1
    kind: Component
    metadata:
    labels:
    intropy.io/port: order-extractor-source
    name: order-extractor-source
    namespace: order-flow
    scopes:
    - order-extractor
    spec:
    metadata:
    - name: rootPath
    value: /data/order-extractor-source
    type: bindings.localstorage
    version: v1
  • A pub/sub Component shared by publisher and subscriber, pointing at the local fixture broker.

Everything lands in a namespace named after the System (order-flow here); --namespace changes it. Images default to the local convention (local/<component>:dev); override one with --image <component>=<name:tag>, or retag every component with --image :<tag>.

This is not the inner loop. For building and debugging a System, the host’s Aspire run is faster and needs no cluster: F5, mocked platform services, drop folders standing in for ports, the dashboard for traces (see Systems and topology).

Render to a local cluster when the deployed shape is the thing under test: that the workload kinds and sidecar annotations are what you expect, that port Components are scoped correctly, that the System behaves on real Kubernetes with a real broker.

Full flag detail: intropy manifests render.