June 2026 · CloudOps

Shipping a service
on the platform

You own one YAML file.
The platform owns everything it implies.

Why a new platform?

1
The natural next step from Container Apps
the container model you already run in production — now with built-in SSO, passwordless databases, and per-service dashboards and alerts.
2
Secure & SOC 2-compliant by default
hardened pods, passwordless Azure access, audit-ready controls — you inherit it instead of building it.
3
A clean slate, done right
every lesson from the legacy cluster fixed at the foundation — no inherited drift, no one-off hacks.
4
Reliable by design
Git is the source of truth; the platform reconciles, autoscales and pages itself. Less to babysit.
5
Cheaper to run
a shared cluster bin-packs and right-sizes workloads far tighter than per-app allocations — pay for what you use, not what you reserve.
Younium Platform · CloudOps

What the platform actually is

All of Kubernetes, none of the YAML. You declare a service; the platform writes every object it takes to run it.

A shared cluster (AKS)

Run from Git by ArgoCD, partitioned into two projects:
core — the platform plumbing CloudOps runs
services — your workloads, each in its own namespace

One file, translated for you

Your .infrastructure/<env>.yaml lives in your repo. A shared chart compiles it into every K8s object — Deployment, routing, TLS, scaling, secrets, identity.
You never write raw Kubernetes.

No opinions on your code. The only requirement is a Dockerfile — any language, any framework, built your way. The platform just gets it deployed.
Younium Platform · CloudOps

The manifest

You write
.infrastructure/
development.yaml
Git
merge to development
ArgoCD
discovers your repo
no registration · no infra PR
Live · ≤15 min
myapp.dev.younium.cc
TLS · DNS · autoscaling
One file in your repo declares everything about your service:

Configure

image, env, secrets, scaling, health checks

Expose securely

public hostname + automatic TLS · optional SSO (Frontegg / Entra)

Provision

Azure access, Postgres / MSSQL, volumes — passwordless

Observe

dashboards, logs & alerts — per service

Younium Platform · CloudOps

Don't hand-write it — onboard-service

A Claude Code skill scaffolds your whole onboarding from a short chat. You just review the PR.
payments-api
> claude "onboard payments-api to the platform"
readiness check — repo · image · port · branch  
gathered the essentials — hostname, scaling
wrote .infrastructure/development.yaml
wrote .github/workflows/infrastructure.yml (gatecheck)
ran the gatecheck locally — passed ✓
✓ opened PR #42 → development — you review & merge
it scaffolds deployment infra only — never your app code
Younium Platform · CloudOps

Skills for the whole lifecycle

onboard-service is one of a set — the same chat-driven flow covers a service from first deploy to day-2.
onboarding-readiness-check
pre-flight — is the repo ready to deploy?
onboard-service
first-time scaffold — manifest + gatecheck + PR
add-database
add Postgres / MSSQL (carries the runtime gotchas)
modify-manifest
change an already-deployed service's manifest
verify-deployment
did it deploy? is it healthy?
show-manifest · list-services
inspect what's deployed
you stay in control — each one opens a PR or just reads state · your app code is always yours
Younium Platform · CloudOps

Manifest anatomy

One file, top to bottom — every block maps to one of the four jobs.
# .infrastructure/development.yaml
services:
  myapp:
    # ── Configure ──
    image: { repository: ghcr.io/younium/myapp, tag: "1.2.0" }
    ports: [{ name: http, port: 8080 }]
    scaling: { mode: http, min: 0, max: 5 }
    healthchecks: { readiness: { path: /healthz } }
    config: { LOG_LEVEL: "info" }
    secrets: { DB_PASSWORD: { remoteKey: myapp--db--password } }
    # ── Expose ──
    exposure:
      hostname: myapp.dev.younium.cc
      auth: { mode: signedIn, instance: frontegg }
    # ── Provision ──
    azureAccess:
      blob: [{ container: myapp-uploads }]
    # ── Observe ──
    observability: { autoInstrument: dotnet }
# repo-scoped resources
postgres:
  databases: [{ name: appdb, secretName: appdb-conn }]
Configure — image, ports, scaling, health, config, secrets

