GitHub Copilot ChatGPT Claude Codex CLI Cursor opencode Skill Text

azure-kubernetes

Plan, create, and configure production-ready Azure Kubernetes Service (AKS) clusters. Covers Day-0 checklist, SKU selection (Automatic vs Standard), networking options (private API server, Azure CNI Overlay, egress configuration), security, and operations (autoscaling, upgrade st

Ciza · 0 points · 23 views 1 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download microsoft-skills-.github_plugins_azure-skills_skills_azure-kubernetes-e58528d.zip · 92 KB
Part of microsoft/skills — 195 skills

Install

skills CLI npx skills add https://github.com/microsoft/skills/tree/main/.github/plugins/azure-skills/skills/azure-kubernetes
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install microsoft-skills@llmmart
Git git clone https://github.com/microsoft/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole microsoft/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Azure Kubernetes Service

AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE

This skill produces a recommended AKS cluster configuration based on user requirements, distinguishing Day-0 decisions (networking, API server — hard to change later) from Day-1 features (can enable post-creation). See CLI reference for commands.

Quick Reference

Property Value
Best for AKS cluster planning and Day-0 decisions
MCP Tools mcp_azure_mcp_aks
CLI az aks create, az aks show, kubectl get, kubectl describe
Related skills azure-kubernetes-app-deploy (deploy an app to an existing cluster), azure-diagnostics (troubleshooting AKS), azure-validate (readiness checks), azure-kubernetes-automatic-readiness (migrate existing cluster to AKS Automatic)

When to Use This Skill

Activate this skill when user wants to:

  • Create a new AKS cluster
  • Plan AKS cluster configuration for production workloads
  • Design AKS networking (API server access, pod IP model, egress)
  • Set up AKS identity and secrets management
  • Configure AKS governance (Azure Policy, Deployment Safeguards)
  • Enable AKS observability (Container Insights, Managed Prometheus, Grafana)
  • Define AKS upgrade and patching strategy
  • Understand AKS Automatic vs Standard SKU differences
  • Get a Day-0 checklist for AKS cluster setup and configuration

Deploying an application to an existing cluster? This skill provisions and configures the cluster. To containerize an app and deploy it to a cluster that already exists (Dockerfile + manifests + Deployment Safeguards), use the azure-kubernetes-app-deploy sub-skill instead.

Rules

  1. Start with the user's requirements for provisioning compute, networking, security, and other settings.
  2. Use the azure MCP server and select mcp_azure_mcp_aks first to discover the exact AKS-specific MCP tools surfaced by the client. Choose the smallest discovered AKS tool that fits the task, and fall back to Azure CLI (az aks) only when the needed functionality is not exposed through the AKS MCP surface.
  3. Determine if AKS Automatic or Standard SKU is more appropriate based on the user's need for control vs convenience. Default to AKS Automatic unless specific customizations are required.
  4. Document decisions and rationale for cluster configuration choices, especially for Day-0 decisions that are hard to change later (networking, API server access).

Required Inputs (Ask only what’s needed)

If the user is unsure, use safe defaults.

  • AKS environment type: dev/test or production
  • Region(s), availability zones, preferred node VM sizes
  • Expected scale (node/cluster count, workload size)
  • Networking requirements (API server access, pod IP model, ingress/egress control)
  • Security and identity requirements, including image registry
  • Upgrade and observability preferences
  • Cost constraints

Workflow

1. Cluster Type

  • AKS Automatic (default): Best for most production workloads, provides a curated experience with pre-configured best practices for security, reliability, and performance. Use unless you have specific custom requirements for networking, autoscaling, or node pool configurations not supported by Node Auto-Provisioning (NAP).
  • AKS Standard: Use if you need full control over environment configuration, which requires additional overhead to set up and manage.

2. Networking (Pod IP, Egress, Ingress, Dataplane)

Pod IP Model (Key Day-0 decision):

  • Azure CNI Overlay (recommended): pod IPs from private overlay range, not VNet-routable, scales to large environments and good for most workloads
  • Azure CNI (VNet-routable): pod IPs directly from VNet (pod subnet or node subnet), use when pods must be directly addressable from VNet or on-prem

Dataplane & Network Policy:

  • Azure CNI powered by Cilium (recommended): eBPF-based for high-performance packet processing, network policies, and observability

Egress:

  • Static Egress Gateway for stable, predictable outbound IPs
  • For restricted egress: UDR + Azure Firewall or NVA

Ingress:

  • App Routing addon with Gateway API — recommended default for HTTP/HTTPS workloads
  • Istio service mesh with Gateway API - for advanced traffic management, mTLS, canary releases
  • Application Gateway for Containers — for L7 load balancing with WAF integration

DNS:

  • Enable LocalDNS on all node pools for reliable, performant DNS resolution

3. Security

  • Use Microsoft Entra ID everywhere (control plane, Workload Identity for pods, node access). Avoid static credentials.
  • Azure Key Vault via Secrets Store CSI Driver for secrets
  • Enable Azure Policy + Deployment Safeguards
  • Enable Encryption at rest for etcd/API server; in-transit for node-to-node
  • Allow only signed, policy-approved images (Azure Policy + Ratify), prefer Azure Container Registry
  • Isolation: Use namespaces, network policies, scoped logging

4. Observability

  • Use Managed Prometheus and Container Insights with Grafana for AKS observability (logs + metrics).
  • Enable Diagnostic Settings to collect control plane logs and audit logs in a Log Analytics workspace for security monitoring and troubleshooting.
  • For other monitoring and troubleshooting tools, use features like the Agentic CLI for AKS, Application Insights, Resource Health Center, AppLens detectors, and Azure Advisors.

5. Upgrades & Patching

  • Configure Maintenance Windows for controlled upgrade timing
  • Enable auto-upgrades for control plane and node OS to stay up-to-date with security patches and Kubernetes versions
  • Consider LTS versions for enterprise stability (2-year support) by upgrading your AKS environment to the Premium tier
  • Fleet upgrades: Use AKS Fleet Manager for staged rollout across test to production environments

6. Performance

  • Use Ephemeral OS disks (--node-osdisk-type Ephemeral) for faster node startup
  • Select Azure Linux as node OS (smaller footprint, faster boot)
  • Enable KEDA for event-driven autoscaling beyond HPA

7. Node Pools & Compute

  • Dedicated system node pool: At least 2 nodes, tainted for system workloads only (CriticalAddonsOnly)
  • Enable Node Auto Provisioning (NAP) on all pools for cost savings and responsive scaling
  • Use latest generation SKUs (v5/v6) for host-level optimizations
  • Avoid B-series VMs — burstable SKUs cause performance/reliability issues
  • Use SKUs with at least 4 vCPUs for production workloads
  • Set topology spread constraints to distribute pods across hosts/zones per SLO

8. Reliability

  • Deploy across 3 Availability Zones (--zones 1 2 3)
  • Use Standard tier for zone-redundant control plane + 99.95% SLA for API server availability
  • Enable Microsoft Defender for Containers for runtime protection
  • Configure PodDisruptionBudgets for all production workloads
  • Use topology spread constraints to ensure pod distribution across failure domains

9. Cost Controls

  • Use Spot node pools for batch/interruptible workloads (up to 90% savings)
  • Stop/Start dev/test clusters: az aks stop/start
  • Consider Reserved Instances or Savings Plans for steady-state workloads

Deep-dive scenarios — load only the relevant reference file:

Scenario Trigger Keywords Reference
Pod Rightsizing over-provisioned pods, CPU requests, memory requests, rightsize workloads azure-aks-rightsizing.md
VPA Setup vertical pod autoscaler, VPA recommendations, VPA enable azure-aks-vpa.md
Cluster Autoscaler idle nodes, CAS off, enable autoscaler, scale-down profile, node utilization azure-aks-autoscaler.md
Spot Node Pools Spot VMs, Spot nodes, batch workloads, cheaper nodes azure-aks-spot.md

Disambiguation: If a prompt matches multiple rows (e.g., "cheaper nodes" could suggest both Spot and autoscaler), prefer the most specific match. If ambiguous, ask the user to clarify their intent before loading a reference file.

Guardrails / Safety

  • Do not request or output secrets (tokens, keys).
  • Do not ask the user to paste subscription IDs. Discover subscription and resource scope via MCP tools (e.g., list subscriptions, list resource groups) or az account show / az account list so the agent can resolve context without exposing identifiers.
  • If requirements are ambiguous for day-0 critical decisions, ask the user clarifying questions. For day-1 enabled features, propose 2–3 safe options with tradeoffs and choose a conservative default.
  • Do not promise zero downtime; advise workload safeguards (PDBs, probes, replicas) and staged upgrades along with best practices for reliability and performance.

MCP Tools

Tool Purpose Key Parameters
mcp_azure_mcp_aks AKS MCP entry point used to discover the exact AKS-specific tools exposed by the client Discover the callable AKS tool first, then use that tool's parameters

Error Handling

Error / Symptom Likely Cause Remediation
MCP tool call fails or times out Invalid credentials, subscription, or AKS context Verify az login, confirm the active subscription context with az account show, and check the target resource group without echoing subscription identifiers back to the user
Quota exceeded Regional vCPU or resource limits Request quota increase or select different region/VM SKU
Networking conflict (IP exhaustion) Pod subnet too small for overlay/CNI Re-plan IP ranges; may require cluster recreation (Day-0)
Workload Identity not working Missing OIDC issuer or federated credential Enable --enable-oidc-issuer --enable-workload-identity, configure federated identity
Files (skills)
  • azure-kubernetes-app-deploy
    • knowledge-packs
      • frameworks
        • aspnet-core.md 7.3 KB
          # ASP.NET Core Knowledge Pack
          
          > **Applies to:** Projects detected with `*.csproj` containing `Microsoft.NET.Sdk.Web` or referencing `Microsoft.AspNetCore.*` packages
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `*.csproj` with `Microsoft.NET.Sdk.Web` or `Microsoft.AspNetCore.*` |
          | Default port | `8080` (.NET 8+) |
          | Health path | `/healthz` + `/ready` |
          | Base template | `templates/dockerfiles/dotnet.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Health Endpoints
          
          ASP.NET Core has built-in health check middleware via `Microsoft.Extensions.Diagnostics.HealthChecks`:
          
          | Endpoint | Purpose | Probe Type |
          |----------|---------|-----------|
          | `/healthz` | Overall health | `livenessProbe` |
          | `/ready` | Dependency readiness | `readinessProbe` |
          
          ### Required configuration
          
          In `Program.cs`:
          
          ```csharp
          var builder = WebApplication.CreateBuilder(args);
          
          // Register health checks
          builder.Services.AddHealthChecks()
              .AddNpgSql(builder.Configuration.GetConnectionString("DefaultConnection")!,
                  name: "postgresql",
                  tags: new[] { "ready" });
          
          var app = builder.Build();
          
          // Map health endpoints
          app.MapHealthChecks("/healthz", new HealthCheckOptions
          {
              Predicate = _ => false // No dependency checks for liveness
          });
          
          app.MapHealthChecks("/ready", new HealthCheckOptions
          {
              Predicate = check => check.Tags.Contains("ready")
          });
          ```
          
          The `AspNetCore.HealthChecks.NpgSql` NuGet package provides the PostgreSQL health check. Install with:
          
          ```bash
          dotnet add package AspNetCore.HealthChecks.NpgSql
          ```
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** ASP.NET Core apps start significantly faster than JVM-based frameworks — `initialDelaySeconds: 5` is typically sufficient.
          
          ---
          
          ## Database Profiles
          
          ASP.NET Core uses configuration providers and Entity Framework Core for database access:
          
          | Configuration Source | Activation | Typical Usage |
          |---------------------|------------|---------------|
          | `appsettings.json` | Default | Local dev with SQLite or LocalDB |
          | `appsettings.Production.json` | `ASPNETCORE_ENVIRONMENT=Production` | Production connection strings |
          | Environment variables | Always override file config | AKS deployments |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: ASPNETCORE_ENVIRONMENT
              value: Production
            - name: ConnectionStrings__DefaultConnection
              value: "Host={{PG_SERVER_NAME}}.postgres.database.azure.com;Database={{DB_NAME}};Username={{IDENTITY_NAME}};Ssl Mode=Require"
          ```
          
          The double-underscore (`__`) in `ConnectionStrings__DefaultConnection` maps to the `:` separator in .NET configuration — `ConnectionStrings:DefaultConnection`.
          
          ### Workload Identity with Azure.Identity
          
          See `references/workload-identity.md` for connection patterns. Requires `Azure.Identity` and `Npgsql.EntityFrameworkCore.PostgreSQL` packages.
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            ASPNETCORE_ENVIRONMENT: "Production"
            ConnectionStrings__DefaultConnection: "Host={{PG_SERVER_NAME}}.postgres.database.azure.com;Database={{DB_NAME}};Ssl Mode=Require"
            DOTNET_EnableDiagnostics: "0"
            DOTNET_RUNNING_IN_CONTAINER: "true"
          ```
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, ASP.NET Core needs `/tmp` writable:
          
          - **Data Protection keys** are written to a local directory by default for key persistence
          - **Temporary files** from multipart uploads and response buffering use `/tmp`
          - **Entity Framework** compiled models may write to temp directories
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          ### Data Protection key persistence
          
          By default, ASP.NET Core Data Protection stores encryption keys in-memory when no persistent path is available, meaning keys are lost on pod restart. This breaks authentication cookies and anti-forgery tokens across pod restarts or in multi-replica deployments.
          
          For production, persist keys to Azure Blob Storage:
          
          ```csharp
          builder.Services.AddDataProtection()
              .PersistKeysToAzureBlobStorage("<connection-string>", "<container>", "<blob-name>")
              .ProtectKeysWithAzureKeyVault(new Uri("<key-vault-uri>"), new DefaultAzureCredential());
          ```
          
          Alternatively, mount a PVC at a known path and configure:
          
          ```csharp
          builder.Services.AddDataProtection()
              .PersistKeysToFileSystem(new DirectoryInfo("/keys"));
          ```
          
          ---
          
          ## Resource Sizing
          
          ASP.NET Core on the .NET runtime is efficient but needs moderate memory for the CLR.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 200m | 500m |
          | Memory | 256Mi | 512Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 8080 (since .NET 8; previously 80 in .NET 7 and earlier)
          - **Env var override:** `ASPNETCORE_URLS=http://+:8080` or `ASPNETCORE_HTTP_PORTS=8080`
          - **Code override:** `builder.WebHost.UseUrls("http://+:8080")` in `Program.cs`
          
          The port change from 80 to 8080 in .NET 8 aligns with non-root container best practices — port 80 requires elevated privileges. Set `DOTNET_EnableDiagnostics=0` to disable diagnostic pipes that require writable paths not available in read-only filesystems.
          
          ---
          
          ## Build Commands
          
          | Variant | Build Command | Output |
          |---------|---------------|--------|
          | Framework-dependent | `dotnet publish -c Release -o ./publish` | `./publish/<app-name>.dll` — requires .NET runtime on target |
          | Self-contained | `dotnet publish -c Release --self-contained -o ./publish` | `./publish/<app-name>` — includes .NET runtime |
          | Single-file | `dotnet publish -c Release --self-contained -p:PublishSingleFile=true -o ./publish` | Single executable binary |
          
          The `-c Release` flag enables compiler optimizations and disables debug symbols — always use it for production builds.
          
          ---
          
          ## EF Core Migrations
          
          Run `dotnet ef database update` as an init container — never in the Dockerfile build stage (no database access) and never in the entrypoint (race condition when multiple replicas start simultaneously).
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | Kestrel bound to port 80 | `CrashLoopBackOff` — permission denied binding to port 80 as non-root | Set `ASPNETCORE_HTTP_PORTS=8080` or upgrade to .NET 8+ which defaults to 8080 |
          | Data Protection keys lost on restart | Users logged out after pod restart, anti-forgery token validation failures | Persist keys to Azure Blob Storage or a PVC — do not rely on in-memory default |
          | EF Core migrations not applied | `NpgsqlException: relation "..." does not exist` | Run `dotnet ef database update` as an init container or at startup with `Database.Migrate()` |
          | Image too large (>500MB) | Slow pulls, high ACR storage | Use self-contained + trimmed publish with the runtime-deps Alpine base image |
          | HTTPS redirect loop behind gateway | Infinite 307/308 redirects, `ERR_TOO_MANY_REDIRECTS` | Disable HTTPS redirection in `Program.cs` when behind a TLS-terminating gateway — configure `ForwardedHeaders` middleware instead |
          
        • django.md 6 KB
          # Django Knowledge Pack
          
          > **Applies to:** Projects detected with `requirements.txt`, `pyproject.toml`, or `Pipfile` containing `django`, or presence of `manage.py`
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `requirements.txt`/`pyproject.toml`/`Pipfile` containing `django`, or `manage.py` |
          | Default port | `8000` (gunicorn) |
          | Health path | `/health/` (django-health-check) |
          | Base template | `templates/dockerfiles/python.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Health Endpoints
          
          Django does not provide health endpoints out of the box. Use the `django-health-check` package:
          
          ### Installation
          
          ```bash
          pip install django-health-check
          ```
          
          ### Configuration in `settings.py`
          
          ```python
          INSTALLED_APPS = [
              # ...existing apps...
              "health_check",
              "health_check.db",
              "health_check.cache",
              "health_check.storage",
              "health_check.contrib.migrations",
          ]
          ```
          
          ### URL configuration in `urls.py`
          
          ```python
          from django.urls import include, path
          
          urlpatterns = [
              # ...existing urls...
              path("health/", include("health_check.urls")),
          ]
          ```
          
          The `/health/` endpoint returns HTTP 200 when all checks pass and HTTP 500 with details when any check fails.
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /health/
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/
              port: 8000
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** `initialDelaySeconds: 10` is sufficient for most Django apps.
          
          ---
          
          ## Database Profiles
          
          Django does not have a built-in profile system like Spring Boot. Database configuration is driven by `settings.py` with environment variables:
          
          | Pattern | How it works |
          |---------|-------------|
          | `dj-database-url` | Parse `DATABASE_URL` env var (recommended for 12-factor apps)
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "postgres://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
            - name: SECRET_KEY
              valueFrom:
                secretKeyRef:
                  name: {{APP_NAME}}-secrets
                  key: secret-key
          ```
          
          **Important:** `SECRET_KEY` must never be in a ConfigMap or hardcoded. Always store it in a Kubernetes Secret (or Key Vault via Workload Identity).
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            DJANGO_SETTINGS_MODULE: "config.settings.production"
            DJANGO_ALLOWED_HOSTS: "{{INGRESS_HOSTNAME}}"
            DATABASE_URL: "postgres://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
          ```
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Django apps need `/tmp` writable and optionally `/app/staticfiles`:
          
          - **`/tmp`** — required for file uploads (`FILE_UPLOAD_TEMP_DIR` defaults to `/tmp`), session data when using file-based sessions, and temporary processing
          - **`/app/staticfiles`** — optional, only needed if serving collected static files at runtime from the local filesystem (when not using WhiteNoise or a CDN)
          
          ### Volume mount configuration
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
            - name: staticfiles
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
                - name: staticfiles
                  mountPath: /app/staticfiles
          ```
          
          If static files are baked into the image at build time via `collectstatic` and served by WhiteNoise, the `staticfiles` volume can be omitted — only `/tmp` is required.
          
          ---
          
          ## Resource Sizing
          
          Django with Gunicorn runs multiple worker processes. Size for the number of workers (default: 2-4).
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 200m | 500m |
          | Memory | 256Mi | 512Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 8000
          - **CLI flag:** `--bind 0.0.0.0:8000` passed to `gunicorn`
          - **Env var override:** `PORT` (read via `gunicorn --bind 0.0.0.0:$PORT` or `int(os.environ.get("PORT", 8000))`)
          - **Workers formula:** `2 * CPU_CORES + 1` (e.g. `--workers 3` for a 1-vCPU container)
          - **WSGI module path** varies by project scaffold: `config.wsgi:application`, `myproject.wsgi:application`, or `app.wsgi:application` — check `wsgi.py` location
          
          Gunicorn logs the port on startup: `Listening at: http://0.0.0.0:8000`
          
          ---
          
          ## Build Commands
          
          | Command | Purpose | When to run |
          |---------|---------|-------------|
          | `python manage.py collectstatic --noinput` | Gathers static files into `STATIC_ROOT` | In Dockerfile build stage (with `SECRET_KEY=build-placeholder`) |
          | `python manage.py migrate --noinput` | Applies database migrations | As a Kubernetes init container — **never in the Dockerfile** |
          
          **Important:** Database migrations must run as an init container, not during the Docker build. The build stage has no access to the production database, and running migrations in the entrypoint creates race conditions when multiple replicas start simultaneously.
          
          ### Init container for migrations
          
          ```yaml
          initContainers:
            - name: migrate
              image: {{ACR_NAME}}.azurecr.io/{{APP_NAME}}:{{TAG}}
              command: ["python", "manage.py", "migrate", "--noinput"]
              envFrom:
                - configMapRef:
                    name: {{APP_NAME}}-config
                - secretRef:
                    name: {{APP_NAME}}-secrets
          ```
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | `collectstatic` not run | Static files 404 | Run `python manage.py collectstatic --noinput` in Dockerfile build stage |
          | `ALLOWED_HOSTS` not set | `DisallowedHost` error | Set `DJANGO_ALLOWED_HOSTS` env var |
          | Dev server in production | Single-threaded, no security | Use `gunicorn` in ENTRYPOINT |
          | Migrations not applied | `relation "..." does not exist` | Run `manage.py migrate` as init container |
          | `SECRET_KEY` not set | `ImproperlyConfigured` error | Store in Kubernetes Secret |
          | Static files 404 in production | CSS/JS/images not loading | Use WhiteNoise or CDN for static files
          
        • express.md 6.7 KB
          # Express / Fastify Knowledge Pack
          
          > **Applies to:** Projects detected with `package.json` containing `express` or `fastify` as a dependency
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `package.json` containing `express` or `fastify` |
          | Default port | `3000` |
          | Health path | `/healthz` |
          | Base template | `templates/dockerfiles/node.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Signal Handling
          
          Node.js does not handle `SIGTERM` correctly when running as PID 1. The base template includes `dumb-init` as the entrypoint to forward signals properly; no application-level changes are needed unless the app registers explicit cleanup handlers.
          
          ### Fastify listen caveat
          
          Fastify defaults to listening on `127.0.0.1`, which is unreachable from outside the container. Bind to `0.0.0.0` explicitly:
          
          ```js
          await fastify.listen({ port: 3000, host: '0.0.0.0' });
          ```
          
          If the pod starts but health probes fail with `connection refused`, this is almost always the cause. Express already binds to `0.0.0.0` by default — no change needed for Express apps.
          
          ### Package manager variants
          
          | Package Manager | Install (all) | Install (prod only) | Lock File |
          |----------------|---------------|---------------------|-----------|
          | npm | `npm ci` | `npm ci --omit=dev` | `package-lock.json` |
          | yarn | `yarn install --frozen-lockfile` | `yarn install --frozen-lockfile --production` | `yarn.lock` |
          | pnpm | `pnpm install --frozen-lockfile` | `pnpm install --frozen-lockfile --prod` | `pnpm-lock.yaml` |
          
          Copy the correct lock file in the Dockerfile `COPY` step to match the project's package manager.
          
          ---
          
          ## Health Endpoints
          
          Node.js frameworks do not provide health endpoints out of the box. Add a `/healthz` route manually.
          
          ### Express
          
          ```js
          app.get('/healthz', (req, res) => {
            res.status(200).json({ status: 'UP' });
          });
          ```
          
          ### Fastify
          
          ```js
          fastify.get('/healthz', async () => {
            return { status: 'UP' };
          });
          ```
          
          For richer checks (database connectivity, downstream services), extend the handler to verify dependencies and return `503` when unhealthy.
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** Node.js apps start in under a second, so `initialDelaySeconds: 5` is sufficient. No `startupProbe` is needed unless the app performs heavy initialization (e.g., loading ML models).
          
          ---
          
          ## Database Profiles
          
          Node.js projects use a variety of database libraries. The standard pattern is a `DATABASE_URL` connection string injected via environment variable:
          
          | Library | Connection Pattern | Config Property |
          |---------|-------------------|-----------------|
          | `pg` (node-postgres) | `new Pool({ connectionString: process.env.DATABASE_URL })` | `DATABASE_URL` |
          | Prisma | `datasource db { url = env("DATABASE_URL") }` in `schema.prisma` | `DATABASE_URL` |
          | Sequelize | `new Sequelize(process.env.DATABASE_URL)` | `DATABASE_URL` |
          | Knex | `connection: process.env.DATABASE_URL` in `knexfile.js` | `DATABASE_URL` |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "postgresql://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
            - name: PGHOST
              value: "{{PG_SERVER_NAME}}.postgres.database.azure.com"
            - name: PGDATABASE
              value: "{{DB_NAME}}"
            - name: PGUSER
              value: "{{IDENTITY_NAME}}"
            - name: PGPORT
              value: "5432"
            - name: PGSSLMODE
              value: "require"
          ```
          
          For Workload Identity with passwordless authentication, use the `@azure/identity` package with `pg` to obtain Azure AD tokens instead of passwords.
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Node.js apps need only `/tmp` writable:
          
          - **Multipart uploads** (e.g., `multer`, `@fastify/multipart`) stage files to `/tmp`
          - **Logging libraries** that buffer to disk use `/tmp`
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          ---
          
          ## Resource Sizing
          
          Node.js is single-threaded and relatively lightweight. These are starting-point defaults — tune based on observed usage.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 100m | 500m |
          | Memory | 128Mi | 256Mi |
          
          For memory-intensive workloads (large payloads, SSR), increase the memory limit and set `--max-old-space-size` to ~75% of the limit.
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 3000
          - **Env var override:** `PORT=3000`
          - **Code pattern:** `app.listen(process.env.PORT || 3000)`
          
          Express binds to `0.0.0.0` by default, so it is reachable from outside the container without additional configuration.
          
          Fastify binds to `127.0.0.1` by default — **you must pass `host: '0.0.0.0'`** in the `listen()` call or the pod will start but all probes and traffic will fail with `connection refused`.
          
          ---
          
          ## Build Commands
          
          | Scenario | Build Command | Output | Entrypoint |
          |----------|---------------|--------|------------|
          | TypeScript | `npm run build` (invokes `tsc`) | `dist/` | `node dist/index.js` |
          | JavaScript (no build) | None | `src/` | `node src/index.js` |
          | Bundler (esbuild/webpack) | `npm run build` | `dist/bundle.js` | `node dist/bundle.js` |
          
          For TypeScript projects, ensure `tsconfig.json` has `"outDir": "dist"` and the Dockerfile copies the `dist/` folder to the runtime stage. Do **not** install `typescript` or `ts-node` in the production image.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | No SIGTERM handling | Pod takes 30s to terminate (killed by `SIGKILL` after grace period) | Use `dumb-init` as entrypoint, or add explicit `process.on('SIGTERM', ...)` handler to close the server gracefully |
          | ECONNRESET on PostgreSQL | `Error: Connection terminated unexpectedly` | Configure pool `idleTimeoutMillis` and `connectionTimeoutMillis`; Azure PG Flexible Server closes idle connections after ~5 min |
          | Fastify localhost binding | Health probes fail with `connection refused` despite app running | Pass `host: '0.0.0.0'` to `fastify.listen()` — Fastify defaults to `127.0.0.1` |
          | node_modules bloat | Image > 500MB, slow pulls from ACR | Run `npm ci --omit=dev` in a separate stage; consider esbuild bundling for single-file output |
          | Memory leak under load | Pod `OOMKilled` after hours of traffic | Set `--max-old-space-size` to ~75% of container memory limit (e.g., `--max-old-space-size=384` for 512Mi limit); profile with `--inspect` locally |
          
        • fastapi.md 5.8 KB
          # FastAPI Knowledge Pack
          
          > **Applies to:** Projects detected with `requirements.txt`, `pyproject.toml`, or `Pipfile` containing `fastapi`
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `requirements.txt`/`pyproject.toml`/`Pipfile` containing `fastapi` |
          | Default port | `8000` |
          | Health path | `/health` + `/ready` |
          | Base template | `templates/dockerfiles/python.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Health Endpoints
          
          FastAPI health endpoints must be defined explicitly in application code:
          
          ### Minimal health route
          
          ```python
          from fastapi import FastAPI
          
          app = FastAPI()
          
          @app.get("/health")
          async def health():
              return {"status": "ok"}
          ```
          
          ### Readiness route with database check
          
          ```python
          from fastapi import FastAPI, status
          from fastapi.responses import JSONResponse
          from sqlalchemy.ext.asyncio import AsyncSession
          
          @app.get("/ready")
          async def ready(db: AsyncSession = Depends(get_db)):
              try:
                  await db.execute(text("SELECT 1"))
                  return {"status": "ready"}
              except Exception:
                  return JSONResponse(
                      status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
                      content={"status": "not ready"},
                  )
          ```
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** FastAPI apps start quickly (typically <2s), so `initialDelaySeconds: 5` is sufficient — much lower than JVM-based frameworks.
          
          ---
          
          ## Database Profiles
          
          FastAPI does not have a built-in profile system. Database configuration is typically driven by environment variables:
          
          | ORM / Driver | Package(s) | Connection String Format |
          |-------------|-----------|--------------------------|
          | SQLAlchemy async + asyncpg | `sqlalchemy[asyncio]`, `asyncpg` | `postgresql+asyncpg://user:pass@host:5432/db` |
          | Tortoise ORM | `tortoise-orm`, `asyncpg` | `postgres://user:pass@host:5432/db` |
          | SQLModel | `sqlmodel`, `asyncpg` | `postgresql+asyncpg://user:pass@host:5432/db` |
          | asyncpg direct | `asyncpg` | `postgresql://user:pass@host:5432/db` |
          
          **Important:** SQLAlchemy async requires the `+asyncpg` suffix in the connection URL scheme (`postgresql+asyncpg://`). Omitting it will default to the synchronous `psycopg2` driver, which blocks the event loop.
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "postgresql+asyncpg://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
          ```
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            DATABASE_URL: "postgresql+asyncpg://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
            UVICORN_WORKERS: "1"
          ```
          
          ### Workload Identity with azure-identity
          
          See `references/workload-identity.md` for connection patterns. Requires `azure-identity` package.
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, FastAPI apps typically only need `/tmp` writable:
          
          - **Uploaded files** use `/tmp` as the default staging directory for `UploadFile`
          - **Temporary processing** may write intermediate results to `/tmp`
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          No other writable paths are typically needed for production FastAPI apps.
          
          ---
          
          ## Resource Sizing
          
          FastAPI with Uvicorn is async and lightweight. Size for workload concurrency.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 100m | 500m |
          | Memory | 128Mi | 256Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 8000
          - **CLI flag:** `--host 0.0.0.0 --port 8000` passed to `uvicorn` — must include `--host 0.0.0.0` (uvicorn defaults to `127.0.0.1`, unreachable from Kubernetes probes)
          - **Workers:** use `--workers 1` for pure-async apps (async code uses a single event loop); `2 * CPU_CORES + 1` only for sync/blocking handlers
          - **Env var override:** `PORT` (read via `uvicorn --port $PORT` or `int(os.environ.get("PORT", 8000))`)
          
          Uvicorn logs the port on startup: `Uvicorn running on http://0.0.0.0:8000`
          
          ---
          
          ## Build Commands
          
          | Tool | Install Command | Output |
          |------|----------------|--------|
          | pip | `pip install --no-cache-dir -r requirements.txt` | Packages in site-packages |
          | Poetry | `poetry install --only main --no-interaction` | Packages in virtualenv |
          | uv | `uv sync --frozen --no-dev` | Packages in virtualenv |
          
          The `--no-cache-dir` flag (pip) and `--no-interaction` flag (Poetry) suppress interactive prompts — important for CI/CD and Docker builds.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | Uvicorn workers misconfigured | High latency under load, single-core CPU usage | Set `--workers` to `2 * CPU_CORES + 1` for sync code, or `1` when using async handlers (async code uses a single event loop) |
          | Async DB pool exhaustion | `asyncpg.exceptions.TooManyConnectionsError` | Configure pool size with `create_async_engine(pool_size=5, max_overflow=10)` and match PostgreSQL `max_connections` |
          | Alpine build fails | `gcc` errors installing `cryptography`, `psycopg2`, `numpy` | Use the Debian-slim Python base (see `references/base-images.md`) instead of Alpine |
          | Uvicorn binds to localhost | Connection refused from Kubernetes probes | Set `--host 0.0.0.0` — uvicorn defaults to `127.0.0.1` which is unreachable from outside the container |
          | Missing uvicorn in production | `ModuleNotFoundError: No module named 'uvicorn'` | Ensure `uvicorn[standard]` is in `requirements.txt` — it is often only in dev dependencies |
          
        • flask.md 6.2 KB
          # Flask Knowledge Pack
          
          > **Applies to:** Projects detected with `requirements.txt`, `pyproject.toml`, or `Pipfile` containing `flask`
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `requirements.txt`/`pyproject.toml`/`Pipfile` containing `flask` |
          | Default port | `8000` prod (`5000` dev — never in prod) |
          | Health path | `/health` + `/ready` |
          | Base template | `templates/dockerfiles/python.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Health Endpoints
          
          Flask does not include health check endpoints — they must be defined explicitly in application code:
          
          ### Minimal health route
          
          ```python
          from flask import Flask, jsonify
          
          app = Flask(__name__)
          
          @app.route("/health")
          def health():
              return jsonify(status="ok"), 200
          ```
          
          ### Readiness route with database check
          
          ```python
          from flask import jsonify
          from sqlalchemy import text
          
          @app.route("/ready")
          def ready():
              try:
                  db.session.execute(text("SELECT 1"))
                  return jsonify(status="ready"), 200
              except Exception:
                  return jsonify(status="not ready"), 503
          ```
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** Flask apps behind gunicorn start quickly (typically <3s), so `initialDelaySeconds: 5` is sufficient — much lower than JVM-based frameworks.
          
          ---
          
          ## Database Profiles
          
          Flask does not have a built-in profile system. Database configuration is typically driven by environment variables:
          
          | ORM / Driver | Package(s) | Connection String Env Var |
          |-------------|-----------|--------------------------|
          | Flask-SQLAlchemy | `flask-sqlalchemy`, `psycopg2-binary` | `SQLALCHEMY_DATABASE_URI` |
          | SQLAlchemy direct | `sqlalchemy`, `psycopg2-binary` | `DATABASE_URL` |
          | psycopg2 direct | `psycopg2-binary` | `DATABASE_URL` |
          
          **Important:** Flask-SQLAlchemy reads the connection string from `app.config["SQLALCHEMY_DATABASE_URI"]`, which is typically set via `os.environ.get("SQLALCHEMY_DATABASE_URI")` or `os.environ.get("DATABASE_URL")`. Ensure the env var name matches what the app expects.
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: SQLALCHEMY_DATABASE_URI
              value: "postgresql://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
            - name: SECRET_KEY
              valueFrom:
                secretKeyRef:
                  name: {{APP_NAME}}-secrets
                  key: secret-key
          ```
          
          ### Secret for SECRET_KEY
          
          Flask requires `SECRET_KEY` for session signing, CSRF tokens, and any use of `flask.session`. Never hardcode it — store it in a Kubernetes Secret:
          
          ```yaml
          apiVersion: v1
          kind: Secret
          metadata:
            name: {{APP_NAME}}-secrets
          type: Opaque
          stringData:
            secret-key: "<generate-a-random-string>"
          ```
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            SQLALCHEMY_DATABASE_URI: "postgresql://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
          ```
          
          ### Workload Identity with azure-identity
          
          See `references/workload-identity.md` for connection patterns. Requires `azure-identity` package.
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Flask apps typically only need `/tmp` writable:
          
          - **Uploaded files** use `/tmp` as the default staging directory for `request.files`
          - **Temporary processing** may write intermediate results to `/tmp`
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          No other writable paths are typically needed for production Flask apps.
          
          ---
          
          ## Resource Sizing
          
          Flask with Gunicorn runs multiple worker processes. Size for the number of workers (default: 2-4).
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 150m | 500m |
          | Memory | 128Mi | 256Mi |
          
          ---
          
          ## Port Configuration
          
          - **Development port:** 5000 (`flask run` default — do not use in production)
          - **Production port:** 8000 (gunicorn convention)
          - **CLI flag:** `--bind 0.0.0.0:8000` passed to `gunicorn`
          - **Env var override:** `PORT` (read via `gunicorn --bind 0.0.0.0:$PORT` or in app code `int(os.environ.get("PORT", 8000))`)
          - **Workers formula:** `2 * CPU_CORES + 1` — override at runtime via `WEB_CONCURRENCY` env var
          - **Entry point variants:** `gunicorn "app:app"` (module-level) or `gunicorn "myapp:create_app()"` (application factory)
          
          Gunicorn logs the port on startup: `Listening at: http://0.0.0.0:8000`
          
          ---
          
          ## Build Commands
          
          | Tool | Install Command |
          |------|----------------|
          | pip | `pip install --no-cache-dir -r requirements.txt` |
          | Poetry | `poetry install --only main --no-interaction` |
          
          Ensure `gunicorn` is listed in `requirements.txt` or `pyproject.toml` production dependencies.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | Running dev server in production | Single-threaded, poor performance, `WARNING: This is a development server` in logs | Use `gunicorn` as the ENTRYPOINT — never use `flask run` or `app.run()` in production containers |
          | `SECRET_KEY` not set | `RuntimeError: The session is unavailable because no secret key was set`, CSRF failures | Set `SECRET_KEY` via a Kubernetes Secret and reference it as an env var in the Deployment manifest |
          | Flask binds to localhost | Connection refused from Kubernetes probes | Pass `--bind 0.0.0.0:8000` to gunicorn — the Flask dev server defaults to `127.0.0.1` which is unreachable from outside the container |
          | Gunicorn not installed | `ModuleNotFoundError: No module named 'gunicorn'` | Ensure `gunicorn` is in `requirements.txt` or `pyproject.toml` main dependencies — it is often only in dev dependencies or missing entirely |
          | DB connections not closed | `sqlalchemy.exc.TimeoutError: QueuePool limit`, PostgreSQL `max_connections` exhaustion | Configure pool size with `SQLALCHEMY_ENGINE_OPTIONS = {"pool_size": 5, "max_overflow": 10, "pool_recycle": 300}` and match PostgreSQL `max_connections` |
          
        • go.md 6.8 KB
          # Go Knowledge Pack
          
          > **Applies to:** Projects detected with `go.mod` containing `github.com/gin-gonic/gin`, `github.com/labstack/echo`, `github.com/gofiber/fiber`, or any Go project using the standard library `net/http` for HTTP serving
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `go.mod` (gin/echo/fiber or stdlib `net/http`) |
          | Default port | `8080` |
          | Health path | `/healthz` + `/ready` |
          | Base template | `templates/dockerfiles/go.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Build Flags
          
          Two flags are required for a correct production build:
          
          - **`CGO_ENABLED=0`** produces a fully static binary with no libc dependency — required when targeting the distroless static image. If CGO is needed (e.g., for sqlite3 or cgo bindings), use the distroless cc image instead.
          - **`-ldflags="-s -w"`** strips debug symbols and DWARF info, reducing binary size by ~30%.
          
          ---
          
          ## Health Endpoints
          
          Go does not provide health check endpoints out of the box — you must implement them manually. Example using standard library:
          
          ```go
          http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
              w.WriteHeader(http.StatusOK)
              w.Write([]byte(`{"status":"ok"}`))
          })
          http.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
              if err := db.Ping(); err != nil {
                  w.WriteHeader(http.StatusServiceUnavailable)
                  w.Write([]byte(`{"status":"not ready"}`))
                  return
              }
              w.WriteHeader(http.StatusOK)
              w.Write([]byte(`{"status":"ready"}`))
          })
          ```
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 3
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 3
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** Go binaries start in milliseconds — `initialDelaySeconds: 3` is generous. No JVM warmup or interpreter startup to wait for.
          
          ---
          
          ## Graceful Shutdown
          
          Implement `signal.NotifyContext` with `srv.Shutdown(ctx)` to allow in-flight requests to complete before the pod exits during a rolling update. Without this, connections are dropped and callers receive 502 errors.
          
          ---
          
          ## Database Profiles
          
          Go does not have a built-in profile system. Database configuration is typically driven by environment variables:
          
          | Library | Driver | Connection Env Var |
          |---------|--------|--------------------|
          | `database/sql` + `pgx` | `github.com/jackc/pgx/v5/stdlib` | `DATABASE_URL` |
          | GORM | `gorm.io/driver/postgres` | `DATABASE_URL` |
          | sqlx | `github.com/jmoiron/sqlx` + `pgx` | `DATABASE_URL` |
          | pgx direct | `github.com/jackc/pgx/v5` | `DATABASE_URL` |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "host={{PG_SERVER_NAME}}.postgres.database.azure.com port=5432 dbname={{DB_NAME}} user={{IDENTITY_NAME}} sslmode=require"
          ```
          
          ### Workload Identity with pgx
          
          Use `azidentity` to obtain Azure AD tokens and inject them via pgx's `BeforeConnect` hook — no password stored:
          
          ```go
          import (
              "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
              "github.com/jackc/pgx/v5"
          )
          
          cred, _ := azidentity.NewDefaultAzureCredential(nil)
          
          config, _ := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
          config.BeforeConnect = func(ctx context.Context, cfg *pgx.ConnConfig) error {
              token, err := cred.GetToken(ctx, policy.TokenRequestOptions{
                  Scopes: []string{"https://ossrdbms-aad.database.windows.net/.default"},
              })
              if err != nil {
                  return err
              }
              cfg.Password = token.Token
              return nil
          }
          ```
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            DATABASE_URL: "host={{PG_SERVER_NAME}}.postgres.database.azure.com port=5432 dbname={{DB_NAME}} user={{IDENTITY_NAME}} sslmode=require"
          ```
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Go apps typically need **no writable paths**:
          
          - Go compiles to a static binary — no temp files, no interpreted bytecode, no session storage
          - The distroless static base image has no shell or package manager that writes to disk
          
          ### Optional `/tmp` mount
          
          If your application explicitly writes temporary files (e.g., file uploads, report generation):
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          Most Go web APIs do not need this.
          
          ---
          
          ## Resource Sizing
          
          Go compiles to a static binary with no runtime — it is the most resource-efficient option.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 50m | 200m |
          | Memory | 64Mi | 128Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 8080 (Go convention, not enforced by any framework)
          - **Env var override:** `PORT` (commonly used pattern)
          - **Bind port >= 1024** — lower ports require elevated privileges; running as non-root (uid 65534) means port 80 or 443 will fail with `permission denied`.
          
          ### Code pattern
          
          ```go
          port := os.Getenv("PORT")
          if port == "" {
              port = "8080"
          }
          log.Printf("Listening on :%s", port)
          log.Fatal(http.ListenAndServe(":"+port, router))
          ```
          
          All major Go frameworks (Gin, Echo, Fiber) accept the listen address as a string — no special configuration property needed.
          
          ---
          
          ## Build Commands
          
          | Variant | Command | Notes |
          |---------|---------|-------|
          | Standard | `CGO_ENABLED=0 go build -ldflags="-s -w" -o server ./cmd/server` | Production binary, stripped |
          | Race detector (test only) | `go build -race -o server ./cmd/server` | Do **not** use in production — 10x overhead |
          | Multiple binaries | `CGO_ENABLED=0 go build -ldflags="-s -w" -o migrate ./cmd/migrate` | Build each binary target separately |
          
          The `./cmd/server` path is conventional for Go projects using the [Standard Go Project Layout](https://github.com/golang-standards/project-layout). Adjust to match the actual `main` package location.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | Binary not statically linked | `exec format error` or `not found` in distroless | Ensure `CGO_ENABLED=0` is set during build; if CGO is required, use the distroless cc image instead of the distroless static image |
          | DNS resolution issues during build | `dial tcp: lookup ... no such host` | Add `ca-certificates` to the build stage or use a Debian-based build image |
          | Graceful shutdown not implemented | Connections dropped during rolling update, 502 errors | Implement `signal.NotifyContext` with `srv.Shutdown(ctx)` — give in-flight requests time to complete before exit |
          | Binary name mismatch | `exec /server: no such file or directory` | Verify the `-o` flag in `go build` matches the `ENTRYPOINT` path in the Dockerfile |
          | Port < 1024 with non-root user | `bind: permission denied` | Use port 8080 (or any port >= 1024); never bind to 80 or 443 inside the container |
          
        • nestjs.md 6.7 KB
          # NestJS Knowledge Pack
          
          > **Applies to:** Projects detected with `package.json` containing `@nestjs/core` as a dependency
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `package.json` containing `@nestjs/core` |
          | Default port | `3000` |
          | Health path | `/health` |
          | Base template | `templates/dockerfiles/node.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Signal Handling
          
          NestJS lifecycle events (`OnModuleDestroy`, `BeforeApplicationShutdown`) fire only when shutdown hooks are enabled. Call `app.enableShutdownHooks()` in `main.ts` so `SIGTERM` from Kubernetes triggers graceful teardown of HTTP connections, database pools, and message queue consumers:
          
          ```typescript
          const app = await NestFactory.create(AppModule);
          app.enableShutdownHooks();
          await app.listen(process.env.PORT || 3000);
          ```
          
          The base template uses `dumb-init` as the entrypoint to forward `SIGTERM` to the Node process when running as PID 1. Both are required: `dumb-init` routes the signal, `enableShutdownHooks()` handles it.
          
          ---
          
          ## Health Endpoints
          
          NestJS provides health checks via the `@nestjs/terminus` package.
          
          ### Installation
          
          ```bash
          npm install @nestjs/terminus
          ```
          
          ### HealthModule
          
          ```typescript
          import { Module } from '@nestjs/common';
          import { TerminusModule } from '@nestjs/terminus';
          import { HealthController } from './health.controller';
          
          @Module({
            imports: [TerminusModule],
            controllers: [HealthController],
          })
          export class HealthModule {}
          ```
          
          Register `HealthModule` in `AppModule` imports.
          
          ### HealthController with database check
          
          ```typescript
          import { Controller, Get } from '@nestjs/common';
          import { HealthCheck, HealthCheckService, TypeOrmHealthIndicator } from '@nestjs/terminus';
          
          @Controller('health')
          export class HealthController {
            constructor(private health: HealthCheckService, private db: TypeOrmHealthIndicator) {}
            @Get()
            @HealthCheck()
            check() {
              return this.health.check([() => this.db.pingCheck('database')]);
            }
          }
          ```
          
          For Prisma, use `PrismaHealthIndicator`; for MikroORM, use `MikroOrmHealthIndicator`. If no database is used, omit the indicator and return a simple status check.
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health
              port: 3000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** NestJS apps start quickly (typically under 2 seconds), so `initialDelaySeconds: 5` is sufficient. If the app performs heavy initialization (e.g., loading large config, running migrations), increase to 10–15s or add a `startupProbe`.
          
          ---
          
          ## Database Profiles
          
          NestJS supports multiple ORM libraries. The standard pattern is a connection string or individual env vars injected via environment variables:
          
          | ORM | Connection Pattern | Config Property |
          |-----|-------------------|-----------------|
          | TypeORM | `TypeOrmModule.forRoot({ url: process.env.DATABASE_URL })` | `DATABASE_URL` |
          | Prisma | `datasource db { url = env("DATABASE_URL") }` in `schema.prisma` | `DATABASE_URL` |
          | MikroORM | `MikroOrmModule.forRoot({ clientUrl: process.env.DATABASE_URL })` | `DATABASE_URL` |
          | Sequelize | `SequelizeModule.forRoot({ uri: process.env.DATABASE_URL })` | `DATABASE_URL` |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "postgresql://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
          ```
          
          For Workload Identity, see `references/workload-identity.md`.
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, NestJS apps need only `/tmp` writable:
          
          - **Multipart uploads** (e.g., `@nestjs/platform-express` with `multer`) stage files to `/tmp`
          - **Logging libraries** that buffer to disk use `/tmp`
          - **`node_modules` and `dist/`** are read-only at runtime
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          ---
          
          ## Resource Sizing
          
          NestJS is Node.js-based and single-threaded. Similar to Express/Fastify.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 100m | 500m |
          | Memory | 128Mi | 256Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 3000
          - **Env var override:** `PORT=3000`
          - **Code pattern:** `await app.listen(process.env.PORT || 3000)` in `main.ts`
          
          NestJS (via Express adapter) binds to `0.0.0.0` by default. For Fastify adapter, pass `'0.0.0.0'` explicitly: `await app.listen(process.env.PORT || 3000, '0.0.0.0')`.
          
          ---
          
          ## Build Commands
          
          | Scenario | Build Command | Output | Entrypoint |
          |----------|---------------|--------|------------|
          | Standard | `npm run build` (invokes `nest build`) | `dist/` | `node dist/main.js` |
          | Monorepo | `npx nest build <app-name>` | `dist/apps/<app-name>/` | `node dist/apps/<app-name>/main.js` |
          | SWC compiler | `nest build --builder swc` | `dist/` | `node dist/main.js` |
          
          The **SWC compiler** is ~20x faster than the default TypeScript compiler for large projects. Enable it by installing `@swc/cli @swc/core` and passing `--builder swc` or setting `"builder": "swc"` in `nest-cli.json`. SWC does not perform type checking — run `tsc --noEmit` separately in CI if type safety is required.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | SIGTERM not handled | Pod takes 30s to terminate (killed by `SIGKILL` after grace period) | Call `app.enableShutdownHooks()` in `main.ts` so NestJS lifecycle events (`OnModuleDestroy`, `BeforeApplicationShutdown`) fire on `SIGTERM`; also use `dumb-init` as the container entrypoint |
          | TypeORM connection pool exhaustion | `Error: Connection pool exhausted` or `ETIMEDOUT` under load | Set `extra: { max: 10 }` in TypeORM config to limit pool size; Azure PG Flexible Server has a connection limit based on SKU — monitor with `pg_stat_activity` |
          | Circular dependency | `Error: Nest cannot create the ... instance` at startup | Use `forwardRef(() => Module)` in module imports; refactor shared logic into a dedicated module to break the cycle |
          | dist/ not included in image | `Error: Cannot find module '/app/dist/main.js'` at container start | Ensure `COPY --from=build /app/dist ./dist` is present in the Dockerfile runtime stage; verify `nest build` runs successfully in the build stage |
          | Global prefix breaks probes | Health probes return `404` after setting `app.setGlobalPrefix('api')` | The health endpoint moves to `/api/health` — update probe paths in the Deployment manifest, or exclude the health controller from the global prefix using `app.setGlobalPrefix('api', { exclude: ['health'] })` |
          
        • nextjs.md 7.1 KB
          # Next.js Knowledge Pack
          
          > **Applies to:** Projects detected with `package.json` containing `next` as a dependency
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `package.json` containing `next` |
          | Default port | `3000` |
          | Health path | `/api/health` |
          | Base template | `templates/dockerfiles/node.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Standalone Output (Required)
          
          Next.js standalone output mode is critical for containerized deployments — it reduces the image from ~1GB to ~100MB by bundling only the files needed to run the server. Without it, the build copies all of `node_modules` into the image.
          
          Enable it in `next.config.js` (or `next.config.mjs` / `next.config.ts`):
          
          ```js
          /** @type {import('next').NextConfig} */
          const nextConfig = {
            output: 'standalone',
          };
          
          module.exports = nextConfig;
          ```
          
          The build then produces `.next/standalone/server.js`, a self-contained HTTP server that does not require the `next` CLI at runtime.
          
          ### Three required COPY targets
          
          The Dockerfile runtime stage needs exactly three items from the build stage:
          
          1. `public/` — static assets served directly
          2. `.next/standalone/` — the standalone server and its bundled dependencies
          3. `.next/static/` — client-side JS/CSS bundles (must be copied into `.next/static` inside the standalone directory, not alongside it)
          
          ### Hostname binding
          
          Set `HOSTNAME="0.0.0.0"` as a runtime environment variable (required for Next.js 14+). The standalone `server.js` reads this variable on startup to listen on all interfaces. Without it, the server binds to `127.0.0.1` and Kubernetes probes fail.
          
          Set `NEXT_TELEMETRY_DISABLED=1` in both the build stage and the runtime stage to prevent outbound telemetry calls to `telemetry.nextjs.org` from the container.
          
          ---
          
          ## Health Endpoints
          
          Next.js does not provide health endpoints out of the box. Add a custom API route — the implementation depends on whether the project uses App Router or Pages Router.
          
          ### App Router (Next.js 13.4+)
          
          Create `app/api/health/route.ts`:
          
          ```ts
          import { NextResponse } from 'next/server';
          
          export async function GET() {
            return NextResponse.json({ status: 'UP' });
          }
          
          export const dynamic = 'force-dynamic';
          ```
          
          The `force-dynamic` export prevents Next.js from statically caching the health response at build time.
          
          ### Pages Router
          
          Create `pages/api/health.ts`:
          
          ```ts
          import type { NextApiRequest, NextApiResponse } from 'next';
          
          export default function handler(req: NextApiRequest, res: NextApiResponse) {
            res.status(200).json({ status: 'UP' });
          }
          ```
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          livenessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /api/health
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Note:** Next.js standalone server starts in 1–3 seconds, but `initialDelaySeconds: 10` provides a safe margin for cold starts and environment variable resolution. No `startupProbe` is needed unless the app performs heavy server-side initialization.
          
          ---
          
          ## Database Profiles
          
          Next.js apps commonly use Prisma, Drizzle, or `pg` (node-postgres) for database access. All follow the `DATABASE_URL` connection string pattern:
          
          | Library | Connection Pattern | Config Property |
          |---------|-------------------|-----------------|
          | Prisma | `datasource db { url = env("DATABASE_URL") }` in `schema.prisma` | `DATABASE_URL` |
          | Drizzle | `postgres(process.env.DATABASE_URL!)` or `drizzle(process.env.DATABASE_URL!)` | `DATABASE_URL` |
          | `pg` (node-postgres) | `new Pool({ connectionString: process.env.DATABASE_URL })` | `DATABASE_URL` |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: DATABASE_URL
              value: "postgresql://{{IDENTITY_NAME}}@{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}?sslmode=require"
          ```
          
          For Workload Identity, see `references/workload-identity.md`.
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Next.js needs **two** writable paths:
          
          - **`/tmp`** — general-purpose temporary file storage
          - **`/app/.next/cache`** — ISR (Incremental Static Regeneration) page cache and `next/image` optimization cache; without this, ISR and image optimization fail with `EROFS: read-only file system` errors
          
          ### Required volume mounts
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
            - name: next-cache
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
                - name: next-cache
                  mountPath: /app/.next/cache
          ```
          
          Both mounts are required. Missing the cache mount is the most common cause of ISR failures on AKS.
          
          ---
          
          ## Resource Sizing
          
          Next.js SSR needs more memory than a plain API due to React rendering. Static-only exports can use lower limits.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 200m | 1000m |
          | Memory | 256Mi | 512Mi |
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 3000
          - **Env var override:** `PORT=3000`
          - **Hostname binding:** `HOSTNAME="0.0.0.0"` (Next.js 14+)
          
          The standalone `server.js` reads the `PORT` and `HOSTNAME` environment variables automatically. No code changes are needed to customize the port.
          
          ---
          
          ## Build Commands
          
          | Scenario | Build Command | Output | Entrypoint |
          |----------|---------------|--------|------------|
          | Standalone build (required) | `npm run build` with `output: 'standalone'` | `.next/standalone/server.js` | `node server.js` |
          | Standard build (not for containers) | `npm run build` | `.next/` (full) | `next start` |
          
          Always use the standalone build for container deployments. The standard build requires the full `node_modules` directory at runtime, resulting in images 5–10x larger.
          
          ### sharp for next/image
          
          If the app uses `next/image`, install `sharp` explicitly in the runtime stage. Without it, Next.js falls back to the slower `squoosh` library and image optimization may fail under load.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | Image too large without standalone | Image > 1GB, slow pulls from ACR | Set `output: 'standalone'` in `next.config.js` — reduces image to ~100MB |
          | Static assets 404 | CSS/JS files return 404 after deployment | Ensure `.next/static` is copied to `.next/static` in the runtime stage (not into `standalone/.next/static`) |
          | ISR fails with read-only filesystem | `EROFS: read-only file system` when revalidating pages | Mount `emptyDir` volume at `/app/.next/cache` — ISR writes regenerated pages to the cache directory |
          | next/image optimization fails | Images return 500 or timeout under load | Install `sharp` explicitly; the standalone build may not include it automatically |
          | Env vars undefined (`NEXT_PUBLIC_` prefix) | Client-side code sees `undefined` for environment variables | `NEXT_PUBLIC_` vars are inlined at **build time**, not runtime; set them as build args in the Dockerfile or use runtime config via `publicRuntimeConfig` |
          | Telemetry calls from container | Unexpected outbound network requests to `telemetry.nextjs.org` | Set `NEXT_TELEMETRY_DISABLED=1` in both the build stage and runtime stage of the Dockerfile |
          
        • spring-boot.md 6 KB
          # Spring Boot Knowledge Pack
          
          > **Applies to:** Projects detected with `pom.xml` containing `spring-boot-starter-web` or `build.gradle`/`build.gradle.kts` containing `org.springframework.boot`
          
          ## Quick Reference
          
          | Property | Value |
          |----------|-------|
          | Signal files | `pom.xml` with `spring-boot-starter-web` or `build.gradle(.kts)` with `org.springframework.boot` |
          | Default port | `8080` |
          | Health path | `/actuator/health/liveness` + `/actuator/health/readiness` |
          | Base template | `templates/dockerfiles/java.Dockerfile` (+ `references/base-images.md`) |
          
          ---
          
          ## Dockerfile Patterns
          
          The base template handles the multi-stage build. One Spring Boot-specific optimization worth applying: use layered JAR extraction (`java -Djarmode=layertools -jar app.jar extract`) so that dependencies, Spring Boot loader, and application code land in separate Docker layers — only changed layers are rebuilt or pushed on each deployment.
          
          ---
          
          ## Health Endpoints
          
          Spring Boot Actuator provides health endpoints out of the box:
          
          | Endpoint | Purpose | Probe Type |
          |----------|---------|-----------|
          | `/actuator/health` | Overall health | General |
          | `/actuator/health/liveness` | Liveness group | `livenessProbe` |
          | `/actuator/health/readiness` | Readiness group | `readinessProbe` |
          
          ### Required configuration
          
          In `application.properties` or `application.yml`:
          
          ```properties
          management.endpoints.web.exposure.include=health
          management.endpoint.health.probes.enabled=true
          management.endpoint.health.show-details=always
          ```
          
          The probes are automatically enabled when running in Kubernetes (detected via the `KUBERNETES_SERVICE_HOST` env var), but it's best practice to enable them explicitly.
          
          ### Probe configuration in Deployment manifest
          
          ```yaml
          startupProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 10
            failureThreshold: 30        # allows up to 300s for JVM warmup + Spring context init
          livenessProbe:
            httpGet:
              path: /actuator/health/liveness
              port: 8080
            periodSeconds: 15
            timeoutSeconds: 3
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /actuator/health/readiness
              port: 8080
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          ```
          
          **Important:** Spring Boot apps require a `startupProbe`. JVM warmup and Spring context initialization typically take 15–60 seconds. Without it, the liveness probe may kill the pod before it finishes starting. The startup probe gives the app up to 300 seconds to become healthy before the liveness probe takes over. Uncomment the `startupProbe` section in the deployment template.
          
          ---
          
          ## Database Profiles
          
          Spring Boot uses Spring Profiles to switch database configurations:
          
          | Profile | Activation | Typical Config File |
          |---------|------------|-------------------|
          | `default` | No profile set | `application.properties` — usually H2 in-memory |
          | `mysql` | `SPRING_PROFILES_ACTIVE=mysql` | `application-mysql.properties` |
          | `postgres` | `SPRING_PROFILES_ACTIVE=postgres` | `application-postgres.properties` |
          
          ### Environment variables for PostgreSQL on AKS
          
          ```yaml
          env:
            - name: SPRING_PROFILES_ACTIVE
              value: postgres
            - name: POSTGRES_URL
              value: "jdbc:postgresql://{{PG_SERVER_NAME}}.postgres.database.azure.com:5432/{{DB_NAME}}"
            - name: POSTGRES_USER
              value: "{{IDENTITY_NAME}}"
            - name: SPRING_DATASOURCE_AZURE_PASSWORDLESS_ENABLED
              value: "true"
          ```
          
          With Workload Identity and the `spring-cloud-azure-starter-jdbc-postgresql` dependency, Spring Boot can authenticate to PostgreSQL without a password using Azure AD tokens.
          
          ### ConfigMap pattern
          
          ```yaml
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: {{APP_NAME}}-config
          data:
            SPRING_PROFILES_ACTIVE: "postgres"
            MANAGEMENT_ENDPOINTS_WEB_EXPOSURE_INCLUDE: "health"
            MANAGEMENT_ENDPOINT_HEALTH_PROBES_ENABLED: "true"
          ```
          
          ---
          
          ## Writable Paths (DS012 Compliance)
          
          When `readOnlyRootFilesystem: true` is set, Spring Boot needs `/tmp` writable:
          
          - **Tomcat** writes session data and compiled JSPs to `/tmp`
          - **Multipart file uploads** use `/tmp` as the staging directory
          - **Spring Boot DevTools** (if accidentally included) writes to `/tmp`
          
          ### Required volume mount
          
          ```yaml
          volumes:
            - name: tmp
              emptyDir: {}
          containers:
            - name: app
              volumeMounts:
                - name: tmp
                  mountPath: /tmp
          ```
          
          No other writable paths are typically needed for production Spring Boot apps.
          
          ---
          
          ## Resource Sizing
          
          Spring Boot apps running on the JVM need more memory than interpreted languages. These are starting-point defaults — tune based on observed usage.
          
          | Resource | Request | Limit |
          |----------|---------|-------|
          | CPU | 250m | 1000m |
          | Memory | 512Mi | 1Gi |
          
          Set `-XX:MaxRAMPercentage=75.0` in `JAVA_OPTS` so the JVM uses at most 75% of the container's memory limit, leaving headroom for the OS and non-heap memory.
          
          ---
          
          ## Port Configuration
          
          - **Default port:** 8080
          - **Config property:** `server.port` in `application.properties`
          - **Env var override:** `SERVER_PORT=8080`
          
          Spring Boot always logs the port on startup: `Tomcat started on port(s): 8080 (http)`
          
          ---
          
          ## Build Commands
          
          | Build Tool | Build Command | Output |
          |-----------|---------------|--------|
          | Maven | `./mvnw package -DskipTests -B` | `target/*.jar` |
          | Gradle | `./gradlew bootJar` | `build/libs/*.jar` |
          
          The `-B` flag (batch mode) suppresses interactive Maven output — important for CI/CD and Docker builds.
          
          ---
          
          ## Common Issues on AKS
          
          | Issue | Symptom | Fix |
          |-------|---------|-----|
          | JVM OOM in container | `OOMKilled` pod status | Set `-XX:MaxRAMPercentage=75.0` in `JAVA_OPTS` and ensure memory limit >= 256Mi |
          | Slow startup | Readiness probe fails, pod restarted | Increase `initialDelaySeconds` to 45-60s, or add a `startupProbe` with higher `failureThreshold` |
          | H2 in-memory on AKS | Data lost on pod restart | Switch to PostgreSQL profile — H2 is for local dev only |
          | Connection refused to PostgreSQL | `PSQLException: Connection refused` | Verify firewall rules on PostgreSQL Flexible Server allow AKS subnet |
          | Image too large (>500MB) | Slow pulls, high ACR storage | Use Alpine base image + layered JAR extraction |
          
    • phases
      • quick-deploy.md 7.8 KB
        # Quick Deploy
        
        Deploy an application to an existing AKS cluster with production-grade artifacts.
        
        ## Section 1: Detection
        
        Scan the project and Azure environment. Ask at most one clarifying question if genuinely ambiguous (multiple Dockerfiles, ACRs, or identities).
        
        ### Framework Detection
        
        Follow the framework detection table in `references/detection.md`. Scan for signal files at the project root (and one level deep for monorepos).
        
        ### Port and Health Endpoint Detection
        
        Follow the port and health endpoint detection tables in `references/detection.md` (first match wins). If none found, use `/health` as default in probes.
        
        ### Existing Artifact Detection
        
        Check for existing `Dockerfile` and `k8s/` (or `manifests/`, `deploy/`) directories.
        
        ### Azure Infrastructure Detection
        
        ```bash
        kubectl config current-context
        az aks show -g <rg> -n <cluster> -o json
        ```
        
        Extract from cluster details:
        - **AKS flavor**: `nodeProvisioningProfile.mode` — `"Auto"` = AKS Automatic, otherwise Standard
        - **OIDC issuer**: `oidcIssuerProfile.issuerUrl`
        - **Azure RBAC**: `aadProfile.enableAzureRBAC`
        
        ### Routing Detection
        
        ```bash
        az aks show -g <rg> -n <cluster> --query '{webAppRoutingEnabled: ingressProfile.webAppRouting.enabled, istioMode: serviceMeshProfile.istio.mode}' -o json
        ```
        
        - If `webAppRoutingEnabled` is not `true`, stop with error: `az aks approuting enable -g <rg> -n <cluster>`
        - If `istioMode` is `"Enabled"` → use **Gateway API** (`gateway.yaml` + `httproute.yaml`, `gatewayClassName: istio`)
        - Otherwise → use **Ingress** (`ingress.yaml`, `ingressClassName: webapprouting.kubernetes.azure.com`)
        
        ```bash
        az acr list -g <rg> -o json
        az identity list -g <rg> -o json
        ```
        
        ### ACR-AKS Integration
        
        Verify the AKS kubelet identity can pull images from the detected ACR:
        
        ```bash
        az aks check-acr --resource-group <rg> --name <cluster> --acr <acr-name>.azurecr.io
        ```
        
        If the check fails, attach the ACR (requires confirmation): `az aks update -g <rg> -n <cluster> --attach-acr <acr-name>`
        
        If Azure RBAC is enabled, verify namespace create permission: `kubectl auth can-i create namespaces` — if `no`, stop and offer alternatives: have an admin create it, or deploy to an existing namespace.
        
        If any detection command fails, suggest: `az login`, `az account set -s <subscription-id>`, `az aks get-credentials -g <rg> -n <cluster>`.
        
        ### Knowledge Pack
        
        After framework detection, load the matching pack from `knowledge-packs/frameworks/` if available (see `SKILL.md`).
        
        Knowledge packs influence Dockerfile optimization, probe configuration, and writable paths.
        
        ---
        
        ## Section 2: File Generation
        
        Write all files in a single response turn.
        
        ### Dockerfile
        
        **If existing Dockerfile:** Validate against best practices (multi-stage build, non-root USER, base tags pinned to a stable major tag (not :latest, not a frozen patch), layer caching, .dockerignore). Apply targeted fixes for failures — do not regenerate the file.
        
        **If no Dockerfile:** Generate from the appropriate template:
        
        | Language | Template |
        |----------|----------|
        | Node.js | `templates/dockerfiles/node.Dockerfile` |
        | Python | `templates/dockerfiles/python.Dockerfile` |
        | Java | `templates/dockerfiles/java.Dockerfile` |
        | Go | `templates/dockerfiles/go.Dockerfile` |
        | .NET | `templates/dockerfiles/dotnet.Dockerfile` |
        | Rust | `templates/dockerfiles/rust.Dockerfile` |
        
        **Resolve the base image version.** The templates carry `<LATEST_STABLE_*>`
        placeholders. Before writing the Dockerfile, replace each with the current
        stable major the project targets, following `references/base-images.md`
        (explicit registry/release check; fall back to latest-known with a verify
        comment). The generated Dockerfile must end up pinned to a concrete major tag
        — this is required for DS009.
        
        Generate `.dockerignore` if missing — use the matching template from `templates/dockerfiles/<language>.dockerignore`.
        
        ### Kubernetes Manifests
        
        **If existing manifests found** (in `k8s/`, `manifests/`, or `deploy/`): Validate against AKS Deployment Safeguards (Section 3) and apply targeted fixes. Do not regenerate — improve in place.
        
        **If no manifests found:** Generate from `templates/k8s/` templates. Replace `<angle-bracket>` placeholders with detected values.
        
        | Manifest | Template | Notes |
        |----------|----------|-------|
        | `k8s/namespace.yaml` | `templates/k8s/namespace.yaml` | |
        | `k8s/serviceaccount.yaml` | `templates/k8s/serviceaccount.yaml` | Workload Identity |
        | `k8s/deployment.yaml` | `templates/k8s/deployment.yaml` | image tag set after `az acr build`, not at generation time |
        | `k8s/service.yaml` | `templates/k8s/service.yaml` | |
        | `k8s/gateway.yaml` | `templates/k8s/gateway.yaml` | Istio only |
        | `k8s/httproute.yaml` | `templates/k8s/httproute.yaml` | Istio only |
        | `k8s/ingress.yaml` | `templates/k8s/ingress.yaml` | Ingress only |
        | `k8s/hpa.yaml` | `templates/k8s/hpa.yaml` | min: 2, max: 10 |
        | `k8s/pdb.yaml` | `templates/k8s/pdb.yaml` | minAvailable: 1 |
        | `k8s/configmap.yaml` | `templates/k8s/configmap.yaml` | If env config needed |
        | `k8s/networkpolicy.yaml` | `templates/k8s/networkpolicy.yaml` | Ingress-controller-scoped |
        
        ### Hostname
        
        Omit the `host` field from Ingress `rules` (or Gateway `listeners`) for initial deployments — traffic routes to the external IP directly. Add `host` and TLS once the user has a domain.
        
        ### Resource Sizing
        
        Use the framework-specific defaults from the knowledge pack's "Resource Sizing" section. If no pack is loaded, use `requests: {cpu: 100m, memory: 128Mi}` and `limits: {cpu: 500m, memory: 256Mi}`.
        
        ### Startup Probe
        
        For slow-start frameworks (Java/Spring Boot, .NET with heavy DI), uncomment `startupProbe` in the deployment template to prevent liveness restarts during init.
        
        ---
        
        ## Section 3: Safeguards Validation
        
        Validate all generated manifests against DS001-DS013 (reference `references/safeguards.md`).
        
        - 12 of 13 rules are auto-fixable. DS009 (no `:latest` tag) is resolved by tagging with git SHA.
        - Apply the writable paths from the loaded pack (for `readOnlyRootFilesystem: true` compliance with DS012).
        - Reference `references/workload-identity.md` for Workload Identity configuration.
        
        **AKS Automatic:** all violations must be fixed.
        
        **AKS Standard:** check `safeguardsProfile.level`:
        ```bash
        az aks show -g <rg> -n <cluster> --query 'safeguardsProfile.level' -o tsv
        ```
        - `Enforcement`: fix all violations
        - `Warning` or `Off`: warn, don't block
        
        ---
        
        ## Section 4: Deploy
        
        ### Ensure kubectl context
        
        ```bash
        az aks get-credentials -g <resource_group> -n <aks_cluster_name> --overwrite-existing
        ```
        
        ### Verify Gateway API CRDs (only if Istio Gateway API detected)
        
        ```bash
        kubectl get crd gateways.gateway.networking.k8s.io httproutes.gateway.networking.k8s.io 2>/dev/null
        ```
        
        If missing: `kubectl apply -f https://github.com/kubernetes-sigs/gateway-api/releases/latest/download/standard-install.yaml`
        
        ### Build and push
        
        ```bash
        IMAGE_TAG=$(git rev-parse --short HEAD)   # fallback: date +%Y%m%d%H%M%S
        az acr build --registry <acr_name> --image <app-name>:$IMAGE_TAG --file Dockerfile .
        ```
        
        **Monorepo:** Adjust `--file` and context to the app subdirectory (e.g., `--file apps/myapp/Dockerfile apps/myapp/`).
        
        ### Deploy to cluster
        
        ```bash
        # 1. Create namespace (must succeed before proceeding)
        kubectl apply -f k8s/namespace.yaml
        kubectl get namespace <namespace> -o name   # verify
        
        # 2. Apply remaining manifests
        kubectl apply -f k8s/ --recursive
        
        # 3. Wait for rollout
        kubectl rollout status deployment/<app-name> -n <namespace> --timeout=300s
        ```
        
        If any step fails, show the error and stop. See `references/rollback.md` for recovery.
        
        ---
        
        ## Section 5: Verify
        
        ```bash
        kubectl get pods -n <namespace> -l app=<app-name>
        kubectl get gateway -n <namespace> -o jsonpath='{.items[0].status.addresses[0].value}'  # if Gateway API
        kubectl get ingress -n <namespace> -o jsonpath='{.items[0].status.loadBalancer.ingress[0].ip}'  # if Ingress
        ```
        
        Wait up to 3 minutes for external IP, then curl the health endpoint.
        
    • references
      • base-images.md 3.6 KB
        # Base Image Policy
        
        How the skill chooses container base images. Goal: **no version number is
        stored in this repo** — the concrete tag is resolved when the Dockerfile is
        generated, so templates never go stale.
        
        ## Rules
        
        1. **Never pin minor/patch.** Use a floating **major** (or major.minor) tag.
           Floating tags receive security patches automatically — a frozen patch tag
           does not.
        2. **Resolve `<LATEST_STABLE_*>` at generation time.** When generating a
           Dockerfile, replace each placeholder with the current stable major the
           project targets (see "Resolution" below), then keep that major tag in the
           output. A major tag is concrete, so it satisfies Deployment Safeguard DS009
           (no `:latest`).
        3. **Prefer the Microsoft/Azure Linux image when one exists and the project
           has no reason to avoid it.** These are first-party, signed, and rebuilt for
           CVEs. They are listed below as the preferred option; the current default
           source is kept for drop-in compatibility.
        
        ## Per-language images
        
        | Language | Default source (template today) | Tag policy | Preferred Microsoft / Azure Linux image |
        |----------|---------------------------------|-----------|------------------------------------------|
        | .NET | `mcr.microsoft.com/dotnet/{sdk,aspnet}` | floating major (e.g. `9.0`) | already Microsoft ✓ |
        | Java | `eclipse-temurin:<maj>-{jdk,jre}-alpine` | floating major LTS | `mcr.microsoft.com/openjdk/jdk:<maj>-azurelinux` (also `-distroless`) |
        | Python | `python:<maj.min>-slim` | floating major.minor | `mcr.microsoft.com/azurelinux/base/python:<maj.min>` |
        | Node | `node:<maj>-alpine` | floating major LTS | `mcr.microsoft.com/azurelinux/base/nodejs:<maj>` |
        | Go | build `golang:<ver>-alpine`, runtime `gcr.io/distroless/static-debian12` | floating major.minor | `mcr.microsoft.com/oss/go/microsoft/golang:<ver>-azurelinux3.0` |
        | Rust | build `rust:<ver>-slim`, runtime `gcr.io/distroless/cc-debian12` | floating major.minor | `mcr.microsoft.com/azurelinux/base/rust:<maj.min>` |
        
        > The Go and Rust **runtime** stages keep the literal
        > `gcr.io/distroless/*-debian12` names and do **not** use a `<LATEST_STABLE_*>`
        > placeholder: distroless images are identified by base-OS variant (the Debian
        > release), not by language version, and they float their patch level within
        > that Debian release. This asymmetry with the build stages is intentional —
        > don't "fix" it by adding a placeholder.
        
        > Adopting the Microsoft Azure Linux images for Python/Node/Go/Rust changes the
        > in-image package manager (`tdnf`, not `apk`/`apt`) and the non-root-user
        > setup. That migration is intentionally **out of scope** here — this file
        > documents the target so a later change can adopt it.
        
        ## Resolution
        
        To fill a `<LATEST_STABLE_*>` placeholder at generation time, in order of
        preference:
        
        1. **Explicit check.** Query the registry or release channel for the current
           stable major, e.g.:
           - .NET / Java / Go (Microsoft): `az acr manifest list-metadata` against the
             MCR repo, or the image's tag list.
           - Python / Node / Rust: the language's published release channel (Docker
             Hub tag list, or the language's downloads page).
        2. **Fallback.** If no check is possible (offline, no registry access), use the
           latest stable major you know, and add a comment in the generated Dockerfile:
           `# verify this is still the current stable major`.
        
        Why floating majors are maintenance-free: Microsoft major tags always carry
        the latest minor and are rebuilt for CVEs on a regular cadence; Docker Hub
        `-slim`/`-alpine` tags float their patch level the same way. A major tag is
        therefore both stable to reference and current for security.
        
      • detection.md 3.5 KB
        # Detection Reference
        
        Shared detection logic used by Section 1 (Detection) in Quick Deploy.
        
        ## Framework Detection
        
        Scan for signal files at the project root (and one level deep for monorepos). Map each signal to a framework and, where possible, a sub-framework:
        
        | Signal File | Framework | Sub-framework Detection |
        |---|---|---|
        | `package.json` | Node.js | Inspect `dependencies` for: **Express** (`express`), **Fastify** (`fastify`), **NestJS** (`@nestjs/core`), **Next.js** (`next`), **Remix** (`@remix-run/node`), **Hono** (`hono`), **Koa** (`koa`) |
        | `requirements.txt` | Python | Scan for: **FastAPI** (`fastapi`), **Django** (`django`), **Flask** (`flask`), **Starlette** (`starlette`), **Gunicorn** (`gunicorn`) |
        | `pyproject.toml` | Python | Parse `[project.dependencies]` or `[tool.poetry.dependencies]` for the same libraries as above |
        | `Pipfile` | Python | Parse `[packages]` section for the same libraries as above |
        | `pom.xml` | Java | Search for `<artifactId>spring-boot-starter-web</artifactId>` → **Spring Boot**; `<artifactId>quarkus-resteasy</artifactId>` → **Quarkus**; `<artifactId>micronaut-http-server-netty</artifactId>` → **Micronaut** |
        | `build.gradle` / `build.gradle.kts` | Java / Kotlin | Search for `org.springframework.boot` → **Spring Boot**; `io.quarkus` → **Quarkus**; `io.micronaut` → **Micronaut** |
        | `go.mod` | Go | Parse `require` block for: `github.com/gin-gonic/gin` → **Gin**; `github.com/labstack/echo` → **Echo**; `github.com/gofiber/fiber` → **Fiber**. For `net/http` (stdlib): search `.go` source files for `"net/http"` import — stdlib packages never appear in the `require` block |
        | `*.csproj` | .NET | Search for `<PackageReference Include="Microsoft.AspNetCore.*"` → **ASP.NET Core**; check `<TargetFramework>` for version (e.g. `net8.0`) |
        | `Cargo.toml` | Rust | Parse `[dependencies]` for: `actix-web` → **Actix**; `axum` → **Axum**; `rocket` → **Rocket**; `warp` → **Warp** |
        
        **If multiple signal files are found** (e.g. both `package.json` and `requirements.txt`), record all of them — this may indicate a monorepo or polyglot project. Flag for clarification.
        
        ## Port Detection
        
        Check these sources in priority order (first match wins):
        
        | Source | What to Look For | Example |
        |---|---|---|
        | `Dockerfile` | `EXPOSE <port>` directive | `EXPOSE 3000` |
        | `.env` / `.env.example` | `PORT=<number>` | `PORT=8080` |
        | `package.json` (`scripts.start`) | `--port <number>` or `-p <number>` | `next start --port 3000` |
        | Source code | `app.listen(<number>)`, `.listen(<number>)`, `server.port=<number>` | `app.listen(3000)` |
        | `application.properties` / `application.yml` (Java) | `server.port=<number>` | `server.port=8080` |
        | `appsettings.json` (.NET) | `"Urls": "http://*:<number>"` | `"Urls": "http://*:8080"` |
        | Framework defaults | Use known defaults if nothing explicit found | Express: 3000, FastAPI: 8000, Spring Boot: 8080, ASP.NET: 8080, Gin: 8080 |
        
        ## Health Endpoint Detection
        
        Grep the source tree for route registrations matching these patterns:
        
        | Pattern | Endpoint Type |
        |---|---|
        | `/health` | Generic health check |
        | `/healthz` | Kubernetes-style health check |
        | `/ready`, `/readiness` | Readiness probe |
        | `/liveness` | Liveness probe |
        | `/startup` | Startup probe |
        | `/ping` | Simple ping (sometimes used as health) |
        | `/status` | Status endpoint |
        | `/api/health`, `/api/healthz` | Prefixed health check |
        
        Record the **HTTP method** (GET/HEAD) and **expected response code** (200) for each detected endpoint. If no health endpoints are found, flag it — probes will use `/health` as default.
        
      • rollback.md 2 KB
        # Rollback Guidance
        
        Recovery procedures for deployment failures. Referenced from Section 4 (Deploy).
        
        ---
        
        ## Image Build Failed
        
        ```bash
        # No cloud resources were persisted — nothing to roll back.
        # Fix the issue and retry:
        
        # Common fixes:
        # - Dockerfile syntax error       → edit Dockerfile
        # - Missing file in build context → check .dockerignore
        # - Dependency install failure    → fix package.json / requirements.txt / go.mod
        
        # Retry:
        az acr build --registry <acr-name> --image <app-name>:<git-sha> .
        ```
        
        ## kubectl apply Failed (Section 4 — Deploy to Cluster)
        
        ```bash
        # Remove the partially applied resources:
        kubectl delete -f k8s/
        
        # Common fixes:
        # - YAML syntax error      → validate with: kubectl apply -f k8s/ --dry-run=client
        # - Invalid resource field  → check API version matches cluster version
        # - Image pull error        → verify ACR name in deployment.yaml matches actual ACR
        # - Namespace doesn't exist → create it first or remove namespace from manifests
        
        # Fix and retry:
        kubectl apply -f k8s/
        ```
        
        ## Pods Not Starting (Section 5 — Verify)
        
        ```bash
        # Diagnose:
        kubectl get pods -l app=myapp
        kubectl describe pod -l app=myapp
        kubectl logs -l app=myapp --tail=50
        
        # Common error patterns:
        
        # CrashLoopBackOff — app crashes on startup
        #   → Check logs for the crash reason
        #   → Usually: missing env var, bad database connection string, port mismatch
        
        # ImagePullBackOff — can't pull the container image
        #   → Verify image name: kubectl get deployment myapp -o jsonpath='{.spec.template.spec.containers[0].image}'
        #   → Verify ACR access: az aks check-acr --resource-group <rg> --name <aks> --acr <acr>.azurecr.io
        
        # Pending — pod can't be scheduled
        #   → Check node status: kubectl get nodes
        #   → Check resource requests vs available capacity: kubectl describe nodes
        
        # OOMKilled — app exceeded memory limit
        #   → Increase memory limit in k8s/deployment.yaml and re-apply
        
        # After fixing, re-apply:
        kubectl apply -f k8s/
        kubectl rollout status deployment/myapp --timeout=300s
        ```
        
      • safeguards.md 2.9 KB
        # AKS Deployment Safeguards Reference
        
        > **Source of truth:** the Deployment Safeguards policy initiative is defined
        > once in
        > `../../azure-kubernetes-automatic-readiness/references/constraint-spec-v1.yaml`
        > (initiative `c047ea8e-…`). This file is the **deploy-time checklist**: which
        > rules the app-deploy workflow auto-fixes vs. warns on, and how. When the
        > policy set changes, update the constraint spec; only the app-deploy-specific
        > fix behavior below is maintained here.
        
        This checklist maps each safeguard to how the quick-deploy workflow handles it.
        
        ## DS001 — Resource Limits Required (Error)
        
        Every container needs `resources.requests` AND `resources.limits` for both `cpu` and `memory`.
        
        ## DS002 — Liveness Probe Required (Warning)
        
        Every container needs a `livenessProbe`. Use `httpGet`, `tcpSocket`, or `exec`.
        
        ## DS003 — Readiness Probe Required (Warning)
        
        Every container needs a `readinessProbe`.
        
        ## DS004 — runAsNonRoot Required (Error)
        
        Set at **both** pod and container level.
        
        ## DS005 — No hostNetwork (Error)
        
        Remove `hostNetwork: true` or set to `false`.
        
        ## DS006 — No hostPID (Error)
        
        Remove `hostPID: true` or set to `false`.
        
        ## DS007 — No hostIPC (Error)
        
        Remove `hostIPC: true` or set to `false`.
        
        ## DS008 — No Privileged Containers (Error)
        
        Remove `securityContext.privileged: true` or set to `false`.
        
        ## DS009 — No :latest Image Tag (Error, NOT auto-fixable)
        
        Use a semantic version, git SHA, or digest — never `:latest` or omit the tag.
        
        ## DS010 — Minimum 2 Replicas (Warning)
        
        Set `spec.replicas: 2` or higher. Pair with a PodDisruptionBudget.
        
        ## DS011 — allowPrivilegeEscalation: false (Error)
        
        Every container must set `securityContext.allowPrivilegeEscalation: false`.
        
        ## DS012 — readOnlyRootFilesystem: true (Warning)
        
        Every container must set `securityContext.readOnlyRootFilesystem: true`.
        
        If the app writes to specific paths, mount `emptyDir` volumes:
        
        ```yaml
        volumes:
          - name: tmp
            emptyDir: {}
        containers:
          - volumeMounts:
              - name: tmp
                mountPath: /tmp
        ```
        
        Common writable paths: Spring Boot `/tmp`, ASP.NET `/tmp`, Django `/tmp`, Express `/tmp`, Go `/tmp`.
        
        ## DS013 — automountServiceAccountToken: false (Warning)
        
        Set `spec.automountServiceAccountToken: false`. Set to `true` only if the app genuinely calls the K8s API (scope with RBAC).
        
        ---
        
        ## Quick Reference
        
        | Rule | What | Severity | Auto-Fix |
        |------|------|----------|----------|
        | DS001 | Resource limits | Error | Yes |
        | DS002 | Liveness probe | Warning | Yes |
        | DS003 | Readiness probe | Warning | Yes |
        | DS004 | runAsNonRoot | Error | Yes |
        | DS005 | No hostNetwork | Error | Yes |
        | DS006 | No hostPID | Error | Yes |
        | DS007 | No hostIPC | Error | Yes |
        | DS008 | No privileged | Error | Yes |
        | DS009 | No :latest tag | Error | No |
        | DS010 | Min 2 replicas | Warning | Yes |
        | DS011 | No privilege escalation | Error | Yes |
        | DS012 | Read-only root FS | Warning | Yes |
        | DS013 | No SA token mount | Warning | Yes |
        
      • workload-identity.md 7.4 KB
        # Azure Workload Identity for AKS
        
        > **Last updated:** 2026-04-02
        
        ## What Is Workload Identity?
        
        Workload Identity lets pods in AKS authenticate to Azure services (Key Vault, Storage,
        PostgreSQL, etc.) without storing any secrets. Instead of injecting connection strings or
        passwords, your pod proves its identity through a short-lived token issued by the
        cluster's OIDC provider, which Microsoft Entra ID trusts because you've set up a federation
        between the cluster and a Managed Identity. The pod gets a token automatically — your
        app code just uses the standard Azure SDK credential chain.
        
        ---
        
        ## Three Components
        
        ### 1. User-Assigned Managed Identity
        
        A Managed Identity in Azure that has RBAC role assignments on the target resources
        (e.g., `Key Vault Secrets User`, `Storage Blob Data Contributor`).
        
        ```
        Managed Identity
          ├── Client ID: <AZURE_CLIENT_ID>
          ├── Tenant ID: <AZURE_TENANT_ID>
          └── Role assignments:
               ├── Key Vault Secrets User  → /subscriptions/.../vaults/my-kv
               ├── Storage Blob Data Contributor → /subscriptions/.../storageAccounts/my-sa
               └── ...
        ```
        
        ### 2. Federated Identity Credential
        
        A trust relationship that says: "When the AKS cluster's OIDC issuer presents a token
        for ServiceAccount `<namespace>/<sa-name>`, treat it as this Managed Identity."
        
        ```
        Federated Credential
          ├── Issuer:  https://oidc.prod-aks.azure.com/<tenant-id>/<cluster-id>
          ├── Subject: system:serviceaccount:<namespace>:<sa-name>
          └── Audience: api://AzureADTokenExchange
        ```
        
        ### 3. Kubernetes ServiceAccount
        
        A standard K8s ServiceAccount annotated with the Managed Identity's client ID.
        
        ```yaml
        apiVersion: v1
        kind: ServiceAccount
        metadata:
          name: <app-name>
          namespace: <namespace>
          annotations:
            azure.workload.identity/client-id: "<AZURE_CLIENT_ID>"
        ```
        
        ---
        
        ## How They Link Together
        
        ```
        Pod (with label azure.workload.identity/use: "true")
          │
          ├── References ServiceAccount (annotated with client-id)
          │
          ▼
        AKS OIDC Issuer issues a projected service account token
          │
          ├── Issuer URL matches the Federated Credential's issuer
          ├── Subject (system:serviceaccount:ns:sa) matches the Federated Credential's subject
          │
          ▼
        Microsoft Entra ID validates the federation and issues a token
          │
          ▼
        Azure SDK (DefaultAzureCredential) uses the token to access Azure resources
        ```
        
        The Workload Identity webhook in AKS automatically:
        - Projects the service account token into the pod at a well-known path
        - Sets the `AZURE_CLIENT_ID`, `AZURE_TENANT_ID`, and `AZURE_FEDERATED_TOKEN_FILE`
          environment variables in the container
        
        Your app code does **not** need to know about any of this — `DefaultAzureCredential`
        picks it up automatically.
        
        ---
        
        ## Per-Service Patterns
        
        ### PostgreSQL (Flexible Server with Microsoft Entra ID Auth)
        
        The Managed Identity needs the `<db-name> Admin` or a custom PostgreSQL role.
        
        ```python
        # Python — psycopg2 + DefaultAzureCredential
        import psycopg2
        from azure.identity import DefaultAzureCredential
        
        credential = DefaultAzureCredential()
        token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default")
        
        conn = psycopg2.connect(
            host="<server>.postgres.database.azure.com",
            dbname="<database>",
            user="<managed-identity-name>",
            password=token.token,
            sslmode="require",
        )
        ```
        
        ```csharp
        // C# — Npgsql + Azure.Identity
        var credential = new DefaultAzureCredential();
        var token = await credential.GetTokenAsync(
            new TokenRequestContext(new[] { "https://ossrdbms-aad.database.windows.net/.default" }));
        
        var connString = $"Host=<server>.postgres.database.azure.com;Database=<database>;"
                       + $"Username=<managed-identity-name>;Password={token.Token};SSL Mode=Require";
        await using var conn = new NpgsqlConnection(connString);
        ```
        
        **Required env vars** (injected by Workload Identity webhook):
        - `AZURE_CLIENT_ID` — used by `DefaultAzureCredential`
        
        ### Key Vault
        
        Role assignment: `Key Vault Secrets User` (or `Key Vault Crypto User` for keys).
        
        ```python
        # Python
        from azure.identity import DefaultAzureCredential
        from azure.keyvault.secrets import SecretClient
        
        credential = DefaultAzureCredential()
        client = SecretClient(vault_url="https://<vault-name>.vault.azure.net", credential=credential)
        secret = client.get_secret("my-secret")
        ```
        
        ```csharp
        // C#
        var credential = new DefaultAzureCredential();
        var client = new SecretClient(new Uri("https://<vault-name>.vault.azure.net"), credential);
        KeyVaultSecret secret = await client.GetSecretAsync("my-secret");
        ```
        
        ### Azure Blob Storage
        
        Role assignment: `Storage Blob Data Contributor` (or `Reader` for read-only).
        
        ```python
        # Python
        from azure.identity import DefaultAzureCredential
        from azure.storage.blob import BlobServiceClient
        
        credential = DefaultAzureCredential()
        client = BlobServiceClient(
            account_url="https://<account>.blob.core.windows.net",
            credential=credential,
        )
        ```
        
        ```csharp
        // C#
        var credential = new DefaultAzureCredential();
        var client = new BlobServiceClient(
            new Uri("https://<account>.blob.core.windows.net"), credential);
        ```
        
        ### Azure Cache for Redis (Microsoft Entra ID Token Auth)
        
        Role assignment: `Redis Cache Contributor` or custom data-plane role.
        
        ```python
        # Python — redis-py with Microsoft Entra ID token
        import os
        from azure.identity import DefaultAzureCredential
        import redis
        
        credential = DefaultAzureCredential()
        token = credential.get_token("https://redis.azure.com/.default")
        
        r = redis.Redis(
            host="<cache-name>.redis.cache.windows.net",
            port=6380,
            ssl=True,
            username=os.environ["AZURE_CLIENT_ID"],
            password=token.token,
        )
        ```
        
        ```csharp
        // C#
        var credential = new DefaultAzureCredential();
        var token = await credential.GetTokenAsync(
            new TokenRequestContext(new[] { "https://redis.azure.com/.default" }));
        
        var muxer = await ConnectionMultiplexer.ConnectAsync(new ConfigurationOptions
        {
            EndPoints = { "<cache-name>.redis.cache.windows.net:6380" },
            Ssl = true,
            User = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"),
            Password = token.Token,
        });
        ```
        
        ---
        
        ## Required Pod Labels and ServiceAccount Annotations
        
        ### Pod Label (on the Deployment's `spec.template.metadata.labels`)
        
        ```yaml
        labels:
          azure.workload.identity/use: "true"
        ```
        
        This label tells the Workload Identity webhook to inject the projected token volume
        and environment variables into the pod.
        
        ### ServiceAccount Annotation
        
        ```yaml
        apiVersion: v1
        kind: ServiceAccount
        metadata:
          name: <app-name>
          annotations:
            azure.workload.identity/client-id: "<AZURE_CLIENT_ID>"
        ```
        
        This annotation tells the webhook which Managed Identity to federate with.
        
        ### Complete Deployment Snippet
        
        ```yaml
        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: <app-name>
        spec:
          template:
            metadata:
              labels:
                app: <app-name>
                azure.workload.identity/use: "true"     # ← required label
            spec:
              serviceAccountName: <app-name>            # ← references annotated SA
              automountServiceAccountToken: false        # ← DS013 (Workload Identity uses projected volume, not SA token)
              containers:
                - name: <app-name>
                  # AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_FEDERATED_TOKEN_FILE
                  # are injected automatically by the webhook
        ```
        
        > **Note:** `automountServiceAccountToken: false` disables the *default* SA token mount.
        > Workload Identity uses a separate projected volume that the webhook manages independently,
        > so both can coexist without conflict.
        
    • templates
      • dockerfiles
        • dotnet.Dockerfile 2.6 KB · in bundle
        • dotnet.dockerignore 125 B · in bundle
        • go.Dockerfile 2.1 KB · in bundle
        • go.dockerignore 128 B · in bundle
        • java.Dockerfile 3 KB · in bundle
        • java.dockerignore 156 B · in bundle
        • node.Dockerfile 3 KB · in bundle
        • node.dockerignore 173 B · in bundle
        • python.Dockerfile 2.5 KB · in bundle
        • python.dockerignore 201 B · in bundle
        • rust.Dockerfile 3.2 KB · in bundle
        • rust.dockerignore 87 B · in bundle
      • github-actions
        • deploy.yml 7.4 KB
          # GitHub Actions workflow: Deploy to AKS
          #
          # This workflow builds a container image, pushes it to Azure Container Registry,
          # and deploys it to an Azure Kubernetes Service cluster.
          #
          # Authentication uses OIDC federation (workload identity) — no stored passwords.
          # Required GitHub secrets: AZURE_CLIENT_ID, AZURE_TENANT_ID, AZURE_SUBSCRIPTION_ID
          #
          # Placeholders to replace (uses __DOUBLE_UNDERSCORE__ style; K8s templates use angle-bracket style):
          #   __ACR_NAME__    — Azure Container Registry name (e.g. myappacr)
          #   __AKS_CLUSTER__ — AKS cluster name (e.g. myapp-aks)
          #   __RG_NAME__     — Azure resource group containing ACR and AKS
          #   __APP_NAME__    — Application / deployment name in Kubernetes
          #   __NAMESPACE__   — Kubernetes namespace to deploy into
          
          name: Deploy to AKS
          
          on:
            # Trigger on push to main branch (app code changes only)
            push:
              branches:
                - main
              paths-ignore:
                - 'docs/**'
                - '*.md'
                - '.github/**'
                - '.vscode/**'
          
            # Allow manual trigger from the Actions tab
            workflow_dispatch:
          
          # OIDC federation requires these permissions so GitHub can issue
          # an ID token that Microsoft Entra ID will accept.
          permissions:
            id-token: write   # Required for requesting the JWT
            contents: read     # Required for actions/checkout
          
          # Prevent parallel deployments on the same branch.
          # Uses workflow + ref so staging and production runs can proceed independently.
          # cancel-in-progress: false ensures the running deploy finishes
          # before the queued deploy starts (avoids mid-rollout conflicts).
          # Note: env context is not available here — use github or vars contexts only.
          concurrency:
            group: ${{ github.workflow }}-${{ github.ref_name }}
            cancel-in-progress: false
          
          env:
            ACR_NAME: __ACR_NAME__
            AKS_CLUSTER: __AKS_CLUSTER__
            RESOURCE_GROUP: __RG_NAME__
            APP_NAME: __APP_NAME__
            NAMESPACE: __NAMESPACE__
          
          defaults:
            run:
              shell: bash
          
          jobs:
            build-and-deploy:
              runs-on: ubuntu-latest
          
              steps:
                - name: Checkout repository
                  uses: actions/checkout@v4
          
                # -----------------------------------------------------------
                # Validate that no placeholders remain unreplaced
                #
                #    Checks for __PLACEHOLDER__ and <placeholder> patterns in
                #    env vars and k8s/ directory. Fails fast with clear error
                #    if any found.
                # -----------------------------------------------------------
                - name: Validate — no unreplaced placeholders
                  run: |
                    PLACEHOLDERS_FOUND=0
          
                    # Check env variables
                    for VAR in ACR_NAME AKS_CLUSTER RESOURCE_GROUP APP_NAME NAMESPACE; do
                      VALUE="${!VAR}"
                      if [[ "$VALUE" =~ __[A-Z_]+__ ]]; then
                        echo "❌ Placeholder found in \$${VAR}: ${VALUE}"
                        PLACEHOLDERS_FOUND=1
                      fi
                    done
          
                    # Check k8s/ directory if it exists
                    if [ -d k8s ]; then
                      # __PLACEHOLDER__ style (env var style used in this workflow)
                      if grep -rq '__[A-Z_]\+__' k8s/; then
                        echo "❌ Placeholders found in k8s/ manifests:"
                        grep -rn '__[A-Z_]\+__' k8s/ || true
                        PLACEHOLDERS_FOUND=1
                      fi
                      # <placeholder> style (angle-bracket style used in K8s manifest templates).
                      # Exclude <image>: it is intentionally left in place here and replaced
                      # with the SHA-tagged image in the "Substitute image tag" step below.
                      if grep -rnP '<[a-z][a-z0-9-]*>' k8s/ | grep -vq '<image>'; then
                        echo "❌ Angle-bracket placeholders found in k8s/ manifests:"
                        grep -rnP '<[a-z][a-z0-9-]*>' k8s/ | grep -v '<image>' || true
                        PLACEHOLDERS_FOUND=1
                      fi
                    fi
          
                    if [ $PLACEHOLDERS_FOUND -eq 1 ]; then
                      echo ""
                      echo "⚠️  Workflow failed: unreplaced placeholders detected."
                      echo "Replace the following in your deploy.yml:"
                      echo "  - __ACR_NAME__    → Your Container Registry name"
                      echo "  - __AKS_CLUSTER__ → Your AKS cluster name"
                      echo "  - __RG_NAME__     → Your resource group name"
                      echo "  - __APP_NAME__    → Your application name"
                      echo "  - __NAMESPACE__   → Your Kubernetes namespace"
                      exit 1
                    fi
          
                    echo "✓ All placeholders replaced"
          
                # -----------------------------------------------------------
                # Authenticate to Azure using OIDC (workload identity)
                #
                #    This exchanges the GitHub-issued OIDC token for an Azure
                #    access token — no client secret required.
                # -----------------------------------------------------------
                - name: Azure Login (OIDC)
                  uses: azure/login@v2
                  with:
                    client-id: ${{ secrets.AZURE_CLIENT_ID }}
                    tenant-id: ${{ secrets.AZURE_TENANT_ID }}
                    subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
          
                # -----------------------------------------------------------
                # Build container image and push to ACR
                #
                #    `az acr build` runs the Docker build remotely on ACR,
                #    so no local Docker daemon is needed. The image is tagged
                #    with the commit SHA for traceability.
                # -----------------------------------------------------------
                - name: Build and push image to ACR
                  run: |
                    az acr build \
                      --registry ${{ env.ACR_NAME }} \
                      --image ${{ env.APP_NAME }}:${{ github.sha }} \
                      .
          
                - name: Set AKS context
                  uses: azure/aks-set-context@v4
                  with:
                    resource-group: ${{ env.RESOURCE_GROUP }}
                    cluster-name: ${{ env.AKS_CLUSTER }}
          
                # -----------------------------------------------------------
                # Deploy to AKS
                #
                #    Applies all K8s resources (with substituted image tag),
                #    then waits for the rollout to complete successfully.
                #    Sets a step output flag used to gate the rollback step.
                # -----------------------------------------------------------
                - name: Substitute image tag in manifests
                  env:
                    IMAGE: ${{ env.ACR_NAME }}.azurecr.io/${{ env.APP_NAME }}:${{ github.sha }}
                  run: |
                    if [ ! -d k8s ]; then
                      echo "❌ k8s/ directory not found — cannot deploy without manifests"
                      exit 1
                    fi
                    # Use xargs to preserve sed exit codes (find|while swallows them)
                    find k8s -name "*.yaml" -o -name "*.yml" \
                      | xargs -I{} sed -i "s|<image>|${IMAGE}|g" "{}"
                    echo "✓ Image tag substituted in all manifests"
          
                - name: Deploy to AKS
                  id: deploy
                  run: |
                    # Ensure the namespace exists before applying manifests
                    kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml \
                      | kubectl apply -f -
                    kubectl apply -f k8s/ --namespace ${{ env.NAMESPACE }}
          
                    kubectl rollout status deployment/${{ env.APP_NAME }} \
                      --namespace ${{ env.NAMESPACE }} \
                      --timeout=300s
          
                    # Signal that the deployment was applied — used to gate rollback
                    echo "deployed=true" >> "$GITHUB_OUTPUT"
          
                - name: Rollback on failure
                  if: failure() && steps.deploy.outputs.deployed == 'true'
                  run: |
                    kubectl rollout undo deployment/${{ env.APP_NAME }} \
                      --namespace ${{ env.NAMESPACE }}
                    kubectl rollout status deployment/${{ env.APP_NAME }} \
                      --namespace ${{ env.NAMESPACE }} \
                      --timeout=120s
                    echo "⚠️ Rolled back to previous revision"
          
      • k8s
        • configmap.yaml 1 KB
          # =============================================================================
          # Kubernetes ConfigMap Template — AKS Deploy Skill
          # =============================================================================
          # Stores non-sensitive configuration data as key-value pairs. Values are
          # injected into pods as environment variables via envFrom or env/valueFrom.
          #
          # Do NOT store secrets here — use Azure Key Vault + Workload Identity instead.
          #
          # REPLACE: <app-name>   — your application name (e.g., order-api)
          # REPLACE: <namespace>  — target namespace (e.g., production)
          # =============================================================================
          apiVersion: v1
          kind: ConfigMap
          metadata:
            name: <app-name>-config
            namespace: <namespace>
            labels:
              app: <app-name>
          data: {}
            # Add application configuration as key-value pairs. Remove the `{}` above
            # when you add real entries (a populated `data:` map cannot also be `{}`).
            # Example:
            # LOG_LEVEL: "info"
            # ASPNETCORE_ENVIRONMENT: "Production"
            # SPRING_PROFILES_ACTIVE: "prod"
          
        • deployment.yaml 3.4 KB
          # Kubernetes Deployment Template — AKS Deploy Skill
          # Satisfies Deployment Safeguard rules DS001–DS013. Replace <placeholder> values before applying.
          apiVersion: apps/v1
          kind: Deployment
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            # DS010: Minimum 2 replicas for high availability.
            # If HPA is enabled, remove this field or set it to the HPA minReplicas value
            # to prevent kubectl apply from resetting the replica count on each deploy.
            replicas: 2
            selector:
              matchLabels:
                app: <app-name>
            strategy:
              type: RollingUpdate
              rollingUpdate:
                maxSurge: 1
                maxUnavailable: 0
            template:
              metadata:
                labels:
                  app: <app-name>
                  # Workload Identity: enables the mutating webhook to inject
                  # AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE
                  azure.workload.identity/use: "true"
              spec:
                serviceAccountName: <app-name>
          
                # DS013: Do not auto-mount the default ServiceAccount token.
                # Workload Identity uses a separate projected volume managed by its webhook.
                automountServiceAccountToken: false
          
                # DS004 (pod-level): Run as non-root
                securityContext:
                  runAsNonRoot: true
                  runAsUser: 1000
                  runAsGroup: 1000
                  fsGroup: 1000
                  seccompProfile:
                    type: RuntimeDefault
          
                containers:
                  - name: <app-name>
                    # DS009: Always use an explicit tag — never :latest or bare image
                    image: <image>
                    ports:
                      - name: http
                        containerPort: <port>
                        protocol: TCP
          
                    # DS001: Resource requests AND limits for cpu and memory
                    resources:
                      requests:
                        cpu: "<cpu-request>"
                        memory: "<memory-request>"
                      limits:
                        cpu: "<cpu-limit>"
                        memory: "<memory-limit>"
          
                    # DS002: Liveness probe
                    livenessProbe:
                      httpGet:
                        path: <health-path>
                        port: <port>
                      initialDelaySeconds: 10
                      periodSeconds: 15
                      timeoutSeconds: 3
                      failureThreshold: 3
          
                    # DS003: Readiness probe
                    readinessProbe:
                      httpGet:
                        path: <ready-path>
                        port: <port>
                      initialDelaySeconds: 5
                      periodSeconds: 10
                      timeoutSeconds: 3
                      failureThreshold: 3
          
                    # Startup probe — uncomment for slow-start frameworks (Java/Spring Boot,
                    # .NET with heavy DI). Prevents the liveness probe from killing the pod
                    # before it finishes initializing. The pod has up to 30 * 10s = 300s to start.
                    # startupProbe:
                    #   httpGet:
                    #     path: <health-path>
                    #     port: <port>
                    #   periodSeconds: 10
                    #   failureThreshold: 30
          
                    # DS004, DS008, DS011, DS012
                    securityContext:
                      runAsNonRoot: true
                      privileged: false
                      allowPrivilegeEscalation: false
                      readOnlyRootFilesystem: true
                      capabilities:
                        drop:
                          - ALL
          
                    # If the app needs to write to specific paths (logs, tmp, cache),
                    # mount emptyDir volumes below instead of disabling readOnlyRootFilesystem.
                    volumeMounts:
                      - name: tmp
                        mountPath: /tmp
          
                volumes:
                  - name: tmp
                    emptyDir: {}
          
        • gateway.yaml 1.5 KB
          # =============================================================================
          # Gateway API — Gateway Resource Template — AKS Deploy Skill
          # =============================================================================
          # Use this template for AKS clusters with Istio Gateway API enabled
          # (appRoutingIstio.mode: Enabled). This applies to both AKS Automatic and Standard.
          #
          # For clusters using the default Web App Routing add-on, use ingress.yaml instead.
          #
          # REPLACE: <gateway-name> — name for the gateway (e.g., app-gateway)
          # REPLACE: <namespace>    — target namespace (e.g., production)
          # REPLACE: <hostname>     — FQDN for the listener (e.g., api.example.com)
          # =============================================================================
          apiVersion: gateway.networking.k8s.io/v1
          kind: Gateway
          metadata:
            name: <gateway-name>
            namespace: <namespace>
            labels:
              app: <gateway-name>
          spec:
            # Istio gateway controller — available on both AKS Automatic and Standard when enabled
            gatewayClassName: istio
            listeners:
              - name: http
                protocol: HTTP
                port: 80
                hostname: "<hostname>"
                allowedRoutes:
                  namespaces:
                    from: Same
              # Uncomment for TLS — requires a Secret with the certificate
              # - name: https
              #   protocol: HTTPS
              #   port: 443
              #   hostname: "<hostname>"
              #   tls:
              #     mode: Terminate
              #     certificateRefs:
              #       - kind: Secret
              #         name: <tls-secret-name>
              #   allowedRoutes:
              #     namespaces:
              #       from: Same
          
        • hpa.yaml 1.4 KB
          # =============================================================================
          # HorizontalPodAutoscaler Template — AKS Deploy Skill
          # =============================================================================
          # Scales the Deployment between min and max replicas based on CPU utilization.
          # Minimum of 2 replicas ensures HA even at low load (aligns with DS010).
          #
          # REPLACE: <app-name>   — your application name (e.g., order-api)
          # REPLACE: <namespace>  — target namespace (e.g., production)
          # REPLACE: <min>        — minimum replicas (default: 2, must be >= 2 for DS010)
          # REPLACE: <max>        — maximum replicas (e.g., 10)
          # =============================================================================
          apiVersion: autoscaling/v2
          kind: HorizontalPodAutoscaler
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            scaleTargetRef:
              apiVersion: apps/v1
              kind: Deployment
              name: <app-name>
            minReplicas: <min>
            maxReplicas: <max>
            metrics:
              - type: Resource
                resource:
                  name: cpu
                  target:
                    type: Utilization
                    averageUtilization: 70
            behavior:
              scaleDown:
                stabilizationWindowSeconds: 300
                policies:
                  - type: Pods
                    value: 1
                    periodSeconds: 60
              scaleUp:
                stabilizationWindowSeconds: 0
                policies:
                  - type: Pods
                    value: 2
                    periodSeconds: 60
          
        • httproute.yaml 1.3 KB
          # =============================================================================
          # Gateway API — HTTPRoute Template — AKS Deploy Skill
          # =============================================================================
          # Routes HTTP traffic from a Gateway to a backend Service.
          # Use this together with gateway.yaml on clusters with Istio Gateway API enabled.
          #
          # REPLACE: <app-name>      — your application name (e.g., order-api)
          # REPLACE: <namespace>     — target namespace (e.g., production)
          # REPLACE: <gateway-name>  — name of the Gateway resource (e.g., app-gateway)
          # REPLACE: <hostname>      — FQDN matching the Gateway listener (e.g., api.example.com)
          # REPLACE: <path-prefix>   — URL path prefix to match (e.g., /)
          # REPLACE: <service-port>  — port on the backend Service (e.g., 80)
          # =============================================================================
          apiVersion: gateway.networking.k8s.io/v1
          kind: HTTPRoute
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            parentRefs:
              - name: <gateway-name>
                namespace: <namespace>
            hostnames:
              - "<hostname>"
            rules:
              - matches:
                  - path:
                      type: PathPrefix
                      value: "<path-prefix>"
                backendRefs:
                  - name: <app-name>
                    port: <service-port>
                    kind: Service
          
        • ingress.yaml 1.9 KB
          # =============================================================================
          # Kubernetes Ingress Template — AKS Deploy Skill
          # =============================================================================
          # Use this template for AKS clusters with the Web App Routing add-on
          # This is the default for both AKS Automatic and AKS Standard.
          # For clusters with Istio Gateway API enabled, use gateway.yaml + httproute.yaml instead.
          #
          # REPLACE: <app-name>    — your application name (e.g., order-api)
          # REPLACE: <namespace>   — target namespace (e.g., production)
          # REPLACE: <path>        — URL path (e.g., /)
          # REPLACE: <service-port> — port on the backend Service (e.g., 80)
          # NOTE: <hostname> is in the commented host-based rule — fill it in when DNS is configured
          # =============================================================================
          apiVersion: networking.k8s.io/v1
          kind: Ingress
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            ingressClassName: webapprouting.kubernetes.azure.com
            # Uncomment for TLS — requires a Secret with the certificate
            # tls:
            #   - hosts:
            #       - <hostname>
            #     secretName: <tls-secret-name>
            rules:
              # Initial deploy (no custom domain) — traffic routes to the external IP directly.
              # Once DNS is configured, replace this rule with the host-based variant below.
              - http:
                  paths:
                    - path: "<path>"
                      pathType: Prefix
                      backend:
                        service:
                          name: <app-name>
                          port:
                            number: <service-port>
              # Host-based rule — uncomment and replace the rule above once DNS is configured:
              # - host: "<hostname>"
              #   http:
              #     paths:
              #       - path: "<path>"
              #         pathType: Prefix
              #         backend:
              #           service:
              #             name: <app-name>
              #             port:
              #               number: <service-port>
          
        • namespace.yaml 734 B
          # =============================================================================
          # Kubernetes Namespace Template — AKS Deploy Skill
          # =============================================================================
          # Creates an isolated namespace for the application workload. Using a dedicated
          # namespace (rather than "default") improves resource organization, access
          # control, and makes cleanup easier (delete the namespace to remove everything).
          #
          # REPLACE: <namespace> — target namespace (e.g., myapp, production)
          # =============================================================================
          apiVersion: v1
          kind: Namespace
          metadata:
            name: <namespace>
            labels:
              app.kubernetes.io/managed-by: azure-kubernetes-app-deploy
          
        • networkpolicy.yaml 1.6 KB
          # =============================================================================
          # Kubernetes NetworkPolicy Template — AKS Deploy Skill
          # =============================================================================
          # Restricts ingress to the application pod so only the ingress controller
          # (or gateway) namespace can reach it. Denies all other inbound traffic.
          #
          # REPLACE: <app-name>             — your application name (e.g., order-api)
          # REPLACE: <namespace>            — target namespace (e.g., production)
          # REPLACE: <ingress-namespace>    — namespace of the ingress controller
          #                                   AKS Web App Routing: app-routing-system
          #                                   Istio Gateway:       aks-istio-ingress
          # =============================================================================
          apiVersion: networking.k8s.io/v1
          kind: NetworkPolicy
          metadata:
            name: <app-name>-allow-ingress
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            podSelector:
              matchLabels:
                app: <app-name>
            policyTypes:
              - Ingress
              # Uncomment to also restrict egress (recommended for production):
              # - Egress
            ingress:
              - from:
                  - namespaceSelector:
                      matchLabels:
                        kubernetes.io/metadata.name: <ingress-namespace>
            # Uncomment and customize to restrict egress (e.g., allow only DNS + database):
            # egress:
            #   - ports:
            #       - port: 53
            #         protocol: UDP
            #       - port: 53
            #         protocol: TCP
            #   - to:
            #       - namespaceSelector:
            #           matchLabels:
            #             kubernetes.io/metadata.name: <database-namespace>
          
        • pdb.yaml 758 B
          # =============================================================================
          # PodDisruptionBudget Template — AKS Deploy Skill
          # =============================================================================
          # Ensures at least one pod remains available during voluntary disruptions
          # (node drains, cluster upgrades, spot evictions).
          #
          # REPLACE: <app-name>   — your application name (e.g., order-api)
          # REPLACE: <namespace>  — target namespace (e.g., production)
          # =============================================================================
          apiVersion: policy/v1
          kind: PodDisruptionBudget
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            minAvailable: 1
            selector:
              matchLabels:
                app: <app-name>
          
        • service.yaml 864 B
          # =============================================================================
          # Kubernetes Service Template — AKS Deploy Skill
          # =============================================================================
          # ClusterIP Service that routes traffic to application pods.
          #
          # REPLACE: <app-name>    — your application name (e.g., order-api)
          # REPLACE: <namespace>   — target namespace (e.g., production)
          # REPLACE: <port>        — service port (e.g., 80)
          # REPLACE: <target-port> — container port (e.g., 8080)
          # =============================================================================
          apiVersion: v1
          kind: Service
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
          spec:
            type: ClusterIP
            selector:
              app: <app-name>
            ports:
              - name: http
                port: <port>
                targetPort: <target-port>
                protocol: TCP
          
        • serviceaccount.yaml 1.4 KB
          # =============================================================================
          # ServiceAccount Template — AKS Deploy Skill
          # =============================================================================
          # Kubernetes ServiceAccount with Azure Workload Identity annotation.
          # The annotation links this SA to an Azure Managed Identity via OIDC federation.
          #
          # Prerequisites:
          #   1. A User-Assigned Managed Identity exists in Azure
          #   2. A Federated Identity Credential is configured with:
          #      - Issuer:  <AKS cluster OIDC issuer URL>
          #      - Subject: system:serviceaccount:<namespace>:<app-name>
          #      - Audience: api://AzureADTokenExchange
          #
          # REPLACE: <app-name>          — your application name (e.g., order-api)
          # REPLACE: <namespace>         — target namespace (e.g., production)
          # REPLACE: <azure-client-id>   — client ID of the Managed Identity
          # =============================================================================
          apiVersion: v1
          kind: ServiceAccount
          metadata:
            name: <app-name>
            namespace: <namespace>
            labels:
              app: <app-name>
            annotations:
              # Workload Identity: maps this ServiceAccount to an Azure Managed Identity.
              # The Workload Identity webhook reads this annotation and injects
              # AZURE_CLIENT_ID, AZURE_TENANT_ID, and AZURE_FEDERATED_TOKEN_FILE
              # into any pod that references this ServiceAccount AND has the label
              # azure.workload.identity/use: "true".
              azure.workload.identity/client-id: "<azure-client-id>"
          
      • mermaid
        • architecture-diagram.md 1.6 KB
          # Architecture Diagram Template
          
          Render this mermaid diagram in the terminal, replacing all `{{PLACEHOLDER}}` tokens with detected values from Section 1 (Detection) and the chosen backing services.
          
          ## Diagram
          
          ~~~mermaid
          flowchart LR
              Users([Users]) -->|HTTPS| GW
          
              subgraph AKS["AKS Cluster: {{AKS_CLUSTER_NAME}}"]
                  direction LR
                  GW[{{INGRESS_TYPE}}] --> SVC[Service\n{{APP_NAME}}:{{PORT}}]
                  SVC --> DEP[Deployment\n{{REPLICA_COUNT}} replicas]
              end
          
              DEP -.->|Workload Identity| MI[Managed Identity\n{{IDENTITY_NAME}}]
              ACR[ACR\n{{ACR_NAME}}.azurecr.io] -->|pull| AKS
              CICD[GitHub Actions] -->|push| ACR
          
              %% Backing services — include only those in the architecture contract
              %% Delete lines for services not selected
              DEP -.->|Workload Identity| PG[(PostgreSQL\n{{PG_SERVER_NAME}})]
              DEP -.->|Workload Identity| REDIS[(Redis\n{{REDIS_NAME}})]
              DEP -.->|Workload Identity| KV[Key Vault\n{{KV_NAME}}]
          
              MON[Log Analytics\n{{LAW_NAME}}] -..- AKS
          
              style AKS fill:#e8f5e9,stroke:#107C10,stroke-width:2px
              style ACR fill:#e3f2fd,stroke:#0078D4
              style PG fill:#fff3e0,stroke:#f57c00
              style REDIS fill:#fce4ec,stroke:#c62828
              style KV fill:#f3e5f5,stroke:#7b1fa2
              style MON fill:#f5f5f5,stroke:#757575
          ~~~
          
          ## Rendering instructions
          
          Output this diagram as a fenced mermaid code block in the terminal. The developer will see it rendered if their terminal/tool supports mermaid, or as readable text if not.
          
          After the diagram, output a cost estimate table listing each Azure resource with its SKU/tier and approximate monthly cost. Use your knowledge of Azure pricing to provide estimates.
          
        • summary-dashboard.md 1.7 KB
          # Deployment Summary Template
          
          After successful deployment, render this summary in the terminal.
          
          ## Template
          
          ```
          ╔══════════════════════════════════════════════════════╗
          ║  DEPLOYMENT SUCCESSFUL                               ║
          ║  {{APP_NAME}} is live at {{APP_URL}}                 ║
          ║  Deployed: {{DEPLOY_TIMESTAMP}}                      ║
          ╚══════════════════════════════════════════════════════╝
          ```
          
          ### Azure Resources
          
          | Resource | Type | Name | Portal Link |
          |----------|------|------|-------------|
          | Resource Group | resourceGroups | {{RG_NAME}} | `https://portal.azure.com/...` |
          | AKS Cluster | managedClusters | {{AKS_NAME}} | `https://portal.azure.com/...` |
          | Container Registry | registries | {{ACR_NAME}} | `https://portal.azure.com/...` |
          | {{BACKING_SERVICE}} | {{TYPE}} | {{NAME}} | `https://portal.azure.com/...` |
          
          Replace each portal link with the full URL using the subscription ID, resource group, and resource name.
          
          ### Files Created / Modified
          
          List all files generated during the workflow with `+` for created and `~` for modified.
          
          ### Monthly Cost Estimate
          
          List each Azure resource with its SKU/tier and approximate monthly cost.
          
          ### Next Steps
          
          1. **Custom Domain** — Point DNS to external IP, update Gateway/Ingress
          2. **TLS Certificate** — Enable HTTPS via cert-manager or Azure-managed TLS
          3. **Monitoring Dashboard** — Set up Azure Monitor / Prometheus + Grafana
          4. **Scaling** — Tune HPA min/max replicas and resource requests/limits
          5. **CI/CD Trigger** — Push to default branch to trigger pipeline
          
    • SKILL.md 2 KB
      ---
      name: azure-kubernetes-app-deploy
      license: MIT
      metadata:
        author: Microsoft
        version: "1.0.0"
      description: "Use when deploying an existing web application or API to an already-running Azure Kubernetes Service cluster. Detects the framework, generates a Dockerfile and Kubernetes manifests, validates against AKS Deployment Safeguards, and deploys with verification. WHEN: deploy app to AKS, deploy to existing AKS cluster, containerize app for Kubernetes, generate K8s manifests for Azure, set up CI/CD for AKS, my AKS deployment is failing safeguard checks, I have a Django/Express/Spring Boot app to run on AKS. DO NOT USE FOR: creating or provisioning an AKS cluster (use azure-kubernetes), assessing migration to AKS Automatic (use azure-kubernetes-automatic-readiness), or deploying to non-AKS targets like Web Apps, Container Apps, or Functions."
      ---
      
      # Deploy to AKS
      
      **Use when:** deploying a web app/API to AKS; containerizing for Kubernetes; generating manifests; AKS CI/CD; DS001–DS013 failures.
      
      **Not for:** provisioning clusters (`azure-kubernetes`), AKS Automatic readiness (`azure-kubernetes-automatic-readiness`), non-AKS targets.
      
      ## Workflow
      
      Requires: existing AKS cluster, `az login`, `kubectl` configured. Follow `phases/quick-deploy.md`. On failure: `references/rollback.md`.
      
      ## References
      
      - [detection.md](./references/detection.md) — framework/port/health detection
      - [safeguards.md](./references/safeguards.md) — DS001-DS013 checklist
      - [workload-identity.md](./references/workload-identity.md) — Workload Identity setup
      - [rollback.md](./references/rollback.md) — recovery procedures
      - [base-images.md](./references/base-images.md) — base image policy and `<LATEST_STABLE_*>` resolution
      
      ## Knowledge Packs
      
      Load `knowledge-packs/frameworks/<framework>.md` per detected framework. Available: `spring-boot`, `express`, `nextjs`, `fastapi`, `django`, `nestjs`, `aspnet-core`, `go`, `flask`
      
      ## Templates
      
      `templates/` (dockerfiles/, k8s/, github-actions/, mermaid/).
      
  • azure-kubernetes-automatic-readiness
    • references
      • common-fixes.md 6.7 KB
        # Common Fix Patterns for AKS Automatic Compatibility
        
        Loaded on demand when generating YAML fixes during assessment.
        Maps to constraint IDs in `constraint-spec-v1.yaml`.
        
        ---
        
        ## `safeguard-container-resource-requests` — Add resource requests/limits
        
        **Before:**
        ```yaml
        containers:
          - name: web
            image: myapp:v1.0.0
        ```
        
        **After:**
        ```yaml
        containers:
          - name: web
            image: myapp:v1.0.0
            resources:
              requests:
                cpu: "250m"
                memory: "256Mi"
              limits:
                cpu: "500m"
                memory: "512Mi"
        ```
        
        > 💡 **Tip:** Use safe minimums as starting values. VPA (auto-enabled on AKS Automatic) will tune these after deployment based on actual usage.
        
        ---
        
        ## `safeguard-container-capabilities` — Drop all capabilities
        
        **Before:**
        ```yaml
        securityContext:
          capabilities:
            add: ["NET_ADMIN"]
        ```
        
        **After:**
        ```yaml
        securityContext:
          capabilities:
            drop: ["ALL"]
        ```
        
        > ⚠️ **Warning:** If the app genuinely requires `NET_ADMIN` or similar, it is **incompatible** with AKS Automatic. Do not silently drop — explain the incompatibility and suggest redesign.
        
        ---
        
        ## `safeguard-allowed-seccomp-profiles` — Add seccomp profile
        
        **Before:**
        ```yaml
        spec:
          containers:
            - name: web
        ```
        
        **After:**
        ```yaml
        spec:
          securityContext:
            seccompProfile:
              type: RuntimeDefault
          containers:
            - name: web
        ```
        
        ---
        
        ## `safeguard-allowed-seccomp-profiles` — Remove 'Unconfined' seccomp profile
        
        **Before:**
        ```yaml
        spec:
          securityContext:
            seccompProfile:
              type: Unconfined
          containers:
            - name: web
        ```
        
        **After:**
        ```yaml
        spec:
          containers:
            - name: web
        ```
        
        ---
        
        ## `safeguard-enforce-apparmor` — Add AppArmor annotation
        
        **Before:**
        ```yaml
        metadata:
          name: my-deployment
        ```
        
        **After:**
        ```yaml
        metadata:
          name: my-deployment
          annotations:
            container.apparmor.security.beta.kubernetes.io/web: runtime/default
        ```
        
        > 💡 **Tip:** Replace `web` with the actual container name. Add one annotation per container.
        
        ---
        
        ## `safeguard-images-no-latest` — Pin image tag *(LLM-reasoned — ask user)*
        
        **Before:**
        ```yaml
        image: myapp:latest
        ```
        
        **After:**
        ```yaml
        image: myapp:v1.2.3   # ← version confirmed with user
        ```
        
        > ⚠️ **Warning:** Do not guess the version. Ask the user: _"What specific version tag or SHA digest should I pin this image to?"_ If from a public registry, suggest checking Docker Hub or the registry for the latest stable tag.
        
        ---
        
        ## `safeguard-probes-configured` — Add probes *(best-practice recommendation — warning-only, not blocked at admission)*
        
        **HTTP app (most common):**
        ```yaml
        readinessProbe:
          httpGet:
            path: /healthz        # ← ask user for their health endpoint
            port: 8080            # ← ask user for port
          initialDelaySeconds: 5
          periodSeconds: 10
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /healthz
            port: 8080
          initialDelaySeconds: 15
          periodSeconds: 20
          failureThreshold: 3
        ```
        
        **TCP-only app (databases, Redis, etc.):**
        ```yaml
        readinessProbe:
          tcpSocket:
            port: 6379           # ← service port
          initialDelaySeconds: 5
          periodSeconds: 10
        livenessProbe:
          tcpSocket:
            port: 6379
          initialDelaySeconds: 15
          periodSeconds: 20
        ```
        
        **gRPC app:**
        ```yaml
        readinessProbe:
          grpc:
            port: 50051
          initialDelaySeconds: 5
          periodSeconds: 10
        ```
        
        ---
        
        ## `safeguard-host-probes` — Remove host field in probes and lifecycle hooks
        
        **Before:**
        ```yaml
        spec:
          containers:
          - name: my-container
            image: nginx:v1.2.3
            livenessProbe:
              httpGet:
                host: "my-host"
                path: /healthz
                port: 8080
              initialDelaySeconds: 15
              periodSeconds: 20
              failureThreshold: 3
        ```
        
        **After:**
        Remove the `host` field
        Example:
        ```yaml
        spec:
          containers:
          - name: my-container
            image: nginx:v1.2.3
            livenessProbe:
              httpGet:
                path: /healthz
                port: 8080
              initialDelaySeconds: 15
              periodSeconds: 20
              failureThreshold: 3
        ```
        
        ---
        
        ## `safeguard-pod-enforce-antiaffinity` — Add topology spread *(LLM-reasoned — ask user for label)*
        
        Ask user: _"What label key/value identifies your workload's pods?"_
        
        ```yaml
        spec:
          template:
            spec:
              topologySpreadConstraints:
                - maxSkew: 1
                  topologyKey: kubernetes.io/hostname
                  whenUnsatisfiable: DoNotSchedule
                  labelSelector:
                    matchLabels:
                      app: <app-label>     # ← from user
              containers:
                - name: web
        ```
        
        ---
        
        ## `safeguard-csi-driver-storage-class` — Migrate in-tree to CSI
        
        **Before (Azure Disk in-tree):**
        ```yaml
        apiVersion: storage.k8s.io/v1
        kind: StorageClass
        metadata:
          name: fast-storage
        provisioner: kubernetes.io/azure-disk
        parameters:
          skuName: Premium_LRS
        reclaimPolicy: Delete
        volumeBindingMode: Immediate
        ```
        
        **After (Azure Disk CSI):**
        ```yaml
        apiVersion: storage.k8s.io/v1
        kind: StorageClass
        metadata:
          name: fast-storage
        provisioner: disk.csi.azure.com
        parameters:
          skuName: Premium_LRS
        reclaimPolicy: Delete
        volumeBindingMode: WaitForFirstConsumer   # ← preferred for zonal disks
        ```
        
        | In-tree provisioner | CSI replacement |
        |---|---|
        | `kubernetes.io/azure-disk` | `disk.csi.azure.com` |
        | `kubernetes.io/azure-file` | `file.csi.azure.com` |
        
        ---
        
        ## PodDisruptionBudget — Add missing PDB
        
        ```yaml
        apiVersion: policy/v1
        kind: PodDisruptionBudget
        metadata:
          name: <app-name>-pdb
          namespace: <namespace>
        spec:
          maxUnavailable: 1
          selector:
            matchLabels:
              app: <app-label>
        ```
        
        ## PodDisruptionBudget — Fix blocking `maxUnavailable: 0`
        
        **Before:**
        ```yaml
        spec:
          maxUnavailable: 0
        ```
        
        **After:**
        ```yaml
        spec:
          maxUnavailable: 1
        ```
        
        > ⚠️ **Warning:** `maxUnavailable: 0` completely blocks node drain during AKS Automatic upgrades. At least 1 pod must be allowed unavailable for upgrades to proceed.
        
        ---
        
        ## `safeguard-no-host-path-volumes` — Replace hostPath *(incompatible — suggest alternatives)*
        
        | hostPath use case | Recommended replacement |
        |---|---|
        | Log collection (`/var/log`) | Azure Monitor Container Insights (auto-enabled on AKS Automatic) |
        | Container runtime socket (`/var/run/docker.sock`) | Use the AKS Automatic node observability features — direct socket access not supported |
        | Shared config files | `configMap` volume |
        | Secrets / credentials | Kubernetes `secret` volume or Azure Key Vault CSI Driver |
        | Ephemeral scratch space | `emptyDir` volume |
        | Persistent app data | Azure Disk CSI via PVC (`disk.csi.azure.com`) |
        | Shared file storage across pods | Azure Files CSI via PVC (`file.csi.azure.com`) |
        
        **emptyDir example:**
        ```yaml
        volumes:
          - name: scratch
            emptyDir: {}
        ```
        
        **Azure Files CSI PVC example:**
        ```yaml
        apiVersion: v1
        kind: PersistentVolumeClaim
        metadata:
          name: logs-pvc
        spec:
          accessModes:
            - ReadWriteMany
          storageClassName: azurefile-csi
          resources:
            requests:
              storage: 10Gi
        ```
        
      • constraint-spec-v1.yaml 19 KB
        # AKS Automatic Compatibility Constraint Spec — Condensed Reference
        # Version: 1.1.1 | AKS: 2026-03-15
        # This condensed version is optimized for LLM context.
        
        apiVersion: aks-automatic.azure.com/v1
        kind: ConstraintSpecReference
        metadata:
          version: "1.1.1"
          aksVersion: "2026-03-15"
          policyInitiatives:
            deploymentSafeguards: c047ea8e-9c78-49b2-958b-37e56d291a44
            podSecurityBaseline: a8640138-9b0a-4a28-b8cb-1666c838647d
            podSecurityRestricted: 42b8ef37-b724-4e24-bbc8-7a7708edfe00
        
        # =============================================================================
        # CLUSTER CONSTRAINTS (23 total)
        # =============================================================================
        clusterConstraints:
          # -- Addons --
          - id: cluster-azure-policy-addon
            severity: requiresChanges
            field: addonProfiles.azurepolicy.enabled
            required: true
            fix: "az aks addon enable --addon azure-policy"
        
          - id: cluster-keyvault-secrets-provider
            severity: requiresChanges
            field: addonProfiles.azureKeyvaultSecretsProvider.enabled
            required:
              enabled: true
              enableSecretRotation: true
            fix: "az aks addon enable --addon azure-keyvault-secrets-provider --enable-secret-rotation"
        
          # -- Networking --
          - id: cluster-api-server-vnet-integration
            severity: requiresChanges
            field: privateConnectProfile.enabled
            required: true
            fix: "az aks update --enable-apiserver-vnet-integration --apiserver-subnet-id <subnet-id>"
        
          - id: cluster-azure-cni-overlay-cilium
            severity: requiresChanges
            field: networkPlugin/networkPluginMode/networkPolicy/ebpfDataplane
            required: azure/overlay/cilium/cilium
            fix: |
              Step 1: az aks update --network-plugin-mode overlay --pod-cidr 192.168.0.0/16
              Step 2: az aks update --network-dataplane cilium
              Note: Irreversible. Disable NAP before Cilium update.
        
          - id: cluster-standard-load-balancer
            severity: requiresChanges
            field: loadBalancerSku
            required: standard
            fix: "az aks update --load-balancer-sku standard (in-place upgrade from Basic supported)"
        
          - id: cluster-nat-gateway-managed-vnet
            severity: requiresChanges
            condition: AKS-managed VNet only
            field: outboundType
            required: managedNATGateway
            fix: "az aks update --outbound-type managedNATGateway"
        
          # -- Upgrades --
          - id: cluster-auto-upgrade
            severity: requiresChanges
            field: autoUpgradeProfile
            required: upgradeChannel=stable, nodeOSUpgradeChannel=NodeImage
            fix: "az aks update --auto-upgrade-channel stable --node-os-upgrade-channel NodeImage"
        
          # -- Ingress --
          - id: cluster-web-app-routing
            severity: requiresChanges
            field: ingressProfile.webAppRouting.enabled
            required: true
            fix: "az aks addon enable --addon web_application_routing"
        
          # -- Identity --
          - id: cluster-workload-identity-oidc
            severity: requiresChanges
            field: workloadIdentity.enabled + oidcProfile.enabled
            required: true
            fix: "az aks update --enable-oidc-issuer --enable-workload-identity"
        
          - id: cluster-azure-rbac
            severity: requiresChanges
            field: aadProfile (managed + enableAzureRBAC)
            required: true
            fix: "az aks update --enable-aad --enable-azure-rbac"
        
          - id: cluster-disable-local-accounts
            severity: requiresChanges
            field: disableLocalAccounts
            required: true
            fix: "az aks update --disable-local-accounts"
        
          - id: cluster-system-assigned-managed-identity
            severity: requiresChanges
            condition: AKS-managed VNet only
            field: identity.type
            required: SystemAssigned
            fix: "Day-0 decision for managed VNet clusters."
        
          # -- Security --
          - id: cluster-image-cleaner
            severity: requiresChanges
            field: securityProfile.imageCleaner.enabled
            required: true
            fix: "az aks update --enable-image-cleaner"
        
          # -- Autoscaling --
          - id: cluster-vpa
            severity: requiresChanges
            field: verticalPodAutoscaler
            required: enabled=true, updateMode=Off
            fix: "az aks update --enable-vpa"
        
          - id: cluster-keda
            severity: requiresChanges
            field: keda.enabled
            required: true
            fix: "az aks update --enable-keda"
        
          - id: cluster-node-auto-provisioning
            severity: requiresChanges
            field: nodeProvisioningProfile.mode
            required: Auto
            fix: "az aks update --node-provisioning-mode Auto"
        
          # -- Governance --
          - id: cluster-node-rg-readonly
            severity: requiresChanges
            field: nodeResourceGroupProfile.restrictionLevel
            required: ReadOnly
            fix: "Day-0 setting. May require new cluster."
        
          # -- Node Pool (system pools) --
          - id: pool-ephemeral-os-disk
            severity: incompatible
            appliesTo: system pools
            field: storageProfile
            required: Ephemeral
            fix: "Day-0. Recreate system node pool."
        
          - id: pool-availability-zones
            severity: incompatible
            appliesTo: system pools only
            field: availabilityZones
            required: "[1, 2, 3]"
            fix: "Day-0. Recreate system pool in 3-AZ region. User pools not affected."
        
          - id: pool-critical-addons-taint
            severity: requiresChanges
            appliesTo: system pools
            field: taints
            required: CriticalAddonsOnly=true:NoSchedule
            fix: "az aks nodepool update --node-taints CriticalAddonsOnly=true:NoSchedule"
        
          - id: pool-vmss-type
            severity: incompatible
            appliesTo: system pools
            field: type
            required: VirtualMachineScaleSets
            fix: "Day-0. Recreate as VMSS."
        
          - id: pool-azure-linux-os
            severity: incompatible
            appliesTo: system pools only
            field: osSKU
            required: AzureLinux
            fix: "Day-0. Recreate system pool with --os-sku AzureLinux. User pools can use any OS."
        
          - id: pool-ssh-disabled
            severity: requiresChanges
            appliesTo: all pools
            field: agentPoolProfiles[*].securityProfile.sshAccess
            required: Disabled
            fix: "az aks nodepool update --cluster-name CLUSTER --name POOL_NAME --ssh-access disabled"
        
        # =============================================================================
        # WORKLOAD CONSTRAINTS — Deployment Safeguards (21 active policies)
        # Initiative: c047ea8e | Effect: Mixed(Deny/Warn/Mutate) on Automatic
        # =============================================================================
        safeguards:
          # -- AKS Best Practices (9 policies) --
          - id: safeguard-restricted-node-edits
            policyId: 53a4a537
            severity: requiresChanges
            category: nodeProtection
            check: Check if a rolebinding for a service account references a role with node edit permissions. The application might try to edit node objects directly
            fix: Manage node pools through the AKS API (az aks nodepool) instead of direct Node object edits
        
          - id: safeguard-container-resource-requests
            policyId: 03a4ecdb
            severity: autoFixed
            category: resources
            check: Every container must have cpu + memory requests and limits
            effect: "ResourceRequestsWorkloadMutator sets defaults cpu=500m, memory=2Gi for requests+limits; enforces minimums cpu=100m, memory=100Mi; fixes QoS if requests > limits"
        
          - id: safeguard-pod-enforce-antiaffinity
            policyId: 34c88cd4
            severity: autoFixed
            category: availability
            check: Replicated workloads with >1 replica should have podAntiAffinity or topologySpreadConstraints
            effect: "AntiAffinityTopologySpreadWorkloadMutator adds preferred anti-affinity (weight=100, hostname) + topology spread (maxSkew=1, hostname, ScheduleAnyway) if neither exists"
        
          - id: safeguard-restricted-labels
            policyId: a22123bd
            severity: requiresChanges
            category: labeling
            check: AKS-reserved label prefixes blocked
            fix: Remove/rename labels with kubernetes.azure.com/ prefix
        
          - id: safeguard-restricted-taints
            policyId: 48940d92
            severity: requiresChanges
            category: nodeProtection
            check: AKS-reserved taint CriticalAddonsOnly key blocked for users
            fix: Remove reserved taints, use custom taint keys
        
          - id: safeguard-probes-configured
            policyId: b1a9997f
            severity: informational
            enforcement: warn  # Warning-only — deployments are admitted with a kubectl warning, not denied
            category: reliability
            check: Every container should have readinessProbe + livenessProbe (recommended best practice)
            fix: Add probes (app-specific — HTTP/TCP/exec/gRPC) — recommended, not required for migration
        
          - id: safeguard-csi-driver-storage-class
            policyId: 4f3823b6
            severity: requiresChanges
            category: storage
            check: StorageClass must use CSI provisioner (not in-tree)
            fix: "Replace kubernetes.io/azure-disk → disk.csi.azure.com, also replace kubernetes.io/azure-file with file.csi.azure.com"
        
          - id: safeguard-unique-service-selectors
            policyId: b0fdedee
            severity: requiresChanges
            category: networking
            check: Services must have unique selectors per namespace
            fix: Deduplicate Service selectors
        
          - id: safeguard-images-no-latest
            policyId: 021f8078
            severity: requiresChanges
            category: imagePolicy
            check: Image tag must not be :latest or untagged (no colon)
            patch: "replace image tag with specific version or sha256 digest"
        
          # -- PSS-related policies in Safeguards (12 policies) --
          - id: safeguard-block-host-namespaces
            policyId: 47a1ee2f
            severity: incompatible
            category: podSecurity
            check: |
              Sharing the host PID or IPC namespaces is disallowed in the Baseline policy.
              Check the following fields:
              - spec.hostPID
              - spec.hostIPC
            fix: |
              The allowed values are:
              - undefined/nil
              - false
              Remove hostPID and hostIPC; incompatible if required.
        
          - id: safeguard-host-network-ports
            policyId: 82985f06
            severity: incompatible
            category: podSecurity
            check: |
              Sharing the host network namespace is disallowed, and host ports should not be used.
              Check the following fields:
              - spec.hostNetwork
              - spec.containers[*].ports[*].hostPort
              - spec.initContainers[*].ports[*].hostPort
              - spec.ephemeralContainers[*].ports[*].hostPort
            fix: |
              The allowed values are:
              - spec.hostNetwork: undefined/nil or false
              - hostPort fields: undefined/nil or 0
              Use ClusterIP Services, Ingress, or internal Pod networking instead of host networking or host ports.
        
          - id: safeguard-allowed-sysctls
            policyId: 5e5a0673
            severity: requiresChanges
            category: podSecurity
            check: |
              Sysctls are limited to the Baseline safe subset.
              Check the following field:
              - spec.securityContext.sysctls[*].name
            fix: |
              The allowed values are:
              - undefined/nil
              - kernel.shm_rmid_forced
              - net.ipv4.ip_local_port_range
              - net.ipv4.ip_unprivileged_port_start
              - net.ipv4.tcp_syncookies
              - net.ipv4.ping_group_range
              - net.ipv4.ip_local_reserved_ports
              - net.ipv4.tcp_keepalive_time
              - net.ipv4.tcp_fin_timeout
              - net.ipv4.tcp_keepalive_intvl
              - net.ipv4.tcp_keepalive_probes
              Remove any sysctl not in this list.
        
          - id: safeguard-no-host-path-volumes
            policyId: 098fc59e
            severity: incompatible
            category: podSecurity
            check: |
              HostPath volumes are forbidden in the Baseline policy.
              Check the following field:
              - spec.volumes[*].hostPath
            fix: |
              The allowed values are:
              - undefined/nil
              Replace hostPath volumes with PVCs, ConfigMaps, Secrets, CSI-backed storage, or another non-hostPath volume type.
        
          - id: safeguard-enforce-apparmor
            policyId: 511f5417
            severity: requiresChanges
            category: podSecurity
            check: |
              On supported hosts, the Baseline policy does not allow disabling the default AppArmor profile.
              Check the following fields:
              - spec.securityContext.appArmorProfile.type
              - spec.containers[*].securityContext.appArmorProfile.type
              - spec.initContainers[*].securityContext.appArmorProfile.type
              - spec.ephemeralContainers[*].securityContext.appArmorProfile.type
              - metadata.annotations["container.apparmor.security.beta.kubernetes.io/*"]
            fix: |
              The allowed values are:
              - appArmorProfile.type: undefined/nil, RuntimeDefault, or Localhost
              - AppArmor annotation: undefined/nil, runtime/default, or localhost/*
              Set RuntimeDefault, or use an allowed Localhost profile.
        
          - id: safeguard-enforce-selinux
            policyId: e1e6c427
            severity: informational
            category: podSecurity
            check: |
              SELinux settings are restricted to specific types, and custom user or role values are forbidden.
              Check the following fields:
              - spec.securityContext.seLinuxOptions.type
              - spec.containers[*].securityContext.seLinuxOptions.type
              - spec.initContainers[*].securityContext.seLinuxOptions.type
              - spec.ephemeralContainers[*].securityContext.seLinuxOptions.type
              - spec.securityContext.seLinuxOptions.user
              - spec.containers[*].securityContext.seLinuxOptions.user
              - spec.initContainers[*].securityContext.seLinuxOptions.user
              - spec.ephemeralContainers[*].securityContext.seLinuxOptions.user
              - spec.securityContext.seLinuxOptions.role
              - spec.containers[*].securityContext.seLinuxOptions.role
              - spec.initContainers[*].securityContext.seLinuxOptions.role
              - spec.ephemeralContainers[*].securityContext.seLinuxOptions.role
            fix: |
              The allowed values are:
              - seLinuxOptions.type: undefined/"", container_t, container_init_t, container_kvm_t, or container_engine_t
              - seLinuxOptions.user: undefined/""
              - seLinuxOptions.role: undefined/""
              Optional hardening only: remove custom seLinuxOptions or use one of the allowed types.
        
          - id: safeguard-windows-block-host-process
            policyId: 077f0ce1
            severity: incompatible
            category: podSecurity
            check: |
              Windows Pods offer the ability to run HostProcess containers which enables privileged access to the Windows host machine. Privileged access to the host is disallowed in the Baseline policy.
              Check the following fields:
              - spec.securityContext.windowsOptions.hostProcess
              - spec.containers[*].securityContext.windowsOptions.hostProcess
              - spec.initContainers[*].securityContext.windowsOptions.hostProcess
              - spec.ephemeralContainers[*].securityContext.windowsOptions.hostProcess
            fix: |
              The allowed values are:
              - undefined/nil
              - false
              Remove hostProcess; incompatible if required.
        
          - id: safeguard-no-privileged-containers
            policyId: 95edb821
            severity: incompatible
            category: podSecurity
            check: |
              Privileged containers are disallowed in the Baseline policy.
              Check the following fields:
              - spec.containers[*].securityContext.privileged
              - spec.initContainers[*].securityContext.privileged
              - spec.ephemeralContainers[*].securityContext.privileged
            fix: |
              The allowed values are:
              - undefined/nil
              - false
              Remove privileged mode or set privileged to false; incompatible if privileged access is required.
        
          - id: safeguard-no-custom-proc-mount
            policyId: f85eb0dd
            severity: requiresChanges
            category: podSecurity
            check: |
              Custom /proc mount types are disallowed.
              Check the following fields:
              - spec.containers[*].securityContext.procMount
              - spec.initContainers[*].securityContext.procMount
              - spec.ephemeralContainers[*].securityContext.procMount
            fix: |
              The allowed values are:
              - undefined/nil
              - Default
              Remove custom procMount values or set procMount to Default.
        
          - id: safeguard-container-capabilities
            policyId: c26596ff
            severity: requiresChanges
            category: podSecurity
            check: |
              Adding capabilities is limited to the Baseline allowlist.
              Check the following fields:
              - spec.containers[*].securityContext.capabilities.add
              - spec.initContainers[*].securityContext.capabilities.add
              - spec.ephemeralContainers[*].securityContext.capabilities.add
            fix: |
              The allowed values are:
              - undefined/nil
              - AUDIT_WRITE
              - CHOWN
              - DAC_OVERRIDE
              - FOWNER
              - FSETID
              - KILL
              - MKNOD
              - NET_BIND_SERVICE
              - SETFCAP
              - SETGID
              - SETPCAP
              - SETUID
              - SYS_CHROOT
              Remove any added capability outside this list.
        
          - id: safeguard-host-probes
            policyId: acdf8909
            severity: requiresChanges
            category: podSecurity
            check: |
              The host field in probes and lifecycle hooks is disallowed
              Restricted fields:
                - spec.containers[*].livenessProbe.httpGet.host
                - spec.containers[*].readinessProbe.httpGet.host
                - spec.containers[*].startupProbe.httpGet.host
                - spec.containers[*].livenessProbe.tcpSocket.host
                - spec.containers[*].readinessProbe.tcpSocket.host
                - spec.containers[*].startupProbe.tcpSocket.host
                - spec.containers[*].lifecycle.postStart.tcpSocket.host
                - spec.containers[*].lifecycle.preStop.tcpSocket.host
                - spec.containers[*].lifecycle.postStart.httpGet.host
                - spec.containers[*].lifecycle.preStop.httpGet.host
                - spec.initContainers[*].livenessProbe.httpGet.host
                - spec.initContainers[*].readinessProbe.httpGet.host
                - spec.initContainers[*].startupProbe.httpGet.host
                - spec.initContainers[*].livenessProbe.tcpSocket.host
                - spec.initContainers[*].readinessProbe.tcpSocket.host
                - spec.initContainers[*].startupProbe.tcpSocket.host
                - spec.initContainers[*].lifecycle.postStart.tcpSocket.host
                - spec.initContainers[*].lifecycle.preStop.tcpSocket.host
                - spec.initContainers[*].lifecycle.postStart.httpGet.host
                - spec.initContainers[*].lifecycle.preStop.httpGet.host
            fix: |
              The allowed values are:
                - undefined/nil
                - ""
              Remove the `host` field from probes and lifecycle hooks; the kubelet uses the pod IP by default.
        
          - id: safeguard-allowed-seccomp-profiles
            policyId: 975ce327
            severity: requiresChanges
            category: podSecurity
            check: |
              Seccomp must not be explicitly set to Unconfined.
              Check the following fields:
              - spec.securityContext.seccompProfile.type
              - spec.containers[*].securityContext.seccompProfile.type
              - spec.initContainers[*].securityContext.seccompProfile.type
              - spec.ephemeralContainers[*].securityContext.seccompProfile.type
            fix: |
              The allowed values are:
              - undefined/nil
              - RuntimeDefault
              - Localhost
              Remove Unconfined, or set seccompProfile.type to RuntimeDefault or Localhost.
        
        # =============================================================================
        # WEBHOOK MUTATIONS (2 active mutators) — auto-applied at admission
        # =============================================================================
        mutations:
          - id: mutation-anti-affinity-topology-spread
            policyId: implicit
            target: [Deployment, StatefulSet, ReplicaSet]
            effect: "Adds preferred pod anti-affinity (weight=100, kubernetes.io/hostname) + topology spread (maxSkew=1, kubernetes.io/hostname, ScheduleAnyway). Skips if any existing anti-affinity or topology spread. Label priority: app > app.kubernetes.io/name > default-antiaffinity-applabel."
        
          - id: mutation-resource-requests-default
            policyId: implicit
            target: containers
            effect: "Sets resources.requests+limits defaults cpu=500m, memory=2Gi. Minimums cpu=100m, memory=100Mi. If only limits set, requests=limits. If requests > limits, requests capped at limits (QoS fix)."
        
        
      • mcp-integration.md 6.5 KB
        # MCP Integration Reference
        
        Loaded when troubleshooting MCP tool calls, debugging the fallback chain, or understanding the API response format.
        
        ---
        
        ## Tool Discovery
        
        Always call `mcp_azure_mcp_aks` first to discover the current available tool surface. Do not assume a fixed action name — the available actions depend on the MCP server version deployed to the client.
        
        ```javascript
        mcp_azure_mcp_aks({ action: "discover" })
        ```
        
        The response lists available actions and their parameter schemas. Use the returned schema — do not hardcode parameter names.
        
        ---
        
        ## Assessment Call
        
        After calling `discover`, use the assessment action name returned in the response. Pass parameters according to the discovered schema — do not hardcode action names or API versions.
        
        Typical parameters include:
        - `subscriptionId` — Azure subscription ID
        - `resourceGroupName` — resource group containing the cluster
        - `resourceName` — AKS cluster name
        - `scope` (optional) — filter by namespaces or workload types
        
        Example shape (use actual action name and schema from discover output):
        ```javascript
        mcp_azure_mcp_aks({
          action: "<action-from-discover>",
          subscriptionId: "<subscription-id>",
          resourceGroupName: "<resource-group>",
          resourceName: "<cluster-name>",
          scope: {
            excludeNamespaces: ["kube-system", "gatekeeper-system", "azure-arc"],
            workloadTypes: ["Deployment", "StatefulSet", "DaemonSet", "CronJob", "Job"]
          }
        })
        ```
        
        All `scope` parameters are optional. If omitted, the API assesses all workloads excluding `kube-system` and `gatekeeper-system`.
        
        ---
        
        ## Required Permissions
        
        ```bash
        # Check current role assignments
        az role assignment list \
          --assignee $(az ad signed-in-user show --query id -o tsv) \
          --scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.ContainerService/managedClusters/<cluster>
        
        # Minimum permissions required:
        # - Microsoft.ContainerService/managedClusters/read
        # - Microsoft.ContainerService/managedClusters/listClusterUserCredential/action
        
        # Assign if missing (requires Owner or User Access Administrator)
        az role assignment create \
          --assignee <principal-id> \
          --role "Azure Kubernetes Service Cluster User Role" \
          --scope /subscriptions/<subscription-id>/resourceGroups/<rg>/providers/Microsoft.ContainerService/managedClusters/<cluster>
        ```
        
        ---
        
        ## Response Schema
        
        The API returns three top-level sections:
        
        ### `summary`
        ```json
        {
          "summary": {
            "totalWorkloads": 42,
            "compatible": 27,
            "requiresChanges": 12,
            "incompatible": 3,
            "autoFixed": 8,
            "clusterConfigIssues": 4
          }
        }
        ```
        
        ### `clusterConfiguration`
        ```json
        {
          "clusterConfiguration": [
            {
              "constraintId": "cluster-oidc-issuer",
              "severity": "requiresChanges",
              "description": "OIDC issuer not enabled",
              "remediation": "az aks update --enable-oidc-issuer --resource-group <rg> --name <cluster>",
              "documentationUrl": "https://learn.microsoft.com/azure/aks/..."
            }
          ]
        }
        ```
        
        ### `workloads[]`
        ```json
        {
          "workloads": [
            {
              "name": "sample-app",
              "namespace": "default",
              "kind": "Deployment",
              "overallStatus": "requiresChanges",
              "issues": [
                {
                  "constraintId": "safeguard-images-no-latest",
                  "severity": "requiresChanges",
                  "description": "Container 'web' uses :latest image tag",
                  "field": "/spec/containers/0/image",
                  "suggestedPatch": null,
                  "remediationGuide": "Pin the image to a specific version or SHA digest"
                }
              ]
            }
          ]
        }
        ```
        
        ---
        
        ## Async Response Handling (HTTP 202 — Large Clusters)
        
        For clusters with 500+ workloads, the API returns HTTP 202 Accepted with a `Location` header. Poll until complete:
        
        ```javascript
        // Initial call returns: { status: 202, headers: { Location: "...", "Retry-After": "30" } }
        async function pollAssessment(locationUrl, retryAfterSeconds) {
          while (true) {
            await new Promise(r => setTimeout(r, retryAfterSeconds * 1000));
            const response = await mcp_azure_mcp_aks({
              action: "pollOperation",
              locationUrl: locationUrl
            });
            if (response.status === "Succeeded") return response.result;
            if (response.status === "Failed") throw new Error(response.error.message);
            retryAfterSeconds = response.retryAfter ?? retryAfterSeconds;
          }
        }
        ```
        
        ---
        
        ## Fallback Chain
        
        Attempt each step in order. Do not ask the user which is available — just try:
        
        ```
        1. mcp_azure_mcp_aks → discover, then call the assessment action returned
           ↓ fails (tool not found — Azure MCP server not configured)
        
        2. Inform user to install Azure MCP, then fall back to offline validation
           kubectl get deployment,statefulset,daemonset,job,cronjob -A -o yaml > /tmp/workloads.yaml
           kubectl get pdb,storageclass -A -o yaml > /tmp/policies.yaml
        ```
        
        If `mcp_azure_mcp_aks` is not available, say:
        > "The Azure MCP server is not configured. To enable live cluster assessment, install it following [aka.ms/azure-mcp-setup](https://aka.ms/azure-mcp-setup). For now, I can validate your local manifests offline — export them with `kubectl get ... -o yaml` or share your manifest files."
        
        Then proceed to offline manifest validation against `constraint-spec-v1.yaml`.
        
        ---
        
        ## Prerequisites Verification
        
        Run these before attempting MCP or CLI assessment:
        
        ```bash
        # 1. Verify Azure login
        az account show --query "{name:name, id:id, state:state}" -o table
        
        # 2. Verify cluster exists and is accessible
        az aks show \
          --resource-group <rg> \
          --name <cluster> \
          --query "{name:name, provisioningState:provisioningState, sku:sku.name}" \
          -o table
        
        # 3. Verify kubectl context
        kubectl config current-context
        kubectl cluster-info
        ```
        
        ```javascript
        // 4. Verify MCP server is reachable (Azure MCP)
        // If this returns available actions, MCP is configured
        mcp_azure_mcp_aks({ action: "discover" })
        ```
        
        ---
        
        ## Common MCP Errors
        
        | Error | Cause | Fix |
        |---|---|---|
        | `tool not found: mcp_azure_mcp_aks` | Azure MCP server not configured | Guide user to install: [aka.ms/azure-mcp-setup](https://aka.ms/azure-mcp-setup), then fall back to offline |
        | `HTTP 401 Unauthorized` | Not logged in | `az login` |
        | `HTTP 403 Forbidden` | Insufficient RBAC permissions | Ensure caller has read access to the cluster via AKS APIs |
        | `HTTP 404 Not Found` | Wrong subscription, RG, or cluster name | Verify with `az aks list -o table` |
        | `HTTP 202` with no Location header | API version mismatch | Ensure the MCP server version supports async polling; retry with the latest server |
        | Timeout after 30s | Cluster too large (500+ workloads) | Implement async polling — see section above |
        
      • migration-guide-summary.md 4.4 KB
        # AKS Automatic Migration Guide
        
        Loaded when user asks about migration steps or after assessment is complete.
        
        ---
        
        ## Migration Checklist
        
        ### Phase 1 — Assessment (this skill)
        
        - [ ] Run the AKS Automatic compatibility assessment (via `mcp_azure_mcp_aks({ action: "discover" })` then the assessment action returned, or the offline manifest scan)
        - [ ] Resolve all `incompatible` findings — these are hard blockers
        - [ ] Apply all `requiresChanges` fixes — these will be denied at admission
        - [ ] Review `autoFixed` items — understand what AKS Automatic will mutate at runtime
        - [ ] Address cluster-level Day-0 config issues (see below)
        
        ### Phase 2 — Create AKS Automatic Cluster (use `azure-kubernetes` skill)
        
        ```bash
        az aks create \
          --resource-group <resource-group> \
          --name <new-cluster-name> \
          --sku automatic \
          --location <location> \
          --generate-ssh-keys
        ```
        
        > 💡 **Tip:** AKS Automatic auto-enables: OIDC issuer, workload identity, Azure CNI Overlay, NAP, VPA, Azure Monitor Container Insights, Deployment Safeguards, and Pod Security Standards (Baseline). No manual configuration needed for these.
        
        ### Phase 3 — Validate on New Cluster
        
        ```bash
        # Get credentials
        az aks get-credentials \
          --resource-group <resource-group> \
          --name <new-cluster-name>
        
        # Dry-run server-side apply — catches admission policy rejections
        kubectl apply --dry-run=server -f <manifests-directory>/
        
        # Deploy to a staging namespace first
        kubectl create namespace staging
        kubectl apply -f <manifests-directory>/ -n staging
        
        # Watch pod startup
        kubectl get pods -n staging -w
        
        # Check events for admission rejections
        kubectl get events -n staging --sort-by=.lastTimestamp | grep -i "denied\|error\|failed"
        ```
        
        > ⚠️ **Keep the old cluster running** for a rollback window (recommended: 48 hours minimum) while you validate workloads on the new AKS Automatic cluster.
        
        ### Phase 4 — Decommission Old Cluster
        
        ```bash
        # Only after confirming workloads are stable on AKS Automatic
        az aks delete \
          --resource-group <resource-group> \
          --name <old-cluster-name> \
          --yes --no-wait
        ```
        
        ---
        
        ## Day-0 Decisions — Cluster-Level Configuration Requirements
        
        Some settings require creating a **new** cluster; others can be enabled on existing clusters. Route to `azure-kubernetes` skill for cluster creation.
        
        | Requirement | AKS Automatic default | What to do |
        |---|---|---|
        | API Server VNet Integration | Required, auto-enabled | Requires a new cluster |
        | Network plugin | Azure CNI Overlay | Requires a new cluster if currently on kubenet |
        | System node pool OS | Azure Linux | Recreate system node pool (user pools unaffected) |
        | OIDC Issuer | Auto-enabled | Can be enabled on existing: `az aks update --enable-oidc-issuer` |
        | Workload Identity | Auto-enabled | Can be enabled on existing: `az aks update --enable-workload-identity` |
        
        ---
        
        ## What AKS Automatic Auto-Enables
        
        No manual setup needed for these — show this list when user asks "what do I get for free":
        
        | Feature | Benefit |
        |---|---|
        | Node Auto Provisioning (NAP) | Replaces cluster autoscaler; right-sizes node pools automatically |
        | Vertical Pod Autoscaler (VPA) | Auto-tunes resource requests after deployment |
        | Azure Monitor Container Insights | Logs, metrics, and dashboards out of the box |
        | Deployment Safeguards | 25 active deny policies + 2 webhook mutators at admission (resource-requests defaults + anti-affinity/topology-spread) |
        | Pod Security Standards (Baseline) | Enforced cluster-wide; Restricted available opt-in |
        | Managed OIDC Issuer | Required for workload identity |
        | Azure Key Vault CSI Driver | Secret injection without static credentials |
        | Ephemeral OS disks | Faster node provisioning by default |
        | Azure Linux node OS | Smaller footprint, faster boot times |
        
        ---
        
        ## Post-Migration Verification Commands
        
        ```bash
        # Verify all pods running
        kubectl get pods -A | grep -v Running | grep -v Completed
        
        # Check for pods stuck in Pending (may indicate resource quota or node issues)
        kubectl get pods -A --field-selector status.phase=Pending
        
        # Check Deployment Safeguards are active
        kubectl get constrainttemplate -A
        
        # Verify VPA is running
        kubectl get vpa -A
        
        # Check NAP node pools
        az aks nodepool list \
          --resource-group <resource-group> \
          --cluster-name <cluster-name> \
          --query "[].{name:name, mode:mode, osType:osType, count:count}" \
          -o table
        
        # View Container Insights metrics
        az aks show \
          --resource-group <resource-group> \
          --name <cluster-name> \
          --query addonProfiles.omsagent.enabled
        ```
        
    • SKILL.md 14.5 KB
      ---
      name: azure-kubernetes-automatic-readiness
      license: MIT
      metadata:
        author: Microsoft
        version: "1.0.1"
      description: "Assess Kubernetes workloads and cluster configuration for AKS Automatic compatibility. Identifies incompatibilities, generates fixes, and guides migration from AKS Standard to AKS Automatic. WHEN: migrate to AKS Automatic, check AKS Automatic readiness, validate manifests for Automatic, assess cluster for Automatic compatibility, fix deployment for Automatic compatibility, identify AKS Automatic migration blockers, is my cluster ready for AKS Automatic."
      ---
      
      # AKS Automatic Readiness Assessment
      
      > **AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE**
      >
      > This skill assesses existing AKS clusters or local manifests for AKS Automatic compatibility.
      > For creating a new AKS Automatic cluster, use the `azure-kubernetes` skill instead.
      > See [constraint spec](./references/constraint-spec-v1.yaml) for all safeguard rules, [common fixes](./references/common-fixes.md) for YAML patterns, [migration guide](./references/migration-guide-summary.md) for end-to-end steps, and [MCP integration](./references/mcp-integration.md) for tool details and fallback handling.
      
      You are an AKS Automatic compatibility assessment agent. Your job is to evaluate whether Kubernetes workloads and cluster configurations are compatible with [AKS Automatic](https://learn.microsoft.com/en-us/azure/aks/intro-aks-automatic), identify issues, and help users fix them.
      
      AKS Automatic enforces **Deployment Safeguards** (21 active policies, some deny, some warn only), **Pod Security Standards** (Baseline mandatory, Restricted optional), **2 active webhook mutators** that auto-fix certain fields at admission (resource-requests defaults and anti-affinity/topology-spread), and **23 cluster-level configuration requirements**.
      
      ## Quick Reference
      | Property | Value |
      |----------|-------|
      | Best for | AKS Automatic migration readiness and manifest validation |
      | MCP Tools | `mcp_azure_mcp_aks` |
      | Related skills | azure-kubernetes (cluster creation), azure-diagnostics (live troubleshooting), azure-validate (readiness checks) |
      
      ## When to Use This Skill
      - "Can I migrate to AKS Automatic?"
      - "Check my cluster readiness for Automatic"
      - "Validate manifests against AKS Automatic constraints"
      - "Fix my deployment for Automatic compatibility"
      - "Identify AKS Automatic migration blockers"
      - Any mention of AKS Automatic + (migration | readiness | compatibility | assessment | validation)
      
      ## Routing Rules
      
      ### Route to `azure-kubernetes` instead:
      - "Create an AKS cluster" / "What are AKS best practices?" / "How do I deploy to AKS?"
      - General cluster creation, configuration, scaling, or AKS operations
      
      ### Route to `azure-diagnostics` instead:
      - "My pod is crashing" / "Debug my AKS cluster" / "Why is my deployment failing?"
      - Live troubleshooting, debugging, error diagnosis on a running cluster
      
      ## Guardrails — READ FIRST
      
      1. **Read-only**: NEVER modify cluster state. Assessment is read-only. Do not run `kubectl apply`, `az aks update`, or any command that changes the cluster.
      2. **No secrets**: Do NOT transmit, display, or include in diffs: Secret data values, ConfigMap data values, environment variable values from `valueFrom.secretKeyRef`, service account tokens, or connection strings.
      3. **User approval for file changes**: Present every fix as a diff. The user must explicitly accept before you write to any file.
      4. **Scope boundaries**: Route cluster creation/deletion questions → `azure-kubernetes` skill. Route live troubleshooting → `azure-diagnostics` skill.
      
      ## MCP Tools
      | Tool | Purpose | Key Parameters |
      |------|---------|----------------|
      | `mcp_azure_mcp_aks` | AKS MCP entry point — call `discover` first, then use the assessment action name returned in the response | `subscriptionId`, `resourceGroupName`, `resourceName`, `scope` |
      
      ## Workflow
      
      ### Step 1: Determine Scope
      
      Ask the user what they want to assess:
      
      **Option A — Cluster-connected assessment (via AKS MCP)**
      Use when the user has a connected cluster context (subscription + resource group + cluster name).
      
      **Option B — Offline manifest validation**
      Use when the user has local Kubernetes manifests, Helm charts, or Kustomize overlays in their workspace. Search for files containing `apiVersion:` and `kind:` matching Deployment, StatefulSet, DaemonSet, Job, CronJob, Pod, Service, PodDisruptionBudget, or StorageClass. For Helm charts, look for `Chart.yaml` and rendered templates under `templates/`.
      
      **Option C — Single manifest check**
      If the user pastes or points to a single YAML manifest, validate it directly without asking for scope.
      
      ### Step 2: Run Assessment
      
      #### Cluster-Connected Mode
      
      Call the AKS MCP tool — this is the preferred path. Always call `discover` first to get the available actions, then use the assessment action name returned in the response:
      
      ```javascript
      // Step 1: Discover available actions
      mcp_azure_mcp_aks({ action: "discover" })
      
      // Step 2: Use the assessment action name from the discover response
      mcp_azure_mcp_aks({
        action: "<action-from-discover>",
        subscriptionId: "<subscription-id>",
        resourceGroupName: "<resource-group>",
        resourceName: "<cluster-name>",
        scope: {
          excludeNamespaces: ["kube-system", "gatekeeper-system"],
          workloadTypes: ["Deployment", "StatefulSet", "DaemonSet", "CronJob", "Job"]
        }
      })
      ```
      
      **Required permissions:**
      - `Microsoft.ContainerService/managedClusters/read`
      - `Microsoft.ContainerService/managedClusters/listClusterUserCredential/action`
      
      For large clusters (500+ workloads), the API may return HTTP 202 with a `Location` header. Poll the location URL using the `Retry-After` interval until a 200 response is received.
      
      **Parsing the MCP response:**
      1. **`summary`** — aggregate counts: `compatible`, `requiresChanges`, `incompatible`, `autoFixed`, `totalWorkloads`, `clusterConfigIssues`
      2. **`clusterConfiguration`** — cluster-level issues with `constraintId`, `severity`, `remediation` (az CLI commands), and `documentationUrl`
      3. **`workloads[]`** — per-workload array, each with `name`, `namespace`, `kind`, `overallStatus`, and `issues[]`
      
      Each issue in `workloads[].issues[]` contains: `constraintId`, `severity` (`incompatible`/`requiresChanges`/`autoFixed`/`informational`), `description`, `field` (JSON Pointer), `suggestedPatch` (JSON Patch for deterministic fixes), `remediationGuide` (for LLM-reasoned fixes).
      
      #### Fallback Chain
      
      ```
      1. MCP tool (mcp_azure_mcp_aks)  → preferred, live cluster data
         ↓ fails (tool not found — Azure MCP server not configured)
      2. Offline validation            → works on local manifests without any cluster
      ```
      
      If `mcp_azure_mcp_aks` is not available, inform the user:
      > "The Azure MCP server is not configured in your editor. To enable live cluster assessment, follow the setup guide at [aka.ms/azure-mcp-setup](https://aka.ms/azure-mcp-setup). For now, I can validate your local manifests offline."
      
      Then proceed to offline mode.
      
      #### Offline Mode
      
      Load the constraint spec from `references/constraint-spec-v1.yaml` and evaluate each manifest. The check field tells you what to check for and what fields to check. The fix field will tell you any allowed values and possible fixes. You should evaluate each of the safeguards with each of the manifests to determine if the manifests are compatible. Suggest any fixes that are needed.
      
      Key Checks: 
      **Per container** (containers, initContainers, ephemeralContainers):
      - Resource requests/limits → `safeguard-container-resource-requests`
      - Readiness and liveness probes → `safeguard-probes-configured` *(warning-only — not blocked at admission; treat as informational)*
      - Image tag not `:latest` → `safeguard-images-no-latest`
      - `securityContext.privileged` not true → `safeguard-no-privileged-containers`
      - `capabilities.add` only adds allowed capabilities → `safeguard-container-capabilities`
      - `seccompProfile` is RuntimeDefault/Localhost → `safeguard-allowed-seccomp-profiles`
      - no `host` field in any container probes and lifecycle hooks → `safeguard-host-probes`
      
      **Per pod spec:**
      - `hostPID`/`hostIPC` not true → `safeguard-block-host-namespaces` (incompatible)
      - `hostNetwork`/`hostPort` not true → `safeguard-host-network-ports` (incompatible)
      - No `hostPath` volumes → `safeguard-no-host-path-volumes` (incompatible)
      
      **Per workload type:**
      - Deployments/StatefulSets with replicas > 1: podAntiAffinity or topologySpreadConstraints → `safeguard-pod-enforce-antiaffinity`
      - StorageClass: CSI provisioner (not in-tree) → `safeguard-csi-driver-storage-class`
      
      
      ### Severity Classification
      
      | Severity | Meaning | Action |
      |----------|---------|--------|
      | `incompatible` | Fundamental architecture issue; cannot run on Automatic without redesign | Must fix before migration — flag prominently |
      | `requiresChanges` | Manifest changes needed; will be denied at admission | Generate fix diffs |
      | `autoFixed` | AKS Automatic will mutate this at admission; no user action needed | Informational — show what will change |
      | `informational` | No enforcement | Mention briefly |
      
      ### Step 3: Present Findings
      
      Always start with the summary:
      
      ```
      ## AKS Automatic Readiness Assessment
      
      | Status | Count |
      |--------|-------|
      | ✅ Compatible | X workloads |
      | ⚠️ Requires changes | Y workloads |
      | ❌ Incompatible | Z workloads |
      | 🔧 Auto-fixed by Automatic | W workloads |
      | 🏗️ Cluster config issues | N issues |
      ```
      
      Grouping: ≤ 10 issues → list individually; > 10 → group by constraint ID. Always show **incompatible** first (migration blockers), then **requiresChanges**, then **autoFixed**, then cluster config.
      
      Per-issue format:
      ```
      ### ❌ [constraint-id] — Short description
      **Severity:** incompatible | requiresChanges
      **Affected:** namespace/resource-name (Kind)
      **Current:** <what the manifest has>
      **Required:** <what AKS Automatic requires>
      **Fix:** <remediation summary>
      **Docs:** <documentation URL>
      ```
      
      ### Step 4: Offer Fixes
      
      **Deterministic fixes** (have `suggestedPatch` — generate YAML diff directly):
      - `safeguard-container-resource-requests` — add `resources.requests`
      - `safeguard-container-capabilities` — remove `capabilities.add`
      - `safeguard-allowed-seccomp-profiles` — patch only when `seccompProfile.type: Unconfined` is present, or when the MCP `suggestedPatch` explicitly requires a seccomp change
      - `safeguard-enforce-apparmor` — add AppArmor annotation
      - `safeguard-csi-driver-storage-class` — replace in-tree provisioner
      
      Use patterns in `references/common-fixes.md` and generate a before/after diff. Starting resource values use safe defaults — VPA (enabled on Automatic) will auto-tune after deployment.
      
      **LLM-reasoned fixes** (require app context; use `remediationGuide`):
      - `safeguard-images-no-latest` — correct tag is user- and release-specific; ask the user: _"What specific version tag or SHA digest should I pin this image to?"_ Do not guess
      - `safeguard-pod-enforce-antiaffinity` — needs app labels for selector
      - `safeguard-no-host-path-volumes` — replacement depends on what hostPath is used for
      - `safeguard-block-host-namespaces` — may require architecture redesign
      - `safeguard-host-network-ports` — needs alternative networking approach
      
      For incompatible findings (e.g., hostPath volumes), explain the issue and propose alternatives. For log-collection hostPath, suggest: Azure Monitor Container Insights (recommended, auto-enabled), Azure Files CSI volume, emptyDir, or sidecar pattern.
      
      **Fix application flow:**
      1. Generate the fix as a YAML diff
      2. Show the diff with explanation
      3. Wait for explicit approval: "apply", "edit", or "skip"
      4. On approval, apply the change to the file
      5. Move to the next finding
      
      If the user says "fix all" or "apply all deterministic fixes", first generate a single combined diff containing all eligible `suggestedPatch`-based fixes, show that combined diff with an explanation, and wait for one explicit approval before applying any writes. After approval, apply the batched changes and then suggest re-validation.
      
      ### Step 5: Recommend Next Steps
      
      **All issues resolved (or only autoFixed remaining):**
      ```
      Your workloads are ready for AKS Automatic! Next steps:
      1. Review auto-fixed items — AKS Automatic will mutate N fields at admission.
      2. Apply cluster configuration changes (see cluster config issues above).
      3. Perform the SKU switch — follow the migration guide.
      4. Verify — after migration, check all workloads are running and healthy.
      ```
      See `references/migration-guide-summary.md` for the full migration checklist.
      
      **Incompatible findings remain:** List blockers and offer three options: redesign workloads, keep on a separate AKS Standard cluster, or use Automatic for compatible + Standard for incompatible workloads.
      
      **Cluster config issues remain (Day-0 decisions):** API Server VNet Integration, node pool OS SKU (requires recreating system node pools), and ephemeral OS disks require a new cluster — redirect to `azure-kubernetes` skill for cluster creation help.
      
      ## Error Handling
      
      | Error / Symptom | Likely Cause | Remediation |
      |-----------------|--------------|-------------|
      | MCP tool call fails or times out | Invalid credentials or subscription context | Verify `az login`, confirm active subscription with `az account show`; if MCP remains unavailable, continue with offline validation using local or exported manifests and the bundled constraint spec |
      | HTTP 403 on assessment action | Missing permission | Ensure caller has sufficient RBAC access to read and assess the cluster via AKS APIs |
      | API returns HTTP 202 | Large cluster (500+ workloads) — async operation | Poll the `Location` header URL using `Retry-After` interval |
      | Helm chart uses Go templating — cannot evaluate | Template values not resolved | Ask user for rendered output (`helm template`) or values files |
      | Constraint spec version mismatch | Skill bundles spec v1.1.1 (2026-03-15) | Note version in output; recommend re-running after spec update |
      
      ## Reference Files
      
      | File | When to load |
      |------|--------------|
      | `references/constraint-spec-v1.yaml` | Always load for offline validation — all constraint IDs, severities, and fix patterns |
      | `references/common-fixes.md` | When generating deterministic fixes — before/after YAML patterns |
      | `references/migration-guide-summary.md` | When user asks about migration steps or after assessment is complete |
      | `references/mcp-integration.md` | When troubleshooting MCP tool calls or debugging the fallback chain |
      
      > ⚠️ **Warning:** This skill bundles **constraint spec v1.1.1** (2026-03-15), covering 23 cluster-level constraints, 21 active Deployment Safeguards policies (9 best practices policies, 12 Pod Security Standards policies), and 2 active mutators. Always note the spec version in assessment output.
      
  • references
    • azure-aks-autoscaler.md 3 KB
      # AKS Cluster Autoscaler (CAS)
      
      Enable and tune the Cluster Autoscaler to automatically scale down idle nodes.
      
      ## Check CAS Status
      
      ```bash
      az aks show \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --query "agentPoolProfiles[].{name:name, casEnabled:enableAutoScaling, min:minCount, max:maxCount, count:count}" \
        -o table
      
      az aks show \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --query "autoScalerProfile" -o json
      ```
      
      ## Check Node Utilization (7 days)
      
      Follow the metrics discovery steps in [azure-aks-rightsizing.md](./azure-aks-rightsizing.md#historical-metrics-azure-monitor--use-when-prometheus-or-container-insights-is-enabled) to list available metric definitions and query node CPU utilization. Use metric names such as `node_cpu_usage_percentage` or `cpuUsagePercentage` depending on what's available on the cluster.
      
      ## Enable CAS
      
      ```bash
      # Cluster-level
      az aks update \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --enable-cluster-autoscaler \
        --min-count <MIN_NODES> --max-count <MAX_NODES>
      
      # Specific node pool
      az aks nodepool update \
        --cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --name "<NODEPOOL_NAME>" \
        --enable-cluster-autoscaler \
        --min-count <MIN_NODES> --max-count <MAX_NODES>
      ```
      
      ## Recommended min/max Defaults
      
      | Scenario | min-count | max-count |
      |----------|-----------|-----------|
      | Dev/test | 1 | current_count |
      | Production (web/API) | 2 | current_count * 3 |
      | Production (batch) | 0 | current_count * 5 |
      
      > Risk: Low. CAS only scales down when pods can be safely rescheduled. Set min-count >= 2 for production HA.
      
      ## Tune CAS Profile
      
      Apply when CAS is already on but idle nodes persist:
      
      > ⚠️ **Warning:** Setting `skip-nodes-with-system-pods=false` allows CAS to evict system pods. Ensure all system pods in `kube-system` have PodDisruptionBudgets before enabling this.
      
      ```bash
      az aks update \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --cluster-autoscaler-profile \
          scale-down-delay-after-add=10m \
          scale-down-unneeded-time=10m \
          scale-down-utilization-threshold=0.5 \
          max-graceful-termination-sec=600 \
          skip-nodes-with-system-pods=false
      ```
      
      To roll back to CAS defaults:
      
      ```bash
      az aks update \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --cluster-autoscaler-profile ""
      ```
      
      ## Profile Comparison
      
      | Profile | scale-down-delay-after-add | scale-down-unneeded-time | utilization-threshold | Best For |
      |---------|----------------------------|--------------------------|----------------------|----------|
      | Default | 10m | 10m | 0.5 | General workloads |
      | Cost-Optimized | 5m | 5m | 0.5 | Cost-sensitive, non-critical |
      | Conservative | 30m | 30m | 0.7 | Stateful / production |
      | Aggressive | 2m | 2m | 0.4 | Dev/test, batch |
      
      > Risk: High for aggressive tuning. Ensure PodDisruptionBudgets (PDBs) are set on critical workloads before tuning. Always confirm with user before applying.
      >
      > Check existing PDBs before tuning:
      > ```bash
      > kubectl get pdb --all-namespaces
      > ```
      
      
      
    • azure-aks-rightsizing.md 4.3 KB
      # AKS Pod Rightsizing
      
      Identify pods requesting far more CPU/memory than they use and recommend reduced resource requests.
      
      ## Prerequisites — Check Monitoring State First
      
      Before collecting usage data, determine what monitoring is available on the cluster:
      
      ```bash
      # 1. Check if Azure Managed Prometheus is enabled
      az aks show \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --query "azureMonitorProfile.metrics.enabled" -o tsv
      
      # 2. Check if Container Insights (Log Analytics) is enabled
      az aks show \
        --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --query "addonProfiles.omsagent.enabled" -o tsv
      
      # 3. Check if Metrics Server is running (pre-installed on AKS, but may be unhealthy)
      kubectl get deployment metrics-server -n kube-system
      ```
      
      Based on the result, follow the appropriate path:
      
      | State | Rightsizing Possible? | Data Source | Accuracy |
      |-------|-----------------------|-------------|----------|
      | Azure Managed Prometheus enabled |  Yes | Prometheus metrics via Azure Monitor | Best — full P95/7-day history |
      | Container Insights (Log Analytics) enabled |  Yes | KQL queries on `Perf` / `KubePodInventory` | Good — 7-day trends |
      | Only Metrics Server (no Azure Monitor) |  Limited | `kubectl top pods` — live data only | Low — no historical trends |
      
      > If nothing is enabled, Metrics Server is pre-installed on AKS — confirm it is healthy and use it for live rightsizing data:
      > ```bash
      > kubectl get deployment metrics-server -n kube-system
      > kubectl top pods --all-namespaces --sort-by=cpu
      > ```
      > For historical P95 trends (more accurate rightsizing), recommend enabling Azure Managed Prometheus. Warn user this incurs cost and wait for confirmation before proceeding.
      
      ---
      
      ## Detection
      
      ```bash
      # Authenticate to cluster
      az aks get-credentials --name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>"
      
      # List requests/limits for ALL containers per pod (includes sidecars)
      # Using [*] ensures multi-container pods are not misrepresented
      kubectl get pods --all-namespaces \
        -o custom-columns="NAMESPACE:.metadata.namespace,POD:.metadata.name,CONTAINERS:.spec.containers[*].name,CPU_REQ:.spec.containers[*].resources.requests.cpu,MEM_REQ:.spec.containers[*].resources.requests.memory,CPU_LIM:.spec.containers[*].resources.limits.cpu,MEM_LIM:.spec.containers[*].resources.limits.memory"
      
      # Live per-container usage (shows each container individually, including sidecars)
      kubectl top pods --all-namespaces --containers --sort-by=cpu
      ```
      
      ## Historical Metrics (Azure Monitor — use when Prometheus or Container Insights is enabled)
      
      First discover available metric names, then query:
      
      ```bash
      az monitor metrics list-definitions \
        --resource "<AKS_RESOURCE_ID>" \
        --query "[].name.value" -o tsv
      ```
      
      ```bash
      az monitor metrics list \
        --resource "<AKS_RESOURCE_ID>" \
        --metric "<METRIC_NAME_FROM_ABOVE>" \
        --interval PT1H --aggregation Average \
        --start-time "<YYYY-MM-DDTHH:mm:ssZ>" \
        --end-time "<YYYY-MM-DDTHH:mm:ssZ>"
      ```
      
      ## Optimization Rules
      
      | Condition | Recommendation | Risk |
      |-----------|----------------|------|
      | CPU request >5x P95 actual | Reduce to `P95 * 1.2` | Medium |
      | Memory request >3x P95 actual | Reduce to `P95 * 1.2` | Medium |
      | CPU request >2x P95 actual | Recommend rightsizing with 20% buffer | Low |
      | No resource limits set | Add limits to prevent noisy-neighbor waste | Low |
      | No VPA/HPA configured | Suggest enabling Vertical Pod Autoscaler | Low |
      
      > For VPA setup and configuration, see [azure-aks-vpa.md](./azure-aks-vpa.md).
      
      ## YAML Patch Format
      ```yaml
      # Rightsizing patch for <NAMESPACE>/<DEPLOYMENT_NAME>
      # Current: CPU request=<CURRENT>, P95 actual=<ACTUAL>
      # Recommended: CPU request=<NEW> (P95 * 1.2 buffer)
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: <DEPLOYMENT_NAME>
        namespace: <NAMESPACE>
      spec:
        template:
          spec:
            containers:
            - name: <CONTAINER_NAME>
              resources:
                requests:
                  cpu: "<NEW_CPU>"
                  memory: "<NEW_MEM>"
                limits:
                  cpu: "<NEW_CPU_LIMIT>"     # e.g. CPU limit = 1.5x CPU request, or preserve existing limit-to-request ratio
                  memory: "<NEW_MEM_LIMIT>"  # e.g. memory limit = 1.25x memory request, or preserve existing limit-to-request ratio
      ```
      
      > Risk: Medium-High. Always review patches before applying. Test in non-production first. Get explicit user confirmation before applying to production.
      
    • azure-aks-spot.md 3.7 KB
      # AKS Spot Node Pools
      
      Recommend and create Spot VM node pools for batch, dev/test, or fault-tolerant workloads (60-90% cost reduction vs regular nodes).
      
      ## Check Existing Node Pools
      
      ```bash
      az aks nodepool list \
        --cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --query "[].{name:name, vmSize:vmSize, priority:scaleSetPriority, count:count, mode:mode}" \
        -o table
      ```
      
      ## Identify Spot-Suitable Workloads
      
      Before creating a Spot pool, identify which workloads can tolerate interruptions:
      
      ```bash
      # List deployments without PodDisruptionBudgets (single-replica or no PDB = higher eviction risk)
      kubectl get deployments --all-namespaces -o json | \
        jq -r '.items[] | select(.spec.replicas == 1) | "\(.metadata.namespace)/\(.metadata.name)"'
      
      # Check which pods already have spot tolerations
      kubectl get pods --all-namespaces -o json | \
        jq -r '.items[] | select(.spec.tolerations[]?.key == "kubernetes.azure.com/scalesetpriority") | "\(.metadata.namespace)/\(.metadata.name)"'
      ```
      
      Use the suitability table below to decide which workloads to migrate.
      
      ## Mixed Node Pool Pattern (Spot + Regular)
      
      For workloads that need resilience but want cost savings, use a mixed approach:
      
      ```bash
      # Keep existing regular node pool as fallback (min 1-2 nodes)
      az aks nodepool update \
        --cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --name "<REGULAR_POOL>" \
        --enable-cluster-autoscaler --min-count 1 --max-count 3
      
      # Add Spot pool for the majority of workload capacity
      # -1 means pay up to on-demand price (no cap); set e.g. 0.05 to cap hourly spend
      az aks nodepool add \
        --cluster-name "<CLUSTER_NAME>" --resource-group "<RESOURCE_GROUP>" \
        --name "<SPOT_POOL_NAME>" \
        --priority Spot --eviction-policy Delete --spot-max-price -1 \
        --node-vm-size "<VM_SIZE>" \
        --node-count 3 --min-count 0 --max-count 10 \
        --enable-cluster-autoscaler \
        --node-taints "kubernetes.azure.com/scalesetpriority=spot:NoSchedule" \
        --labels "kubernetes.azure.com/scalesetpriority=spot"
      ```
      
      Pods that tolerate Spot but don't require it (no `nodeSelector` or required node affinity pinning them to the Spot pool) will be rescheduled onto the regular pool after eviction. Pods pinned to Spot via `nodeSelector` cannot reschedule and will remain pending until a Spot node is available again.
      
      ## Workload Toleration (add to Deployment YAML)
      
      ```yaml
      tolerations:
      - key: "kubernetes.azure.com/scalesetpriority"
        operator: "Equal"
        value: "spot"
        effect: "NoSchedule"
      nodeSelector:
        kubernetes.azure.com/scalesetpriority: spot
      ```
      
      ## Suitability
      
      | Workload | Spot-Suitable? |
      |----------|----------------|
      | Batch / data processing | Yes |
      | Dev / test environments | Yes |
      | Stateless web/API (replicas >= 2) | Yes (with care) |
      | Jobs with checkpointing | Yes |
      | Stateful workloads (databases) | No |
      | Single-replica critical services | No |
      
      > Risk: Low for batch/dev. High for production stateful workloads. Spot VMs evict with 30-second notice. Eviction policy Delete is recommended for AKS.
      
      ## Handling Eviction Gracefully
      
      Configure workloads to handle the 30-second eviction notice:
      
      ```yaml
      # Add to Deployment spec — terminationGracePeriodSeconds should be < 30s for Spot
      spec:
        template:
          spec:
            terminationGracePeriodSeconds: 25
            containers:
            - name: <CONTAINER_NAME>
              lifecycle:
                preStop:
                  exec:
                    command: ["/bin/sh", "-c", "sleep 5"]  # Drain in-flight requests
      ```
      
      Set a PodDisruptionBudget to limit simultaneous evictions:
      
      ```bash
      kubectl apply -f - <<EOF
      apiVersion: policy/v1
      kind: PodDisruptionBudget
      metadata:
        name: <APP_NAME>-pdb
        namespace: <NAMESPACE>
      spec:
        minAvailable: 1
        selector:
          matchLabels:
            app: <APP_NAME>
      EOF
      ```
      
    • azure-aks-vpa.md 1.4 KB
      # AKS Vertical Pod Autoscaler (VPA)
      
      Use VPA to get data-driven resource recommendations for rightsizing pods. Always start in recommendation-only mode before considering auto-apply.
      
      ## Enable VPA (Recommendation Mode)
      
      ```bash
      # Enable VPA addon on AKS cluster (if not already enabled)
      az aks update --enable-vpa --resource-group <RESOURCE_GROUP> --name <CLUSTER_NAME>
      
      # Create a VPA object in recommendation mode for a deployment
      kubectl apply -f - <<EOF
      apiVersion: autoscaling.k8s.io/v1
      kind: VerticalPodAutoscaler
      metadata:
        name: <DEPLOYMENT_NAME>-vpa
        namespace: <NAMESPACE>
      spec:
        targetRef:
          apiVersion: apps/v1
          kind: Deployment
          name: <DEPLOYMENT_NAME>
        updatePolicy:
          updateMode: "Off"   # Recommendation only — does not modify pods
      EOF
      
      # Read recommendations after 24+ hours of data collection
      kubectl describe vpa <DEPLOYMENT_NAME>-vpa -n <NAMESPACE>
      ```
      
      > Risk: Low in "Off" mode. **Do not use `updateMode: Auto` in production** without thorough testing and explicit user confirmation.
      
      ## Read VPA Recommendations
      
      ```bash
      kubectl get vpa <DEPLOYMENT_NAME>-vpa -n <NAMESPACE> -o jsonpath='{.status.recommendation}'
      ```
      
      The output shows `lowerBound`, `target`, and `upperBound` for CPU and memory. Use the `target` values as rightsized requests.
      
      ## Apply Recommendations Manually
      
      After reviewing VPA output, patch the deployment — see [azure-aks-rightsizing.md](./azure-aks-rightsizing.md#yaml-patch-format) for the patch format.
      
    • cli-reference.md 1.2 KB
      # CLI Reference for AKS
      
      ```bash
      # List AKS clusters
      az aks list --output table
      
      # Show cluster details
      az aks show --name <cluster-name> --resource-group <resource-group>
      
      # Get available Kubernetes versions
      az aks get-versions --location <location> --output table
      
      # Create AKS Automatic cluster
      az aks create --name <cluster-name> --resource-group <resource-group> --sku automatic \
        --network-plugin azure --network-plugin-mode overlay \
        --enable-oidc-issuer --enable-workload-identity
      
      # Create AKS Standard cluster
      az aks create --name <cluster-name> --resource-group <resource-group> \
        --node-count 3 --zones 1 2 3 \
        --network-plugin azure --network-plugin-mode overlay \
        --enable-cluster-autoscaler --min-count 1 --max-count 10 \
        --enable-oidc-issuer --enable-workload-identity
      
      # Get credentials
      az aks get-credentials --name <cluster-name> --resource-group <resource-group>
      
      # List node pools
      az aks nodepool list --cluster-name <cluster-name> --resource-group <resource-group> --output table
      
      # Enable monitoring
      az aks enable-addons --name <cluster-name> --resource-group <resource-group> \
        --addons monitoring --workspace-resource-id <workspace-resource-id>
      ```
      
  • SKILL.md 10.6 KB
    ---
    name: azure-kubernetes
    license: MIT
    metadata:
      author: Microsoft
      version: "1.2.2"
    description: "Plan, create, and configure production-ready Azure Kubernetes Service (AKS) clusters. Covers Day-0 checklist, SKU selection (Automatic vs Standard), networking options (private API server, Azure CNI Overlay, egress configuration), security, and operations (autoscaling, upgrade strategy, cost analysis). WHEN: create AKS environment, provision AKS, enable AKS observability, design AKS networking, choose AKS SKU, secure AKS, optimize AKS, AKS spot nodes, AKS cluster-autoscaler, rightsize AKS pod, pod rightsizing, over-provisioned AKS pod, pod resource requests and limits, Vertical Pod Autoscaler, VPA recommendations."
    ---
    
    # Azure Kubernetes Service
    
    > **AUTHORITATIVE GUIDANCE — MANDATORY COMPLIANCE**
    >
    > This skill produces a **recommended AKS cluster configuration** based on user requirements, distinguishing **Day-0 decisions** (networking, API server — hard to change later) from **Day-1 features** (can enable post-creation). See [CLI reference](./references/cli-reference.md) for commands.
    
    ## Quick Reference
    | Property | Value |
    |----------|-------|
    | Best for | AKS cluster planning and Day-0 decisions |
    | MCP Tools | `mcp_azure_mcp_aks` |
    | CLI | `az aks create`, `az aks show`, `kubectl get`, `kubectl describe` |
    | Related skills | azure-kubernetes-app-deploy (deploy an app to an existing cluster), azure-diagnostics (troubleshooting AKS), azure-validate (readiness checks), azure-kubernetes-automatic-readiness (migrate existing cluster to AKS Automatic) |
    
    ## When to Use This Skill
    Activate this skill when user wants to:
    - Create a new AKS cluster
    - Plan AKS cluster configuration for production workloads
    - Design AKS networking (API server access, pod IP model, egress)
    - Set up AKS identity and secrets management
    - Configure AKS governance (Azure Policy, Deployment Safeguards)
    - Enable AKS observability (Container Insights, Managed Prometheus, Grafana)
    - Define AKS upgrade and patching strategy
    - Understand AKS Automatic vs Standard SKU differences
    - Get a Day-0 checklist for AKS cluster setup and configuration
    
    > **Deploying an application to an existing cluster?** This skill provisions and
    > configures the *cluster*. To containerize an app and deploy it to a cluster
    > that already exists (Dockerfile + manifests + Deployment Safeguards), use the
    > `azure-kubernetes-app-deploy` sub-skill instead.
    
    ## Rules
    1. Start with the user's requirements for provisioning compute, networking, security, and other settings.
    2. Use the `azure` MCP server and select `mcp_azure_mcp_aks` first to discover the exact AKS-specific MCP tools surfaced by the client. Choose the smallest discovered AKS tool that fits the task, and fall back to Azure CLI (`az aks`) only when the needed functionality is not exposed through the AKS MCP surface.
    3. Determine if AKS Automatic or Standard SKU is more appropriate based on the user's need for control vs convenience. Default to AKS Automatic unless specific customizations are required.
    4. Document decisions and rationale for cluster configuration choices, especially for Day-0 decisions that are hard to change later (networking, API server access).
    
    
    ## Required Inputs (Ask only what’s needed)
    If the user is unsure, use safe defaults.
    - AKS environment type: dev/test or production
    - Region(s), availability zones, preferred node VM sizes
    - Expected scale (node/cluster count, workload size)
    - Networking requirements (API server access, pod IP model, ingress/egress control)
    - Security and identity requirements, including image registry
    - Upgrade and observability preferences
    - Cost constraints
    
    ## Workflow
    
    ### 1. Cluster Type
    - **AKS Automatic** (default): Best for most production workloads, provides a curated experience with pre-configured best practices for security, reliability, and performance. Use unless you have specific custom requirements for networking, autoscaling, or node pool configurations not supported by Node Auto-Provisioning (NAP).
    - **AKS Standard**: Use if you need full control over environment configuration, which requires additional overhead to set up and manage.
    
    ### 2. Networking (Pod IP, Egress, Ingress, Dataplane)
    
    **Pod IP Model** (Key Day-0 decision):
    - **Azure CNI Overlay** (recommended): pod IPs from private overlay range, not VNet-routable, scales to large environments and good for most workloads
    - **Azure CNI (VNet-routable)**: pod IPs directly from VNet (pod subnet or node subnet), use when pods must be directly addressable from VNet or on-prem
      - Docs: https://learn.microsoft.com/azure/aks/azure-cni-overlay
    
    **Dataplane & Network Policy**:
    - **Azure CNI powered by Cilium** (recommended): eBPF-based for high-performance packet processing, network policies, and observability
    
    **Egress**:
    - **Static Egress Gateway** for stable, predictable outbound IPs
    - For restricted egress: UDR + Azure Firewall or NVA
    
    **Ingress**:
    - **App Routing addon with Gateway API** — recommended default for HTTP/HTTPS workloads
    - **Istio service mesh with Gateway API** - for advanced traffic management, mTLS, canary releases
    - **Application Gateway for Containers** — for L7 load balancing with WAF integration
    
    **DNS**:
    - Enable **LocalDNS** on all node pools for reliable, performant DNS resolution
    
    ### 3. Security
    - Use **Microsoft Entra ID** everywhere (control plane, Workload Identity for pods, node access). Avoid static credentials.
    - Azure Key Vault via **Secrets Store CSI Driver** for secrets
    - Enable **Azure Policy** + **Deployment Safeguards**
    - Enable **Encryption at rest** for etcd/API server; **in-transit** for node-to-node
    - Allow only signed, policy-approved images (Azure Policy + Ratify), prefer **Azure Container Registry**
    - **Isolation**: Use namespaces, network policies, scoped logging
    
    ### 4. Observability
    - Use Managed Prometheus and Container Insights with Grafana for AKS observability (logs + metrics).
    - Enable Diagnostic Settings to collect control plane logs and audit logs in a Log Analytics workspace for security monitoring and troubleshooting.
    - For other monitoring and troubleshooting tools, use features like the Agentic CLI for AKS, Application Insights, Resource Health Center, AppLens detectors, and Azure Advisors.
    
    ### 5. Upgrades & Patching
    - Configure **Maintenance Windows** for controlled upgrade timing
    - Enable **auto-upgrades** for control plane and node OS to stay up-to-date with security patches and Kubernetes versions
    - Consider **LTS versions** for enterprise stability (2-year support) by upgrading your AKS environment to the Premium tier
    - **Fleet upgrades**: Use **AKS Fleet Manager** for staged rollout across test to production environments
    
    ### 6. Performance
    - Use **Ephemeral OS disks** (`--node-osdisk-type Ephemeral`) for faster node startup
    - Select **Azure Linux** as node OS (smaller footprint, faster boot)
    - Enable **KEDA** for event-driven autoscaling beyond HPA
    
    ### 7. Node Pools & Compute
    - **Dedicated system node pool**: At least 2 nodes, tainted for system workloads only (`CriticalAddonsOnly`)
    - Enable **Node Auto Provisioning (NAP)** on all pools for cost savings and responsive scaling
    - Use **latest generation SKUs (v5/v6)** for host-level optimizations
    - **Avoid B-series VMs** — burstable SKUs cause performance/reliability issues
    - Use SKUs with **at least 4 vCPUs** for production workloads
    - Set **topology spread constraints** to distribute pods across hosts/zones per SLO
    
    ### 8. Reliability
    - Deploy across **3 Availability Zones** (`--zones 1 2 3`)
    - Use **Standard tier** for zone-redundant control plane + 99.95% SLA for API server availability
    - Enable **Microsoft Defender for Containers** for runtime protection
    - Configure **PodDisruptionBudgets** for all production workloads
    - Use **topology spread constraints** to ensure pod distribution across failure domains
    
    ### 9. Cost Controls
    - Use **Spot node pools** for batch/interruptible workloads (up to 90% savings)
    - **Stop/Start** dev/test clusters: `az aks stop/start`
    - Consider **Reserved Instances** or **Savings Plans** for steady-state workloads
    
    **Deep-dive scenarios** — load only the relevant reference file:
    
    | Scenario | Trigger Keywords | Reference |
    |----------|-----------------|-----------|
    | Pod Rightsizing | over-provisioned pods, CPU requests, memory requests, rightsize workloads | [azure-aks-rightsizing.md](./references/azure-aks-rightsizing.md) |
    | VPA Setup | vertical pod autoscaler, VPA recommendations, VPA enable | [azure-aks-vpa.md](./references/azure-aks-vpa.md) |
    | Cluster Autoscaler | idle nodes, CAS off, enable autoscaler, scale-down profile, node utilization | [azure-aks-autoscaler.md](./references/azure-aks-autoscaler.md) |
    | Spot Node Pools | Spot VMs, Spot nodes, batch workloads, cheaper nodes | [azure-aks-spot.md](./references/azure-aks-spot.md) |
    
    > **Disambiguation:** If a prompt matches multiple rows (e.g., "cheaper nodes" could suggest both Spot and autoscaler), prefer the most specific match. If ambiguous, ask the user to clarify their intent before loading a reference file.
    
    ## Guardrails / Safety
    - Do not request or output secrets (tokens, keys).
    - Do not ask the user to paste subscription IDs. Discover subscription and resource scope via MCP tools (e.g., list subscriptions, list resource groups) or `az account show` / `az account list` so the agent can resolve context without exposing identifiers.
    - If requirements are ambiguous for day-0 critical decisions, ask the user clarifying questions. For day-1 enabled features, propose 2–3 safe options with tradeoffs and choose a conservative default.
    - Do not promise zero downtime; advise workload safeguards (PDBs, probes, replicas) and staged upgrades along with best practices for reliability and performance.
    
    ## MCP Tools
    | Tool | Purpose | Key Parameters |
    |------|---------|----------------|
    | `mcp_azure_mcp_aks` | AKS MCP entry point used to discover the exact AKS-specific tools exposed by the client | Discover the callable AKS tool first, then use that tool's parameters |
    
    ## Error Handling
    | Error / Symptom | Likely Cause | Remediation |
    |-----------------|--------------|-------------|
    | MCP tool call fails or times out | Invalid credentials, subscription, or AKS context | Verify `az login`, confirm the active subscription context with `az account show`, and check the target resource group without echoing subscription identifiers back to the user |
    | Quota exceeded | Regional vCPU or resource limits | Request quota increase or select different region/VM SKU |
    | Networking conflict (IP exhaustion) | Pod subnet too small for overlay/CNI | Re-plan IP ranges; may require cluster recreation (Day-0) |
    | Workload Identity not working | Missing OIDC issuer or federated credential | Enable `--enable-oidc-issuer --enable-workload-identity`, configure federated identity |
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related