End-to-End AKS Deployment (No Helm)

Post-Bicep workflow: Maven build → local run → Docker build/run → ACR tag/login/push → AKS deploy → Ingress controller install via YAML (no Helm) → Ingress deploy → Ops commands → Chrome test.

Maven Docker ACR AKS Ingress (YAML) No Helm

Assumptions (post-Bicep)

  • You already created an Azure Resource Group, AKS, and ACR using Bicep.
  • Your Spring Boot project builds target/app.jar (<finalName>app</finalName> in the POM).
  • You have deployment.yml (Deployment + Service) and ingress.yml (Ingress) files.
  • You will install NGINX Ingress Controller using kubectl apply of official YAML manifests (no Helm).
What you get at the end
  • Image pushed to ACR
  • App running on AKS
  • Ingress exposed with a hostname
  • Operational commands for troubleshooting and logs
  • Chrome-ready URL
Tip
Keep a single terminal session with exported environment variables so every command stays consistent and scriptable.

0) Export environment variables

One-time per terminal session
Required exports
export AZ_SUBSCRIPTION_ID="<your-subscription-id>"
export AZ_RESOURCE_GROUP="rg-aks-spring-demo"
export AZ_LOCATION="eastus"

export AKS_NAME="aks-spring-demo"
export ACR_NAME="<your-acr-name>"                 # no .azurecr.io
export ACR_LOGIN_SERVER="${ACR_NAME}.azurecr.io"

export APP_IMAGE_NAME="spring-ui-api"
export APP_IMAGE_VERSION="1.0"
export APP_PORT="9087"

export INGRESS_HOSTNAME="spring-ui-api.dev.yourcompany.com"
Azure login + subscription context
az login
az account set --subscription "$AZ_SUBSCRIPTION_ID"
az account show --output table

This ensures every subsequent az command operates against the correct subscription.

1) Build & run locally (no Docker)

mvn -U -DskipTests clean package
ls -lh target/app.jar

java -jar target/app.jar
Test locally:
  • http://localhost:9087/
  • http://localhost:9087/actuator/health

2) Docker: build & run locally

2A) Build Docker image
docker build -t ${APP_IMAGE_NAME}:${APP_IMAGE_VERSION} .
docker images | grep "${APP_IMAGE_NAME}" | head
2B) Run Docker image locally
docker run --rm \
  -p 9087:9087 \
  --name ${APP_IMAGE_NAME} \
  ${APP_IMAGE_NAME}:${APP_IMAGE_VERSION}
Local test:
  • http://localhost:9087/
  • http://localhost:9087/actuator/health

3) ACR: tag, login, push

3A) Tag image for ACR
docker tag ${APP_IMAGE_NAME}:${APP_IMAGE_VERSION} \
  ${ACR_LOGIN_SERVER}/${APP_IMAGE_NAME}:${APP_IMAGE_VERSION}

This does not upload anything. It only creates a new tag reference pointing to the same local image layers.
3B) Login to ACR (recommended)
az acr login --name "$ACR_NAME"

3C) Push the image
docker push ${ACR_LOGIN_SERVER}/${APP_IMAGE_NAME}:${APP_IMAGE_VERSION}
Verify repository and tags:
az acr repository list --name "$ACR_NAME" --output table
az acr repository show-tags --name "$ACR_NAME" --repository "$APP_IMAGE_NAME" --output table
Note about docker login
Your Bicep disables ACR admin user (adminUserEnabled: false), so prefer az acr login. Username/password based docker login is generally not desired unless you explicitly enabled admin.

4) AKS: connect kubectl and deploy the app

4A) Get AKS kubeconfig credentials
az aks get-credentials \
  --resource-group "$AZ_RESOURCE_GROUP" \
  --name "$AKS_NAME" \
  --overwrite-existing

kubectl get nodes
4B) Deploy (Deployment + Service)
Your deployment.yml contains this placeholder: image: REPLACEME_ACR_LOGIN/spring-ui-api:1.0
sed -i.bak "s|REPLACEME_ACR_LOGIN|${ACR_LOGIN_SERVER}|g" deployment.yml
kubectl apply -f deployment.yml
Verify:
kubectl get deploy
kubectl get pods -l app=spring-ui-api -o wide
kubectl get svc spring-ui-api-svc -o wide

5) Install NGINX Ingress Controller (No Helm)

