Setting the file. One moment.
Deploy · Azure Kubernetes App Deploy · microsoft/azure-skills · Skills Docs
ContentsBack to the top of the page templates/github-actions/ deploy.yml
YAML · 194 lines · 7 KB
15
16 name : Deploy to AKS
17
18 on :
19 # Trigger on push to main branch (app code changes only)
20 push :
21 branches :
22 - main
23 paths-ignore :
24 - 'docs/**'
25 - '*.md'
26 - '.github/**'
27 - '.vscode/**'
28
29 # Allow manual trigger from the Actions tab
30 workflow_dispatch :
31
32 # OIDC federation requires these permissions so GitHub can issue
33 # an ID token that Microsoft Entra ID will accept.
34 permissions :
35 id-token : write # Required for requesting the JWT
36 contents : read # Required for actions/checkout
37
38 # Prevent parallel deployments on the same branch.
39 # Uses workflow + ref so staging and production runs can proceed independently.
40 # cancel-in-progress: false ensures the running deploy finishes
41 # before the queued deploy starts (avoids mid-rollout conflicts).
42 # Note: env context is not available here — use github or vars contexts only.
43 concurrency :
44 group : ${{ github.workflow }}-${{ github.ref_name }}
45 cancel-in-progress : false
46
47 env :
48 ACR_NAME : __ACR_NAME__
49 AKS_CLUSTER : __AKS_CLUSTER__
50 RESOURCE_GROUP : __RG_NAME__
51 APP_NAME : __APP_NAME__
52 NAMESPACE : __NAMESPACE__
53
54 defaults :
55 run :
56 shell : bash
57
58 jobs :
59 build-and-deploy :
60 runs-on : ubuntu-latest
61
62 steps :
63 - name : Checkout repository
64 uses : actions/checkout@v4
65
66 # -----------------------------------------------------------
67 # Validate that no placeholders remain unreplaced
68 #
69 # Checks for __PLACEHOLDER__ and <placeholder> patterns in
70 # env vars and k8s/ directory. Fails fast with clear error
71 # if any found.
72 # -----------------------------------------------------------
73 - name : Validate — no unreplaced placeholders
74 run : |
75 PLACEHOLDERS_FOUND=0
76
77 # Check env variables
78 for VAR in ACR_NAME AKS_CLUSTER RESOURCE_GROUP APP_NAME NAMESPACE; do
79 VALUE="${!VAR}"
80 if [[ "$VALUE" =~ __[A-Z_]+__ ]]; then
81 echo "❌ Placeholder found in \$${VAR}: ${VALUE}"
82 PLACEHOLDERS_FOUND=1
83 fi
84 done
85
86 # Check k8s/ directory if it exists
87 if [ -d k8s ]; then
88 # __PLACEHOLDER__ style (env var style used in this workflow)
89 if grep -rq '__[A-Z_]\+__' k8s/; then
90 echo "❌ Placeholders found in k8s/ manifests:"
91 grep -rn '__[A-Z_]\+__' k8s/ || true
92 PLACEHOLDERS_FOUND=1
93 fi
94 # <placeholder> style (angle-bracket style used in K8s manifest templates).
95 # Exclude <image>: it is intentionally left in place here and replaced
96 # with the SHA-tagged image in the "Substitute image tag" step below.
97 if grep -rnP '<[a-z][a-z0-9-]*>' k8s/ | grep -vq '<image>'; then
98 echo "❌ Angle-bracket placeholders found in k8s/ manifests:"
99 grep -rnP '<[a-z][a-z0-9-]*>' k8s/ | grep -v '<image>' || true
100 PLACEHOLDERS_FOUND=1
101 fi
102 fi
103
104 if [ $PLACEHOLDERS_FOUND -eq 1 ]; then
105 echo ""
106 echo "⚠️ Workflow failed: unreplaced placeholders detected."
107 echo "Replace the following in your deploy.yml:"
108 echo " - __ACR_NAME__ → Your Container Registry name"
109 echo " - __AKS_CLUSTER__ → Your AKS cluster name"
110 echo " - __RG_NAME__ → Your resource group name"
111 echo " - __APP_NAME__ → Your application name"
112 echo " - __NAMESPACE__ → Your Kubernetes namespace"
113 exit 1
114 fi
115
116 echo "✓ All placeholders replaced"
117
118 # -----------------------------------------------------------
119 # Authenticate to Azure using OIDC (workload identity)
120 #
121 # This exchanges the GitHub-issued OIDC token for an Azure
122 # access token — no client secret required.
123 # -----------------------------------------------------------
124 - name : Azure Login (OIDC)
125 uses : azure/login@v2
126 with :
127 client-id : ${{ secrets.AZURE_CLIENT_ID }}
128 tenant-id : ${{ secrets.AZURE_TENANT_ID }}
129 subscription-id : ${{ secrets.AZURE_SUBSCRIPTION_ID }}
130
131 # -----------------------------------------------------------
132 # Build container image and push to ACR
133 #
134 # `az acr build` runs the Docker build remotely on ACR,
135 # so no local Docker daemon is needed. The image is tagged
136 # with the commit SHA for traceability.
137 # -----------------------------------------------------------
138 - name : Build and push image to ACR
139 run : |
140 az acr build \
141 --registry ${{ env.ACR_NAME }} \
142 --image ${{ env.APP_NAME }}:${{ github.sha }} \
143 .
144
145 - name : Set AKS context
146 uses : azure/aks-set-context@v4
147 with :
148 resource-group : ${{ env.RESOURCE_GROUP }}
149 cluster-name : ${{ env.AKS_CLUSTER }}
150
151 # -----------------------------------------------------------
152 # Deploy to AKS
153 #
154 # Applies all K8s resources (with substituted image tag),
155 # then waits for the rollout to complete successfully.
156 # Sets a step output flag used to gate the rollback step.
157 # -----------------------------------------------------------
158 - name : Substitute image tag in manifests
159 env :
160 IMAGE : ${{ env.ACR_NAME }}.azurecr.io/${{ env.APP_NAME }}:${{ github.sha }}
161 run : |
162 if [ ! -d k8s ]; then
163 echo "❌ k8s/ directory not found — cannot deploy without manifests"
164 exit 1
165 fi
166 # Use xargs to preserve sed exit codes (find|while swallows them)
167 find k8s -name "*.yaml" -o -name "*.yml" \
168 | xargs -I{} sed -i "s|<image>|${IMAGE}|g" "{}"
169 echo "✓ Image tag substituted in all manifests"
170
171 - name : Deploy to AKS
172 id : deploy
173 run : |
174 # Ensure the namespace exists before applying manifests
175 kubectl create namespace ${{ env.NAMESPACE }} --dry-run=client -o yaml \
176 | kubectl apply -f -
177 kubectl apply -f k8s/ --namespace ${{ env.NAMESPACE }}
178
179 kubectl rollout status deployment/${{ env.APP_NAME }} \
180 --namespace ${{ env.NAMESPACE }} \
181 --timeout=300s
182
183 # Signal that the deployment was applied — used to gate rollback
184 echo "deployed=true" >> "$GITHUB_OUTPUT"
185
186 - name : Rollback on failure
187 if : failure() && steps.deploy.outputs.deployed == 'true'
188 run : |
189 kubectl rollout undo deployment/${{ env.APP_NAME }} \
190 --namespace ${{ env.NAMESPACE }}
191 kubectl rollout status deployment/${{ env.APP_NAME }} \
192 --namespace ${{ env.NAMESPACE }} \
193 --timeout=120s
194 echo "⚠️ Rolled back to previous revision"