Expose — a public hostname with automatic TLS; optional SSO (Frontegg / Entra)

Provision — Azure access, databases, volumes

Observe — one line turns on dashboards, metrics & alerts

Promote & deliver — per-env files (dev → sandbox → prod) · canary rollout · Kargo promotion
Younium Platform · CloudOps

Live demo

admin.dev.younium.cc

Discovery
did my service register?
Manifest
config & claims, rendered
Health & logs
is it running cleanly?
Links
dashboards, live URL, …

What every service gets

On by default · every service
HTTPS + DNS
wildcard cert on your hostname
Scale-to-zero
idle services cost nothing
Hardened pods
non-root · read-only FS · no caps
Safe defaults
resource limits + disruption budgets
Observability
dashboards, logs & pod alerts
Self-healing
Git drift corrected, pods restarted
Opt-in · optional blocks
Younium Platform · CloudOps

Exposure — your public URL

https://myapp.dev.younium.cc
# .infrastructure/development.yaml
exposure:
  hostname: myapp.dev.younium.cc
  # path: /api # share a host
  # stripPrefix: true # if app serves from /
  # tls: none # internal-only
That one line gets you wildcard TLS · a DNS record · a route to your pod — no ACME wait, no ticket.
path — share one host · longest prefix wins
tls: none — internal-only, no public route
stripPrefix — drop it if your app serves from /
auth — put SSO in front (next slides)
Younium Platform · CloudOps

SSO — one block, no auth code

request
GET /
Envoy Gateway
runs the OIDC sign-in — redirect to the IdP, validate the session cookie
request + identity
X-Auth-Request-User
X-Auth-Request-Email
X-Auth-Request-Groups
your app
exposure:
  hostname: portal.dev.younium.cc
  auth:
    mode: signedIn  # any logged-in user
    instance: frontegg  # customers · or entra
    gates:  # optional — gate a path to a role
      - pathPrefix: /admin/
        roles: [Admin]
Zero auth code
no OIDC in your app, no AAD app to register
instance
entra for internal tools · frontegg for customers
gates
require a role on a path; everything else stays open to any signed-in user
Automatic
login redirect, session cookie & /logout — all handled
Younium Platform · CloudOps

SSO — what your app sees

HeaderEntra (demo ↗)Frontegg (demo ↗)
X-Auth-Request-Useroid — stable GUIDsub — stable GUID
X-Auth-Request-Preferred-UsernameUPN (first.last@younium.com)email
X-Auth-Request-Emailemail — may be absentemail
X-Auth-Request-Groupsbase64-encoded JSON array — WyJhZG1pbiJd = ["admin"]
Authorization: Bearerthe raw IdP access token — forwarded on signedIn routes (not on gated sub-paths)
Primary key = X-Auth-Request-User (the GUID); Email can be missing on Entra — fall back to Preferred-Username
Groups is base64 → JSON, not comma-separated — decode then parse (changed from oauth2-proxy)
Authorization: Bearer is a real IdP access token — you can relay it to another same-instance service (mind the aud/scopes)
• Trust these headers only from the gateway — EG strips client-supplied copies
• Switching instance resets sessions & flips User (oidsub); /logout signs out every app under .dev.younium.cc
Younium Platform · CloudOps

Scaling — zero, fixed, or by metric

# default — traffic-based, incl. to zero
scaling: { mode: http, min: 0, max: 5 }
# always-on: { mode: replicas, replicas: 3 }
# metrics — scale on Service Bus queue depth (0 → N → 0)
scaling:
  mode: metrics
  min: 0
  max: 5
  triggers:
    - type: azure-servicebus
      metadata:
        queueName: jobs
        messageCount: "5"  # ~1 pod / 5 msgs
      authenticationRef: { name: worker-azure-wi }  # auto
mode: http (default) — scales on request traffic, down to zero when idle

mode: replicas — always-on, fixed count (no autoscaling)

mode: metrics — KEDA triggers between min/max; the example scales on Service Bus queue depth (0 → N → 0) · also cron, custom metrics
Younium Platform · CloudOps

Config & secrets