kubectl apply YAML
Why this step is required
A Kubernetes Ingress object is only a rule definition. You need an Ingress Controller (NGINX here) running in the cluster to implement those rules and expose an external endpoint.
5A) Apply official ingress-nginx cloud manifests
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml

5B) Wait until controller is ready
kubectl get pods -n ingress-nginx -w

5C) Get ingress controller external IP
kubectl get svc -n ingress-nginx ingress-nginx-controller -o wide
Use the EXTERNAL-IP for DNS mapping in the next step. If it shows <pending>, wait and retry.

6) Deploy Ingress rules + DNS

6A) Replace Ingress hostname placeholder
Your ingress.yml has: host: REPLACEME_HOSTNAME
sed -i.bak "s|REPLACEME_HOSTNAME|${INGRESS_HOSTNAME}|g" ingress.yml
kubectl apply -f ingress.yml
Verify:
kubectl get ingress
kubectl describe ingress spring-ui-api-ingress
6B) DNS requirement (for Chrome)
Create a DNS A record:
INGRESS_HOSTNAME NGINX EXTERNAL-IP
Temporary local test option
If you don’t have DNS yet, you can temporarily map the hostname using /etc/hosts:
sudo sh -c 'echo "<EXTERNAL-IP> spring-ui-api.dev.yourcompany.com" >> /etc/hosts'

7) Operations: pods, deployments, services, logs

kubectl get all
kubectl get deploy,rs,pods,svc,ingress -o wide
kubectl get pods -l app=spring-ui-api -o wide
kubectl get endpoints spring-ui-api-svc -o wide

kubectl describe deploy spring-ui-api
kubectl describe pod -l app=spring-ui-api
kubectl describe svc spring-ui-api-svc
kubectl describe ingress spring-ui-api-ingress
Use describe when you see image pull failures, probe failures, 502/503, or unexpected restarts.

kubectl logs -l app=spring-ui-api --tail=200
kubectl logs -l app=spring-ui-api -f

kubectl get pods -n ingress-nginx
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller --tail=200
kubectl logs -n ingress-nginx -l app.kubernetes.io/component=controller -f

kubectl get events --sort-by=.metadata.creationTimestamp | tail -n 80
kubectl rollout status deployment/spring-ui-api
kubectl rollout history deployment/spring-ui-api

8) Test the deployed site (Chrome)

8A) Browser URLs
  • Home: http://INGRESS_HOSTNAME/
  • Health: http://<hostname>/actuator/health

If you later add TLS, switch to https://.
8B) Command-line smoke tests
curl -i http://${INGRESS_HOSTNAME}/
curl -i http://${INGRESS_HOSTNAME}/actuator/health
If Chrome fails but curl works, suspect caching/DNS resolution differences. If both fail, inspect ingress and service endpoints.
Completion criteria
You can open the hostname in Chrome, the page loads, and the health endpoint returns OK through the ingress path.

One-pass (No Helm) command sequence

Copy/paste friendly
# Build JAR
mvn -U -DskipTests clean package

# Build Docker image locally
docker build -t ${APP_IMAGE_NAME}:${APP_IMAGE_VERSION} .

# Azure login & subscription
az login
az account set --subscription "$AZ_SUBSCRIPTION_ID"

# Login to ACR and push
az acr login --name "$ACR_NAME"
docker tag ${APP_IMAGE_NAME}:${APP_IMAGE_VERSION} ${ACR_LOGIN_SERVER}/${APP_IMAGE_NAME}:${APP_IMAGE_VERSION}
docker push ${ACR_LOGIN_SERVER}/${APP_IMAGE_NAME}:${APP_IMAGE_VERSION}

# Connect kubectl to AKS
az aks get-credentials -g "$AZ_RESOURCE_GROUP" -n "$AKS_NAME" --overwrite-existing

# Deploy app (Deployment + Service)
sed -i.bak "s|REPLACEME_ACR_LOGIN|${ACR_LOGIN_SERVER}|g" deployment.yml
kubectl apply -f deployment.yml

# Install NGINX ingress controller (NO HELM)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.11.3/deploy/static/provider/cloud/deploy.yaml
kubectl get svc -n ingress-nginx ingress-nginx-controller -w

# Deploy ingress rules
sed -i.bak "s|REPLACEME_HOSTNAME|${INGRESS_HOSTNAME}|g" ingress.yml
kubectl apply -f ingress.yml

# Verify
kubectl get pods,svc,ingress -o wide
kubectl logs -l app=spring-ui-api --tail=200