Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,105 @@ await service.publishAsKubernetesService(async (resource) => {
</TabItem>
</Tabs>

## Add custom Kubernetes manifests

Within a `PublishAsKubernetesService` callback, you can attach arbitrary Kubernetes manifests — such as [cert-manager](https://cert-manager.io/) `Certificate` definitions, additional `ConfigMap` objects, or any other Kubernetes resource — to the generated Helm chart alongside your service.

The API surface differs by language:

- **C#**: add subclasses of `BaseKubernetesResource` directly to the `AdditionalResources` collection. Several built-in types (such as `ConfigMap`) are available in the `Aspire.Hosting.Kubernetes.Resources` namespace, and you can subclass `BaseKubernetesResource` to model any custom resource definition (CRD).
- **TypeScript**: call `addManifest` on the resource. It takes the manifest's `apiVersion`, `kind`, and `metadata.name`, and returns a handle for setting labels, annotations, a namespace, and arbitrary field values using dot-notation paths.

<Tabs syncKey='aspire-lang'>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — addressed in a76a4be. The Python/Java/Go TabItems were dropped from this section, leaving only csharp and typescript under syncKey='aspire-lang', which matches the convention used everywhere else in this file and in Head.astro.

<TabItem id='csharp' label='C#'>

Define a class for the custom resource by deriving from `BaseKubernetesResource`, then add an instance to `AdditionalResources`. The example below models a cert-manager `Certificate` CRD:

```csharp title="AppHost.cs"
using Aspire.Hosting.Kubernetes.Resources;
using YamlDotNet.Serialization;

builder.AddContainer("service", "nginx")
.PublishAsKubernetesService(resource =>
{
var certificate = new Certificate
{
Metadata = { Name = "service-tls" },
Spec =
{
SecretName = "service-tls",
DnsNames = { "service.example.com" },
IssuerRef = { Name = "letsencrypt-prod", Kind = "ClusterIssuer" },
},
};
certificate.Metadata.Labels["example.com/managed-by"] = "aspire";
resource.AdditionalResources.Add(certificate);
});

public sealed class Certificate() : BaseKubernetesResource("cert-manager.io/v1", "Certificate")
{
[YamlMember(Alias = "spec")]
public CertificateSpec Spec { get; set; } = new();
}

public sealed class CertificateSpec
{
[YamlMember(Alias = "secretName")]
public string SecretName { get; set; } = "";

[YamlMember(Alias = "dnsNames")]
public List<string> DnsNames { get; } = [];

[YamlMember(Alias = "issuerRef")]
public IssuerRef IssuerRef { get; set; } = new();
}

public sealed class IssuerRef
{
[YamlMember(Alias = "name")]
public string Name { get; set; } = "";

[YamlMember(Alias = "kind")]
public string Kind { get; set; } = "Issuer";
}
```

For simple cases that map to a built-in type, instantiate the type directly — for example, `new ConfigMap { Metadata = { Name = "extra-config" }, Data = { ["key"] = "value" } }` — and add it to `AdditionalResources`.

</TabItem>
<TabItem id='typescript' label='TypeScript'>
```typescript title="apphost.ts"
const service = await builder.addContainer('service', 'nginx');
await service.publishAsKubernetesService(async (resource) => {
await resource.addManifest('cert-manager.io/v1', 'Certificate', 'service-tls', {
configure: async (manifest) => {
await manifest.withLabel('example.com/managed-by', 'aspire');
await manifest.withField('spec.secretName', 'service-tls');
await manifest.withField('spec.dnsNames', ['service.example.com']);
await manifest.withField('spec.issuerRef.name', 'letsencrypt-prod');
await manifest.withField('spec.issuerRef.kind', 'ClusterIssuer');
},
});
});
```
</TabItem>
</Tabs>

The manifest handle returned by `addManifest` supports the following configuration methods:

| Method | Description |
|---|---|
| `withNamespace` | Sets the Kubernetes namespace for the manifest. |
| `withLabel` | Adds a metadata label to the manifest. |
| `withAnnotation` | Adds a metadata annotation to the manifest. |
| `withField` | Sets an arbitrary field using a dot-notation path (for example, `spec.maxReplicaCount`). |

Each manifest is emitted as a separate template file inside the generated Helm chart.

<Aside type="note">
`addManifest` is the recommended way to attach custom Kubernetes resources from TypeScript AppHosts, where there is no strongly-typed model for arbitrary CRDs. C# AppHosts have direct access to the resource model, so they add `BaseKubernetesResource` instances to `AdditionalResources` instead.
</Aside>

## Publishing and deployment

The Kubernetes integration supports both `aspire publish` (generate Helm chart artifacts) and `aspire deploy` (deploy directly to your current cluster context).
Expand Down
Loading