config:
  DOWNSTREAM_API_URL: "https://api.${platform.hostZone}"
  FEATURE_X: "true"
secrets:
  DB_PASSWORD:
    remoteKey: myapp--db--password
config → a ConfigMap, mounted as env vars
secrets → pulled from the cluster Key Vault, never in Git

${platform.*} tokens resolve per environment — one file shape works on dev, sandbox, and prod
need a secret created? drop it in the cluster Key Vault (CloudOps can help once) — the manifest only references keys
Younium Platform · CloudOps

Azure resources: declare what you need

azureAccess:
  blob:
    - { container: myapp-uploads, secretName: myapp-uploads }
  appConfig: { enabled: true, secretName: myapp-appconfig }
  serviceBus:
    entities:
      - { path: topics/orders, access: send }
      - { path: topics/orders/subscriptions/billing, access: receive }
  storage:
    - { account: ynmdevsharedtf, services: [blob], access: read }
For each block the platform: creates the resource (where applicable) · gives your service's own Azure identity exactly that access — your container, your topic, that account — · injects coordinates as env vars (BLOB_accountUrl, APPCONFIG_endpoint, …)
Younium Platform · CloudOps

One credential. That's the whole pattern.

var credential = new DefaultAzureCredential();  // no options, no client IDs, no secrets

var blob   = new BlobContainerClient(new Uri($"{Env("BLOB_accountUrl")}/{Env("BLOB_containerName")}"), credential);
builder.Configuration.AddAzureAppConfiguration(o => o.Connect(new Uri(Env("APPCONFIG_endpoint")), credential));
var sender = new ServiceBusClient(Env("SB_namespaceFqdn"), credential).CreateSender("orders");
• Every pod runs as your service's identity — the SDK finds it automatically
• No passwords or connection-string secrets exist anywhere in this flow
• Isolation is mutual and enforced by Azure: you can't read another service's data, and nothing else can read yours
Younium Platform · CloudOps

Databases — Postgres & MSSQL

# Postgres — always empty on create
postgres:
  databases:
    - { name: appdb, secretName: appdb-conn }
# MSSQL fresh — empty DB, run your own migrations
mssql:
  databases:
    - { name: appdb, secretName: appdb-mssql, mode: fresh }
# MSSQL copy — clone the golden reference (default)
mssql:
  databases:
    - { name: appdb, secretName: appdb-mssql }
    # mode: copy is implicit — same as omitting mode
# MSSQL copy — stamp from another DB
mssql:
  databases:
    - { name: appdb, secretName: appdb-mssql,
        mode: copy, sourceDatabase: ynm-db-reference-golden }
MSSQL modes
fresh
empty database — run your own migrations
copy — default
Azure DB copy; stamps from the platform golden DB
sourceDatabase
copy from a specific DB instead (advanced)
✓  Fixes migrations in test environments — every environment gets its own copy of the database, so migrations run isolated and clean: no shared-DB drift, no conflicts between branches or PR previews.
Younium Platform · CloudOps

Read-only content volumes

volumes:
  - catalog: fonts  # platform-managed asset
    mountPath: /usr/share/fonts/truetype/ms
  - container: ai-system-prompts  # your blob container
    mountPath: /mnt/ai-system-prompts
    prefix: prod/  # optional subfolder (must end with /)
static files in your pod — an init container hydrates blob content into a read-only mount at pod start

catalog — platform-managed shared assets (no azureAccess block needed)
catalogtypical mountPath
fonts/usr/share/fonts/truetype/ms
only fonts on dev/eu01 today — new entries need a cluster overlay PR

container — your own blob container (must match azureAccess.blob)
not for read-write state — that's what databases and blob are for
Younium Platform · CloudOps

Test environments — a full stack per branch

Label a PR test-env and the platform spins up a complete, isolated copy — your branch's services, their own URLs, their own data. Close the PR and it's gone.
PR · label test-env namespace env-<name> your branch, live
own URLs · own DB copy
Isolated — own namespace + hostnames under the wildcard zone
Realistic data — its own database copy; migrations run clean
Production-shaped — same platform-service render as dev & prod
Ephemeral — created on the label, torn down when the PR closes
defined as a file in ynm-environments · dev cluster today
Younium Platform · CloudOps

