From 23081bd9c99dda46e8555e05b667130043046723 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Fri, 17 Jul 2026 16:24:14 +0200 Subject: [PATCH 1/4] docs: adding k3s integration docs --- .../config/sidebar/integrations.topics.ts | 1 + src/frontend/src/assets/icons/k3s-icon.svg | 1 + .../src/components/IntegrationGrid.astro | 2 + .../content/docs/integrations/compute/k3s.mdx | 422 ++++++++++++++++++ 4 files changed, 426 insertions(+) create mode 100644 src/frontend/src/assets/icons/k3s-icon.svg create mode 100644 src/frontend/src/content/docs/integrations/compute/k3s.mdx diff --git a/src/frontend/config/sidebar/integrations.topics.ts b/src/frontend/config/sidebar/integrations.topics.ts index 9003ee9e4..75685a62b 100644 --- a/src/frontend/config/sidebar/integrations.topics.ts +++ b/src/frontend/config/sidebar/integrations.topics.ts @@ -868,6 +868,7 @@ export const integrationTopics: StarlightSidebarTopicsUserConfig = { }, items: [ { label: 'Docker', slug: 'integrations/compute/docker' }, + { label: 'K3s', slug: 'integrations/compute/k3s' }, { label: 'Kubernetes', slug: 'integrations/compute/kubernetes' }, ], }, diff --git a/src/frontend/src/assets/icons/k3s-icon.svg b/src/frontend/src/assets/icons/k3s-icon.svg new file mode 100644 index 000000000..555284bf9 --- /dev/null +++ b/src/frontend/src/assets/icons/k3s-icon.svg @@ -0,0 +1 @@ + diff --git a/src/frontend/src/components/IntegrationGrid.astro b/src/frontend/src/components/IntegrationGrid.astro index aaa94bd64..fb7b3c7f8 100644 --- a/src/frontend/src/components/IntegrationGrid.astro +++ b/src/frontend/src/components/IntegrationGrid.astro @@ -52,6 +52,7 @@ import gitHubIcon from '@assets/icons/github-icon.png'; import gitHubLightIcon from '@assets/icons/github-light-icon.png'; import javaIcon from '@assets/icons/java-icon.png'; import javascriptIcon from '@assets/icons/javascript.svg'; +import k3sIcon from '@assets/icons/k3s-icon.svg'; import k6Icon from '@assets/icons/k6-icon.svg'; import keycloakIcon from '@assets/icons/keycloak-icon.svg'; import kubernetesIcon from '@assets/icons/kubernetes.svg'; @@ -188,6 +189,7 @@ const icons = [ { meta: gitHubIcon, alt: 'GitHub', search: 'github', light: gitHubLightIcon }, { meta: javaIcon, alt: 'Java', search: 'java' }, { meta: javascriptIcon, alt: 'JavaScript', search: 'javascript' }, + { meta: k3sIcon, alt: 'k3s', search: 'k3s' }, { meta: k6Icon, alt: 'k6', search: 'k6' }, { meta: keycloakIcon, alt: 'Keycloak', search: 'keycloak' }, { meta: kubernetesIcon, alt: 'Kubernetes', search: 'kubernetes' }, diff --git a/src/frontend/src/content/docs/integrations/compute/k3s.mdx b/src/frontend/src/content/docs/integrations/compute/k3s.mdx new file mode 100644 index 000000000..35103a730 --- /dev/null +++ b/src/frontend/src/content/docs/integrations/compute/k3s.mdx @@ -0,0 +1,422 @@ +--- +title: k3s integration +seoTitle: "k3s integration for Aspire: run a lightweight Kubernetes cluster locally" +description: Learn how to run a k3s Kubernetes cluster as part of your Aspire AppHost using the CommunityToolkit.Aspire.Hosting.K3s hosting integration. +--- + +import { Image } from 'astro:assets'; +import { Aside, Badge, Tabs, TabItem } from '@astrojs/starlight/components'; +import LearnMore from '@components/LearnMore.astro'; +import k3sIcon from '@assets/icons/k3s-icon.svg'; + + + +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](https://k3s.io/) 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. + + + +## Prerequisites + +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) + +:::note[Privileged containers] +k3s requires `--privileged`, `--cgroupns=host`, and a writable cgroup filesystem. The integration passes these flags automatically. +::: + +## Installation + +To start building an Aspire app that uses k3s, install the [📦 CommunityToolkit.Aspire.Hosting.K3s](https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.K3s) NuGet package: + + + + +```bash title="Terminal" +aspire add k3s +``` + + + Learn more about [`aspire add`](/reference/cli/commands/aspire-add/) in the command reference. + + +Or, choose a manual installation approach: + +```csharp title="AppHost.cs" +#:package CommunityToolkit.Aspire.Hosting.K3s@* +``` + +```xml title="AppHost.csproj" + +``` + + + + +```bash title="Terminal" +aspire add k3s +``` + + + Learn more about [`aspire add`](/reference/cli/commands/aspire-add/) in the command reference. + + +This updates your `aspire.config.json` with the k3s hosting integration package: + +```json title="aspire.config.json" ins={3} +{ + "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. + + + + +## Add a k3s cluster + +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: + + + + +```csharp title="AppHost.cs" +var builder = DistributedApplication.CreateBuilder(args); + +var cluster = builder.AddK3sCluster("k8s"); + +builder.AddProject("operator") + .WaitFor(cluster) + .WithReference(cluster); // injects KUBECONFIG automatically + +builder.Build().Run(); +``` + + + + +```typescript title="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(); +``` + + + + +## Configure the cluster + +All cluster options are available as fluent builder methods: + + + + +```csharp title="AppHost.cs" +var cluster = builder + .AddK3sCluster("k8s", + apiServerPort: 16443, // fixed host port for the API server (random by default) + agentCount: 2) // 1 server + 2 agent nodes + .WithK3sVersion("v1.32.3-k3s1") // pin the k3s image tag + .WithPodSubnet("10.42.0.0/16") // --cluster-cidr + .WithServiceSubnet("10.43.0.0/16") // --service-cidr + .WithDisabledComponent("traefik") // --disable=traefik (repeatable) + .WithExtraArg("--write-kubeconfig-mode=644") // raw k3s server flag (repeatable) + .WithHelmImage(tag: "3.18.0") // override the alpine/helm image + .WithKubectlImage(tag: "1.37.0") // override the alpine/kubectl image + .WithDataVolume() // persist cluster state across restarts + .WithLifetime(ContainerLifetime.Persistent); +``` + + + + +```typescript title="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: + +| Method | Parameter / 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=`. Call multiple times for multiple components. | +| `WithExtraArg(arg)` | Appends a raw argument to `k3s server`. | +| `WithHelmImage(registry?, image?, tag?)` | Overrides the `alpine/helm` image used by `AddHelmRelease`. | +| `WithKubectlImage(registry?, image?, tag?)` | 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. | + +## Persist cluster state across runs + + + + +```csharp title="AppHost.cs" +var cluster = builder.AddK3sCluster("k8s") + .WithDataVolume() + .WithLifetime(ContainerLifetime.Persistent); +``` + + + + +```typescript title="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. + + + +## Deploy Helm charts + +`AddHelmRelease` runs `helm upgrade --install --wait` inside an `alpine/helm` container — no host-side `helm` binary is required: + + + + +```csharp title="AppHost.cs" +var podinfo = cluster.AddHelmRelease( + name: "podinfo", + chart: "podinfo", + repo: "https://stefanprodan.github.io/podinfo", + version: "6.7.1", + @namespace: "podinfo") + .WithHelmValue("replicaCount", "2") + .WithHelmValuesFile("./deploy/podinfo-values.yaml"); + +// Wait for the chart install to complete before starting the operator. +builder.AddProject("operator") + .WaitForCompletion(podinfo) + .WithReference(cluster); +``` + + + + +```typescript title="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. + +## Apply Kubernetes manifests + +`AddK8sManifest` runs `kubectl apply --server-side` inside an `alpine/kubectl` container. The apply mode is detected automatically from the path: + +| Path | Mode | +|---|---| +| Single `.yaml` / `.yml` file | `kubectl apply -f ` | +| Directory (no `kustomization.yaml`) | `kubectl apply -f ` (all YAML files, lexicographic order) | +| Directory containing `kustomization.yaml` | `kubectl apply -k ` (Kustomize) | + + + + +```csharp title="AppHost.cs" +// Plain YAML +var appConfig = cluster.AddK8sManifest("app-config", "./k8s/app-config.yaml") + .WaitForCompletion(podinfo); + +// Kustomize overlay — auto-detected; directory is bind-mounted to preserve base references +var monitoring = cluster.AddK8sManifest("monitoring-config", "./k8s/monitoring") + .WaitForCompletion(podinfo) + .WaitForCompletion(appConfig); +``` + + + + +```typescript title="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); +``` + + + + +## Expose Kubernetes services + +`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. + + + + +```csharp title="AppHost.cs" +var podinfoWeb = cluster + .AddServiceEndpoint("podinfo-web", "podinfo", servicePort: 9898, @namespace: "podinfo") + .WaitForCompletion(podinfo); + +// Host processes receive http://localhost:{port} +builder.AddProject("api") + .WaitFor(podinfoWeb) + .WithReference(podinfoWeb); + +// DCP-network containers receive http://host.docker.internal:{port} +// --add-host=host.docker.internal:host-gateway is injected automatically +builder.AddContainer("sidecar", "myorg/sidecar") + .WaitFor(podinfoWeb) + .WithReference(podinfoWeb); +``` + + + + +```typescript title="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")`. + +## Kubeconfig injection + +Both `K3sClusterResource` and `K3sServiceEndpointResource` implement `IResourceWithConnectionString`, so the standard `WithReference` overload handles credential injection automatically: + + + + +```csharp title="AppHost.cs" +// Projects and executables receive KUBECONFIG pointing to the host-accessible variant +builder.AddProject("operator") + .WithReference(cluster); // KUBECONFIG=…/.k3s/k8s/local/kubeconfig.yaml + +// Containers receive a bind-mounted kubeconfig at /tmp/k3s-kubeconfig.yaml +builder.AddContainer("sidecar", "myorg/sidecar") + .WithReference(cluster); // KUBECONFIG=/tmp/k3s-kubeconfig.yaml + file bind-mount +``` + +All standard Kubernetes tooling reads `KUBECONFIG` automatically: + +```csharp +var config = KubernetesClientConfiguration.BuildConfigFromConfigFile( + Environment.GetEnvironmentVariable("KUBECONFIG")); +using var client = new Kubernetes(config); +``` + + + + +```typescript title="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); +``` + + + + +## Reach Aspire services from k3s pods + +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: + +```csharp title="AppHost.cs" +var postgres = builder.AddPostgres("db"); + +cluster.AddHelmRelease("my-operator", "my-operator-chart") + .WithHelmValue("database.host", "host.docker.internal") + .WithHelmValue("database.port", + postgres.GetEndpoint("tcp").Property(EndpointProperty.Port)); +``` + +## See also + +- [k3s website](https://k3s.io/) +- [k3s documentation](https://docs.k3s.io/) +- [Aspire Community Toolkit GitHub](https://github.com/CommunityToolkit/Aspire) +- [CommunityToolkit.Aspire.Hosting.K3s Repository](https://github.com/CommunityToolkit/Aspire/blob/main/src/CommunityToolkit.Aspire.Hosting.K3s/README.md) + From 566ebf0fe0124cfa1f73ec46372f0323a3899452 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Fri, 17 Jul 2026 16:56:28 +0200 Subject: [PATCH 2/4] fix: page rendering + include integration to frontend data --- .../content/docs/integrations/compute/k3s.mdx | 2 +- src/frontend/src/data/aspire-integrations.json | 17 +++++++++++++++++ src/frontend/src/data/integration-docs.json | 4 ++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/frontend/src/content/docs/integrations/compute/k3s.mdx b/src/frontend/src/content/docs/integrations/compute/k3s.mdx index 35103a730..979361824 100644 --- a/src/frontend/src/content/docs/integrations/compute/k3s.mdx +++ b/src/frontend/src/content/docs/integrations/compute/k3s.mdx @@ -419,4 +419,4 @@ cluster.AddHelmRelease("my-operator", "my-operator-chart") - [k3s documentation](https://docs.k3s.io/) - [Aspire Community Toolkit GitHub](https://github.com/CommunityToolkit/Aspire) - [CommunityToolkit.Aspire.Hosting.K3s Repository](https://github.com/CommunityToolkit/Aspire/blob/main/src/CommunityToolkit.Aspire.Hosting.K3s/README.md) - + diff --git a/src/frontend/src/data/aspire-integrations.json b/src/frontend/src/data/aspire-integrations.json index 4340b7579..abc3f69ff 100644 --- a/src/frontend/src/data/aspire-integrations.json +++ b/src/frontend/src/data/aspire-integrations.json @@ -2176,6 +2176,23 @@ "downloads": 84840, "version": "13.4.0" }, + { + "title": "CommunityToolkit.Aspire.Hosting.K3s", + "description": "k3s (lightweight Kubernetes) support for Aspire.", + "icon": "https://api.nuget.org/v3-flatcontainer/communitytoolkit.aspire.hosting.k3s/13.4.1-beta.686/icon", + "href": "https://www.nuget.org/packages/CommunityToolkit.Aspire.Hosting.K3s", + "tags": [ + "aspire", + "integration", + "communitytoolkit", + "dotnetcommunitytoolkit", + "hosting", + "k3s", + "kubernetes" + ], + "downloads": 258, + "version": "13.4.1-beta.686" + }, { "title": "CommunityToolkit.Aspire.Hosting.k6", "description": "Grafana k6 support for Aspire.", diff --git a/src/frontend/src/data/integration-docs.json b/src/frontend/src/data/integration-docs.json index e7c67f277..b3f74fd73 100644 --- a/src/frontend/src/data/integration-docs.json +++ b/src/frontend/src/data/integration-docs.json @@ -415,6 +415,10 @@ "match": "CommunityToolkit.Aspire.Hosting.JavaScript.Extensions", "href": "/integrations/frameworks/nodejs-extensions/" }, + { + "match": "CommunityToolkit.Aspire.Hosting.K3s", + "href": "/integrations/compute/k3s/" + }, { "match": "CommunityToolkit.Aspire.Hosting.k6", "href": "/integrations/devtools/k6/" From b6273d4a934d0845bf293dd949435ef3922e2301 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Fri, 17 Jul 2026 17:27:17 +0200 Subject: [PATCH 3/4] chore: c# interface method params order Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/frontend/src/content/docs/integrations/compute/k3s.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/frontend/src/content/docs/integrations/compute/k3s.mdx b/src/frontend/src/content/docs/integrations/compute/k3s.mdx index 979361824..14ba9bd59 100644 --- a/src/frontend/src/content/docs/integrations/compute/k3s.mdx +++ b/src/frontend/src/content/docs/integrations/compute/k3s.mdx @@ -185,8 +185,8 @@ The available options are: | `WithServiceSubnet(cidr)` | Sets `--service-cidr`. Defaults to the k3s built-in `10.43.0.0/16`. | | `WithDisabledComponent(c)` | Passes `--disable=`. Call multiple times for multiple components. | | `WithExtraArg(arg)` | Appends a raw argument to `k3s server`. | -| `WithHelmImage(registry?, image?, tag?)` | Overrides the `alpine/helm` image used by `AddHelmRelease`. | -| `WithKubectlImage(registry?, image?, tag?)` | Overrides the `alpine/kubectl` image used by `AddK8sManifest`. | +| `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. | From b4f5f33a67b853b91d83349e8d70566c24933a83 Mon Sep 17 00:00:00 2001 From: edmondshtogu Date: Fri, 17 Jul 2026 18:52:59 +0200 Subject: [PATCH 4/4] chore: shorten seo title --- src/frontend/src/content/docs/integrations/compute/k3s.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/frontend/src/content/docs/integrations/compute/k3s.mdx b/src/frontend/src/content/docs/integrations/compute/k3s.mdx index 14ba9bd59..c0b02fe0a 100644 --- a/src/frontend/src/content/docs/integrations/compute/k3s.mdx +++ b/src/frontend/src/content/docs/integrations/compute/k3s.mdx @@ -1,6 +1,6 @@ --- title: k3s integration -seoTitle: "k3s integration for Aspire: run a lightweight Kubernetes cluster locally" +seoTitle: Aspire k3s integration for lightweight Kubernetes description: Learn how to run a k3s Kubernetes cluster as part of your Aspire AppHost using the CommunityToolkit.Aspire.Hosting.K3s hosting integration. ---