.NET workloads¶
How it works¶
Brewlet delivers your application separately from the .NET runtime. For a
framework-dependent .NET application, you publish assemblies, required
configuration and an explicit launch descriptor as an OCI image, without the
.NET runtime. The platform approves and provisions a compatible
runtime image, and your Kubernetes Deployment selects it
by runtime ID. Brewlet runs the declared dotnet command with your
application files mounted read-only under /app.
Self-contained builds carry their own runtime, so they do not get this separation.
Dependencies can ship inside the application image or be published separately as a dependency layer set or governed bundle. Brewlet does not run NuGet or infer your publishing model.
An update starts new Pods; it never patches a running CLR process. See concepts and the launch descriptor for the execution model. The runtime root is also read-only, so temporary files and caches need explicit writable volumes.
Tested runtime images¶
Brewlet has been tested with .NET 10 using:
- unchanged Canonical
ubuntu/dotnet-runtime:10.0images on arm64 and amd64 (digests); - Microsoft
mcr.microsoft.com/dotnet/runtime10.0 images adapted for Brewlet (digests).
These are test inputs, not a security recommendation.
The .NET example fixture runs daily against main in the
.NET example workflow. It does not gate pull
requests or releases; Python is the only qualification fixture (see
verification).
Always use the runtime ID, dotnet path and platforms supplied by your
platform team. This guide assumes a runtime that provides /usr/bin/dotnet
under the example ID dotnet-10.
Publish the application¶
This guide uses the sample application from the Brewlet repository. It has a
main App.dll and a separate Greeting.dll dependency, and pins .NET SDK
10.0.401 in global.json.
-
Get the sample and publish it framework-dependent. Run
dotnet publishfrom the project directory so the pinned SDK is used:git clone --depth 1 --branch v0.8.1 https://github.com/microsoft/brewlet.git cp -R brewlet/integration-tests/fixtures/dotnet dotnet-demo cd dotnet-demo dotnet publish App/App.csproj --configuration Release \ --output published --self-contained false --nologo -
Split the output into an application layer and a dependency layer:
mkdir application dependencies chmod 0755 application dependencies install -m 0644 published/App.dll published/App.deps.json \ published/App.runtimeconfig.json application/ install -m 0644 published/Greeting.dll dependencies/
The .deps.json and .runtimeconfig.json files belong with the application.
This sample deliberately moves one assembly into its own layer; do not split
arbitrary publish directories blindly. For a real application, include every
required native, configuration and content file.
Write the launch descriptor¶
Save as launch.json:
{
"schemaVersion": 1,
"process": {
"entrypoint": [
"/usr/bin/dotnet"
],
"args": [
"/app/App.dll"
],
"workingDir": "/app"
}
}
Greeting.dll must resolve beside App.dll, so both layers map to /app. A
self-contained executable would use its own path as the entrypoint and has its
own loader, native-library and platform requirements.
Build and push¶
Run on Linux or macOS. On macOS, follow the producer filesystem requirements. Replace the registry with your own:
brewlet build registry.example.com/apps/dotnet-demo:v1 \
--platform linux/arm64 \
--launch launch.json \
--layer "dependency=./dependencies:/app" \
--layer "app=./application:/app"
brewlet push registry.example.com/apps/dotnet-demo:v1 --push-result push.json
A .NET assembly is not automatically platform-independent: RID-specific assets, P/Invoke and native libraries still need compatible Linux targets. Dependency layer sets are assembled when you build the image; Brewlet never runs NuGet restore at container startup.
Deploy¶
Replace the image digest, runtime ID, platform and namespace with values from your platform team. Resource values are examples, not a CLR memory budget.
apiVersion: apps/v1
kind: Deployment
metadata:
name: dotnet-demo
namespace: apps
spec:
replicas: 1
selector:
matchLabels: {app: dotnet-demo}
template:
metadata:
labels: {app: dotnet-demo}
annotations:
brewlet.sh/runtime-image: dotnet-10
spec:
runtimeClassName: brewlet
nodeSelector: {kubernetes.io/arch: arm64}
containers:
- name: app
image: registry.example.com/apps/dotnet-demo@sha256:<application-digest>
securityContext: {readOnlyRootFilesystem: true}
resources:
requests: {cpu: 100m, memory: 128Mi}
limits: {cpu: "1", memory: 768Mi}
readinessProbe:
httpGet: {path: /, port: 8000}
Registry credentials, non-root defaults and volumes work as described in Package and deploy.
The sample has its own HTTP listener and does not need ASP.NET Core. An ASP.NET Core application must select a runtime image that includes the ASP.NET Core framework; a plain .NET runtime image is not interchangeable with it.
Verify¶
-
Wait for the rollout and run the probe through
kubectl exec:kubectl -n apps rollout status deployment/dotnet-demo --timeout=5m kubectl -n apps exec deployment/dotnet-demo -- /usr/bin/dotnet \ /app/App.dll --probe "two words"The output includes
"arguments":["--probe","two words"]and a numeric, non-rootuid. -
Forward the port and query the service from another terminal:
kubectl -n apps port-forward deployment/dotnet-demo 18080:8000 curl --fail http://127.0.0.1:18080/Expect
"language":"dotnet", the actual CLR version and"dependency":"shared-contract". Stop the port-forward afterwards. -
Check the actual runtime binding to confirm which runtime image the Pod uses.
Pitfalls and limits¶
- The SDK used to publish and the runtime used to deploy are separate choices. Pin the SDK and select a compatible runtime ID explicitly.
- Framework-dependent, self-contained, single-file and Native AOT outputs have different loader and native-asset needs. This guide covers only the framework-dependent case.
- Shared runtime files do not mean a shared CLR heap. Brewlet injects no
CLR or GC tuning. Size memory from your own tests (the sample's process plus
kubectl execprobes ran out of memory at a 256Mi limit), and mount writable data and temp locations explicitly.