k3s integration

यह कंटेंट अभी तक आपकी भाषा में उपलब्ध नहीं है।

⭐ Community Toolkit k3s logo

This article is the reference for the Aspire k3s Hosting integration. It enumerates the AppHost APIs — with examples for both AppHost.cs and apphost.mts — that you use to run a lightweight k3s Kubernetes cluster as part of your local development inner loop. The cluster, Helm chart installs, manifest applies, and service endpoint exposures all appear as first-class resources in the Aspire dashboard — no external tooling beyond a supported container runtime is required.

A container runtime that supports privileged Linux containers:

  • Docker Engine 20.10+ (Linux) or Docker Desktop (macOS / Windows)
  • Podman 4.0+ (Linux, rootful only — rootless requires cgroup v2 delegation)

To verify Docker Desktop is configured for privileged containers, run:

Terminal window
docker run --rm --privileged alpine echo "Privileged containers supported"

If this command fails with a permission error, you may need to enable privileged mode in Docker Desktop settings or configure your Linux runtime for rootful operation.

To start building an Aspire app that uses k3s, install the 📦 CommunityToolkit.Aspire.Hosting.K3s NuGet package:

Terminal
aspire add communitytoolkit-k3s

Learn more about aspire add in the command reference.

This updates your aspire.config.json with the k3s hosting integration package:

aspire.config.json
{
"packages": {
"CommunityToolkit.Aspire.Hosting.K3s": "*"
}
}

The TypeScript AppHost bindings for k3s are provided by the Community Toolkit package. After installation, the .aspire/modules/aspire.mjs file in your project includes addK3sCluster and related APIs.

Once you’ve installed the hosting integration in your AppHost project, add a k3s cluster and reference it from a project to have KUBECONFIG injected automatically.

apphost.mts
import { createBuilder } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cluster = await builder.addK3sCluster('k8s');
await builder
.addProject('operator', '../MyOperator/MyOperator.csproj')
.withReference(cluster); // injects KUBECONFIG automatically
await builder.build().run();

All cluster options are available as fluent builder methods:

apphost.mts
import { createBuilder, ContainerLifetime } from './.aspire/modules/aspire.mjs';
const builder = await createBuilder();
const cluster = await builder
.addK3sCluster('k8s', { apiServerPort: 16443, agentCount: 2 })
.withK3sVersion('v1.32.3-k3s1')
.withPodSubnet('10.42.0.0/16')
.withServiceSubnet('10.43.0.0/16')
.withDisabledComponent('traefik')
.withExtraArg('--write-kubeconfig-mode=644')
.withDataVolume({ name: 'k8s-data' })
.withHelmImage({ tag: '3.18.0' })
.withKubectlImage({ tag: '1.37.0' })
.withLifetime(ContainerLifetime.Persistent);

The available options are:

MethodParameter / Effect
AddK3sCluster(agentCount:)Number of worker nodes (0 = single-node). Equivalent to WithAgentCount.
WithAgentCount(n)Same as above, fluent alternative. The health check waits for all 1 + n nodes to be Ready.
WithK3sVersion(tag)Overrides the k3s image tag, for example v1.32.3-k3s1. Synced to agents automatically.
WithPodSubnet(cidr)Sets --cluster-cidr. Defaults to the k3s built-in 10.42.0.0/16.
WithServiceSubnet(cidr)Sets --service-cidr. Defaults to the k3s built-in 10.43.0.0/16.
WithDisabledComponent(c)Passes --disable=<c>. Call multiple times for multiple components.
WithExtraArg(arg)Appends a raw argument to k3s server.
WithHelmImage(tag?, image?, registry?)Overrides the alpine/helm image used by AddHelmRelease.
WithKubectlImage(tag?, image?, registry?)Overrides the alpine/kubectl image used by AddK8sManifest.
WithDataVolume(name?)Mounts a named Docker volume at /var/lib/rancher/k3s.
WithLifetime(lifetime)Sets ContainerLifetime.Persistent or Session for the cluster and its agents.
apphost.mts
const cluster = await builder.addK3sCluster('k8s')
.withDataVolume({ name: 'k8s-data' })
.withLifetime(ContainerLifetime.Persistent);

