5.2. Push and Deploy
Push the container image to ACR, write a raw Kubernetes manifest, and deploy the app to the cluster using
kubectl — no Pulumi yet, so you can see what raw Kubernetes resources look like.
Step 1 — Push the image to ACR
Log in to the registry and push the image you built in the previous lab. All values come from Pulumi stack outputs — no copy-pasting from the portal.
Apple Silicon (M1/M2/M3)
AKS nodes run on AMD64. If you built the image on an Apple Silicon Mac, rebuild for the correct platform before pushing:
Confirm the image arrived:
Explanation
az acr login exchanges your current az CLI identity for a short-lived Docker credential stored in the
local Docker credential store — no username or password needed. The AKS node pool can already pull from
this registry because the AcrPull role assignment was provisioned by Pulumi in Chapter 3.
| Command | Purpose |
|---|---|
az acr login | Authenticates Docker to the registry using your CLI identity |
docker tag … :latest | Creates a local alias pointing at the ACR login server |
docker push | Transfers the image layers to the registry |
Step 2 — Write the Kubernetes manifests
Create deployment.yaml inside cloud-native-lab/csharp-app/, replacing YOUR_ACR_LOGIN_SERVER with the
value of $ACR_SERVER from the previous step:
Explanation
A Deployment and a Service are the two objects needed to run and reach any workload in Kubernetes:
| Object | Role |
|---|---|
Deployment | Declares the desired number of pod replicas and the container image to run |
Service | Provides a stable network endpoint that routes traffic to all matching pods |
selector.matchLabels on the Deployment and selector on the Service both reference app: messages-app
— this label is the glue that connects the three objects (Deployment, Pod, Service) without any explicit
reference by name.
The Service maps port 80 → container port 8080. Clients always talk to port 80; the container always
listens on 8080 (ASPNETCORE_URLS=http://+:8080 from the Dockerfile).
No DB_CONNECTION_STRING is set, so the app falls back to a local SQLite file inside the pod — enough for
smoke-testing the deployment path.
Step 3 — Deploy and verify
Apply the manifest and wait for the pod to become ready.
Once STATUS shows Running, check the app is reachable via port-forward:
Open http://localhost:8080, submit a message, and verify it appears in the list. Check the health endpoint:
Expected response:
Stop the port-forward with Ctrl-C. Inspect the running pod:
Explanation
kubectl apply -f is idempotent — running it again when nothing has changed is safe. The -w flag on
get pods streams updates until you interrupt it; use it to watch the pod transition from Pending to
Running.
kubectl port-forward -n messages-app svc/messages-app 8080:80 opens a tunnel from your local machine directly to the
Service. It does not go through the ingress controller — useful for quick verification without needing
DNS or TLS in place.
Data does not survive a pod restart
The SQLite file lives inside the pod’s ephemeral container filesystem. Delete the pod (kubectl delete pod <name>) and the Deployment will schedule a new one — but all submitted messages will be gone. Persistent
storage requires an external database, which you will provision in a later step.