From merge to production

Merge to development → live on dev · automatic
merge → development CI builds image
tagged 1.4.0 → ACR
Kargo picks it up
writes tag → development.yaml
live on dev
webhook-driven · no clicks
Sandbox & production · one click each
Kargo UI · Promote opens a promotion/* PR
tag → sandbox.yaml
auto-merges on green check live · both regions
then Promote → prod

Instant rollback

re-promote a previous version in Kargo — one click, no rebuild (the image already exists)

Canary / blue-green

add a rollout block — weighted traffic shift with auto-analysis; rolls back itself if metrics regress · dashboard ↗

Younium Platform · CloudOps

One product, many repos

A product is rarely one repo. Give each repo the same promotion.project key and they share one Kargo pipeline and UI — see and promote the whole product together.
# in every repo of the product
promotion:
  kargo: true
  project: core  # same key everywhere
Kargo · kargo-grp-core
monolith-api   dev → sandbox → prod
web-angular    dev → sandbox → prod
web-admintool  dev → sandbox → prod
• each repo keeps its own image & build; the group just collects them in one pane
• promote members together or one at a time — same Promote click, same promotion/* PR flow
• add a service to the product by setting the key — no infra PR (groups are pre-registered per cluster)
Younium Platform · CloudOps

What's next

The paved road keeps getting wider — here's what's landing next.
1
Slack integration
build & promotion notifications
2
Onboard CPQ
first big service on the platform
3
Core test envs
test environments for Younium core
4
Promotion procedures
finalize sandbox → prod
shaped by what you ask for — tell us in #cloudops-support
Younium Platform · CloudOps

One folder in your repo.
The platform does the rest.

slack · #cloudops-support

~0:30. First-ever platform intro for the dev team. Promise up front: this talk is everything you need to ship and run a service — and it fits in one YAML file you own.

~1:30. The "why" before the "how". Lead with Container Apps so it reads as evolution, not a rewrite. Security/SOC 2 = credibility + necessity. Clean slate = the legacy cluster's mistakes are fixed at the root, not patched. Reliability + cost are the day-to-day wins devs feel. Land it: same paved road for everyone, you move at Git speed, the platform handles the rest.

~1:30. Demystify "platform". Two honest pieces. One: it's a real AKS cluster, run from Git by ArgoCD and partitioned into two ArgoCD projects — `core` (CloudOps-owned platform addons) and `services` (developer workloads, your namespaces). Two: you only ever touch one file in your repo; a shared Helm chart is the translation layer that turns it into Kubernetes. The K8s complexity is real — it's the platform's problem, not yours.

~2:00. Reframe: the manifest is the one artefact that matters. Walk the chart left-to-right once, then the four jobs it does — these are the rest of the deck's section headers, so this slide is the map. Land the contract: if you ever want to click in Azure or run kubectl, stop — it goes in the file.

~1:00. The hero: you don't even write the YAML. Walk the terminal top to bottom — each ● is a step the skill performs (readiness check → the two files → a local gatecheck → a PR). The payoff line: it never writes app code, so app-side gaps come back as TODOs. Next slide: the rest of the skill family.

~1:00. The point: onboarding is one skill in a family that spans the whole lifecycle — readiness → onboard → add-database → modify → verify → inspect. All in the ynm-platform plugin. None write app code; mutating ones land a PR, inspecting ones are read-only. Reassures the control-conscious dev.

~2:00. Pay off the four verbs from "The manifest": here they are in one real file. Walk it top to bottom — each marked block is one job. Top-level postgres/mssql are repo-scoped (not per-service). Mention the companion gatecheck workflow that blocks bad manifests pre-merge, and point at platform.md + platform-demo to copy from. The deep-dive slides follow.

~3:00. Switch to the browser. Walk admin.dev.younium.cc: the service list, open one service → its rendered manifest, claims, health, and the links out (dashboards, logs, the live URL). Read-only — fastest answer to "what's deployed and is it healthy". Tie back: everything shown here came from the one manifest file.

~2:00. The hero is the baseline: six production features you never asked for — that's the "without asking" payoff. The hardened pod baseline is overridable if your image truly needs root/writable FS, but discourage it. The purple chips are the opt-in menu — gesture at them, don't read them; the main ones get their own slides later.

~1:30. The hero: one hostname line under *.dev.younium.cc → a real HTTPS URL. Wildcard cert, DNS, and the Gateway route are all automatic — no ACME wait. The knobs row: path shares a host (longest prefix wins), stripPrefix only when the app serves from /, tls: none for internal services, auth → SSO, up next.

~2:00. Dev message: one block, no auth code. The gateway (Envoy-native OIDC) does the sign-in; your app trusts the X-Auth-Request-* headers (next slide). instance = entra (employees) / frontegg (customers). gates require a role on a path; everything else is open to any signed-in user. Login redirect, cookie and /logout are automatic. (V2 removed mode: role and logout — use signedIn + gates; a logged-out hit on a gated path is a bare 401, log in at / first.)

~1:30. Deltas from V1 oauth2-proxy: primary identifier is X-Auth-Request-User (oid/sub GUID); Email may be absent on Entra (use Preferred-Username); Groups is base64-encoded JSON, not CSV — decode + parse. No Bearer/access-token header is forwarded. sso-demo-eg echoes the headers for debugging.

~1:30. mode: http is the default and the cost win (zero when idle). replicas for always-on. metrics for queue/event-driven workers (KEDA triggers). The cold-start warning prevents the #1 support thread. Health checks next.

~1:30. Rule of thumb: if it's a value, config. If it's sensitive, Key Vault + secrets block. There is no third place.

~2:30. Emphasize granularity: Service Bus access is per-topic and per-direction (send vs receive). storage = the pre-existing shared data accounts, allowlisted by name. Nobody writes Terraform for any of this.

~2:00. If anyone asks "which credential type": always DefaultAzureCredential, no arguments. It works locally too (az login / VS credential) — same code path from laptop to cluster.

~2:30. Pick fresh for greenfield schema work; copy (default) when you want realistic reference data on day one. sourceDatabase is rarely needed — the golden ynm-db-reference-golden is the usual stamp source. platform-demo has working Npgsql + SqlClient samples. MSSQL: drop InvariantGlobalization in csproj.

~1:30. assetCatalog is cluster-scoped (clusters/dev/eu01/values.yaml); gatecheck rejects unknown catalog names. Content updates need a pod restart. container volumes pair with azureAccess.blob; catalog volumes auto-emit AssetsAccess.

~1:30. Test environments = the environments ApplicationSet (wave 31): a file in ynm-environments deploys charts/platform-service into env-<name>. A PR labelled test-env writes that file for your branch → a full, isolated stack with its own URLs and its own DB copy; closing the PR tears it down. Same render as a real service — what you test is what ships. Pairs with the per-env DB copy (prev slide).

~2:00. Build once, promote the digest — the exact bits tested on dev ship to prod. Dev is fully automatic (Kargo direct-pushes the tag). Sandbox/prod is one Promote click → Kargo opens a kargo/promotion/* PR that auto-merges on the green check (branch protection stays enforced, no bypass) → ArgoCD deploys both regions. Rollback = re-promote an older Freight (no rebuild). Canary/blue-green = the rollout field (Argo Rollouts) with automated analysis + auto-abort.

~1:30. Grouped Projects: promotion.project: <name> lands the repo in the shared kargo-grp-<name> Project (Warehouse + Stage per member) instead of a per-repo one. The use case is a product spanning many repos/services (core = monolith-api + frontend + several services). One UI pane, promote together or individually. Group names are registered in servicesPromo.projects; the gatecheck rejects unregistered names. All real services are grouped.

~1:00. The near-term roadmap: (1) Slack integration for build/promotion notifications, (2) onboard CPQ — the first large service, (3) enable test environments for the Younium core services, (4) finalize the sandbox → prod promotion procedures. Demand-driven — invite asks.

~0:30. Close with the action: clone platform-demo, copy the .infrastructure folder, rename things, open your first PR. Offer to pair on the first onboarding.