WithDataVolume persists the k3s database, certificates, and node tokens across AppHost restarts. WithLifetime(Persistent) tells DCP to keep the Docker container alive between runs, making subsequent starts much faster.

AddHelmRelease runs helm upgrade --install --wait inside an alpine/helm container — no host-side helm binary is required:

apphost.mts
const podinfo = await cluster.addHelmRelease('podinfo', 'podinfo', {
repo: 'https://stefanprodan.github.io/podinfo',
version: '6.7.1',
namespace: 'podinfo',
});
// Wait for the chart install to complete before starting the operator.
const operator = await builder.addProject('operator', '../MyOperator/MyOperator.csproj');
await operator.waitForCompletion(podinfo);
await operator.withReference(cluster);

Values are applied in this order, with the last one winning:

  1. WithHelmValuesFile — in declaration order
  2. WithHelmValue (--set flags) — always override files

Use WithHelmValuesFile for structured overrides (values with commas, braces, or backslashes). WithHelmValue is convenient for individual scalar overrides.

AddK8sManifest runs kubectl apply --server-side inside an alpine/kubectl container. The apply mode is detected automatically from the path:

PathMode
Single .yaml / .yml filekubectl apply -f <file>
Directory (no kustomization.yaml)kubectl apply -f <dir> (all YAML files, lexicographic order)
Directory containing kustomization.yamlkubectl apply -k <dir> (Kustomize)
apphost.mts
// Plain YAML
const appConfig = await cluster.addK8sManifest('app-config', './k8s/app-config.yaml');
await appConfig.waitForCompletion(podinfo);
// Kustomize overlay — auto-detected; directory is bind-mounted to preserve base references
const monitoring = await cluster.addK8sManifest('monitoring-config', './k8s/monitoring');
await monitoring.waitForCompletion(podinfo);
await monitoring.waitForCompletion(appConfig);

AddServiceEndpoint starts an in-process WebSocket port-forward bound to 0.0.0.0:{allocatedPort} — no NodePort or LoadBalancer configuration is required. The endpoint transitions to Running only after the target service has a ready pod.

apphost.mts
const podinfoWeb = await cluster
.addServiceEndpoint('podinfo-web', 'podinfo', 9898, { namespace: 'podinfo' })
.waitForCompletion(podinfo);
// Host processes receive http://localhost:{port}
await builder
.addProject('api', '../MyApi/MyApi.csproj')
.withReference(podinfoWeb);
// DCP-network containers receive http://host.docker.internal:{port}
// --add-host=host.docker.internal:host-gateway is injected automatically
await builder
.addContainer('sidecar', 'myorg/sidecar')
.withReference(podinfoWeb);

The injected environment variable follows the Aspire service-discovery convention: services__{name}__url=http(s)://{host}:{port}.

Scheme is inferred from the port: 443 and 8443 resolve to https, all others to http. Override it with the scheme parameter, for example AddServiceEndpoint("ep", "svc", 8080, scheme: "https").

Both K3sClusterResource and K3sServiceEndpointResource implement IResourceWithConnectionString, so the standard WithReference overload handles credential injection automatically:

apphost.mts
// Standard withReference — injects KUBECONFIG or services__name__url
const operator = await builder.addProject('operator', '../MyOperator/MyOperator.csproj');
await operator.withReference(cluster);
const api = await builder.addProject('api', '../MyApi/MyApi.csproj');
await api.withReference(podinfoWeb);

k3s pods run on the internal pod network (10.42.0.0/16). Flannel masquerades outbound pod traffic through the k3s container’s DCP network IP, so pods can reach DCP services using host.docker.internal and the host-mapped port:

apphost.mts
const postgres = await builder.addPostgres('db');
const helmRelease = await cluster.addHelmRelease('my-operator', 'my-operator-chart')
.withHelmValue('database.host', 'host.docker.internal')
.withHelmValue('database.port', '5432');