<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>5. C# App on Kubernetes :: Cloud Native Engineering</title><link>https://cloud-native.labs.netrics.dev/5-csharp-app/index.html</link><description>What we’re building A small full-stack application — frontend and backend in a single ASP.NET Core container — deployed onto the AKS cluster provisioned in the previous chapters and backed by a managed PostgreSQL database.
Component Technology Details Frontend Static HTML served by ASP.NET Core Input box + submit button; displays all submitted texts below Backend ASP.NET Core minimal API POST /texts writes timestamp + text; GET /texts returns JSON Database PostgreSQL (Azure Flexible Server) Automatic SQLite fallback when DB_CONNECTION_STRING is not set The fallback to SQLite means the app runs entirely locally with dotnet run — no database server needed during development.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://cloud-native.labs.netrics.dev/5-csharp-app/index.xml" rel="self" type="application/rss+xml"/><item><title>5.1. ASP.NET Core App</title><link>https://cloud-native.labs.netrics.dev/5-csharp-app/1-aspnet-app/index.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://cloud-native.labs.netrics.dev/5-csharp-app/1-aspnet-app/index.html</guid><description>Build a minimal ASP.NET Core application that serves a small HTML frontend and a two-endpoint JSON API, backed by SQLite locally and PostgreSQL in production — all in a single container.
Step 1 — Create the project Create the application directory inside cloud-native-lab and add the two source files.
mkdir -p cloud-native-lab/csharp-app cd cloud-native-lab/csharp-app Create MyApp.csproj:</description></item><item><title>5.2. Push and Deploy</title><link>https://cloud-native.labs.netrics.dev/5-csharp-app/2-push-and-deploy/index.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://cloud-native.labs.netrics.dev/5-csharp-app/2-push-and-deploy/index.html</guid><description>Push the container image to ACR, write a raw Kubernetes manifest, and deploy the app to the cluster using kubectl — no Pulumi yet, so you can see what raw Kubernetes resources look like.
Step 1 — Push the image to ACR Log in to the registry and push the image you built in the previous lab. All values come from Pulumi stack outputs — no copy-pasting from the portal.</description></item><item><title>5.3. Connect to PostgreSQL</title><link>https://cloud-native.labs.netrics.dev/5-csharp-app/3-postgresql/index.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://cloud-native.labs.netrics.dev/5-csharp-app/3-postgresql/index.html</guid><description>Provision a managed Azure Database for PostgreSQL Flexible Server using a reusable Pulumi component, then connect it to the running app.
Step 1 — Create the PostgresFlexibleServer component Create a new file postgres_flexible_server.py next to __main__.py:
import pulumi import pulumi_azure_native as azure_native import pulumi_random class PostgresFlexibleServer(pulumi.ComponentResource): def __init__( self, name: str, version: str = "18", storage_size_gb: int = 32, sku_name: str = "Standard_B1ms", sku_tier: str = azure_native.dbforpostgresql.SkuTier.BURSTABLE, opts: pulumi.ResourceOptions = None ): super().__init__("workshop:azure:PostgresFlexibleServer", name, {}, opts) self.child_opts = pulumi.ResourceOptions(parent=self) self._rg = azure_native.resources.ResourceGroup( name, resource_group_name=f"rg-{name}", opts=self.child_opts ) rand = pulumi_random.RandomInteger( f"{name}-login-suffix", min=10000000, max=99999999, opts=self.child_opts ) self._admin_login = rand.result.apply(lambda n: f"admin{n}") password = pulumi_random.RandomPassword( f"{name}-password", length=16, special=False, opts=self.child_opts ) self._admin_password = password.result self.server = azure_native.dbforpostgresql.Server( name, server_name=f"psql-{name}", resource_group_name=self._rg.name, administrator_login=self._admin_login, administrator_login_password=self._admin_password, sku=azure_native.dbforpostgresql.SkuArgs( name=sku_name, tier=sku_tier ), storage=azure_native.dbforpostgresql.StorageArgs( storage_size_gb=storage_size_gb ), version=version, opts=self.child_opts ) def add_database(self, db_name: str) -&gt; azure_native.dbforpostgresql.Database: return azure_native.dbforpostgresql.Database( f"{self.server._name}-{db_name}", server_name=self.server.name, resource_group_name=self._rg.name, database_name=db_name, charset="UTF8", collation="en_US.utf8", opts=self.child_opts ) def get_npgsql_connection_string( self, db: azure_native.dbforpostgresql.Database ) -&gt; pulumi.Output[str]: return pulumi.Output.secret( pulumi.Output.all( self.server.fully_qualified_domain_name, db.name, self._admin_login, self._admin_password ).apply(lambda args: f"Host={args[0]};Database={args[1]};Username={args[2]};" f"Password={args[3]};SSL Mode=Require" ) ) def add_firewall_rule( self, name: str, start_ip_address: str, end_ip_address: str ) -&gt; azure_native.dbforpostgresql.FirewallRule: return azure_native.dbforpostgresql.FirewallRule( f"{self.server._name}-fw-{name}", server_name=self.server.name, resource_group_name=self._rg.name, firewall_rule_name=name, start_ip_address=start_ip_address, end_ip_address=end_ip_address, opts=self.child_opts ) Explanation The component groups three Azure resources — server, database, and firewall rule — under a single Pulumi parent. The credentials are generated internally so no sensitive values are passed by the caller or stored in config.</description></item><item><title>5.4. Package as a Helm Chart</title><link>https://cloud-native.labs.netrics.dev/5-csharp-app/4-helm-chart/index.html</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://cloud-native.labs.netrics.dev/5-csharp-app/4-helm-chart/index.html</guid><description>Convert the hand-written deployment.yaml into a Helm chart so the app can be installed, upgraded, and rolled back as a single versioned unit — and so the hostname and credentials are passed in at install time rather than baked into the manifest.
Step 1 — Create the chart skeleton Create the directory structure by hand — no generated boilerplate needed.</description></item></channel></rss>