Bicep Deployment Stacks: Lifecycle management bez ručního cleanupu
Bicep Deployment Stacks: Lifecycle management bez ručního cleanupu
Klasický Bicep deployment řeší jeden problém: jak nasadit resources. Neřeší dva další: jak je v čase udržovat jako celek a jak je bezpečně odstranit. Deployment Stacks (GA od konce 2024) tohle dorovnávají a v Christie's i Nespresso jsme je v roce 2026 zavedli jako default pattern pro aplikační deploymenty.
Tenhle článek shrnuje, kdy je použít, jak migrovat existující prostředí a kde jsou pasti.
Problém, který Deployment Stack řeší
Scénář, který každý IaC engineer zná: tým má main.bicep s Function App, Storage Account, Key Vault a třemi role assignmenty. Po půl roce už není v šabloně Storage Account potřeba, někdo ho odstraní z main.bicep, mergne PR, spustí az deployment group create. Co se stane?
Storage Account v Azure zůstane. Klasický deployment je idempotentní pouze pro create/update, ne pro delete. Resource zůstává až do ručního smazání. Po roce máte 50 orphan resources, které někdo musí proklikat.
Deployment Stack řeší tohle:
# Bez stacku – Storage zůstane v Azure i po odebrání z šablony
az deployment group create \
--resource-group rg-prod \
--template-file main.bicep
# Se stackem – Storage se smaže při dalším update, pokud chybí v šabloně
az stack group create \
--name "stack-app-prod" \
--resource-group rg-prod \
--template-file main.bicep \
--action-on-unmanage deleteResources \
--deny-settings-mode denyDeleteTři klíčové vlastnosti Deployment Stack
| Vlastnost | Co dělá | Default value |
|---|---|---|
| Managed resources tracking | Persistent list všech resources, které stack vytvořil | Auto-managed |
| actionOnUnmanage | Co dělat s resources odebranými ze šablony | detachResources (bezpečné) |
| denySettings | Vytvoří denyAssignment, který blokuje manuální změny | none |
actionOnUnmanage – tři volby a kdy je použít
# Pro dev/test – mažeme aktivně
--action-on-unmanage deleteResources
# Pro produkci – detach (manuální cleanup po review)
--action-on-unmanage detachResources
# Pro ephemeral environments – mazat všechno včetně RG
--action-on-unmanage deleteAllMůj defaultní pattern: dev má deleteResources, produkce má detachResources plus monitoring alert na detached resources, ephemeral environments mají deleteAll.
denySettings – ochrana proti ručním změnám
To nejvíc novátorské. Stack může vytvořit denyAssignment, který blokuje konkrétní operace na resources i pro vlastníka subskripce:
az stack group create \
--name "stack-prod" \
--resource-group rg-prod \
--template-file main.bicep \
--deny-settings-mode denyDelete \
--deny-settings-excluded-principals "<oncall-team-objectId>" \
--deny-settings-excluded-actions "Microsoft.Authorization/roleAssignments/write"Co tohle vynutí:
- Nikdo (ani Owner) nemůže přes Portal/CLI smazat resources spravované stackem
- Výjimka: oncall team může v incident response
- Výjimka: role assignments lze měnit i mimo stack (např. když Entra ID admin přiřazuje rolu)
Pro regulované workloady (banking, healthcare) je tohle game changer – nahrazuje resource locks s mnohem granulárnější kontrolou.
Reálný stack: aplikační deployment v Christie's
Vzor, který používáme pro každou aplikaci:
// main.bicep
targetScope = 'resourceGroup'
param appName string
param environment string
param location string = resourceGroup().location
// Storage pro app data
module storage 'br:cmnregistry.azurecr.io/bicep/cmn-storage:1.4.3' = {
name: 'storage-${appName}'
params: {
storageAccountName: 'st${appName}${environment}'
location: location
sku: 'Standard_LRS'
}
}
// Function App s VNet integration
module funcApp 'br:cmnregistry.azurecr.io/bicep/cmn-funcapp:2.1.0' = {
name: 'func-${appName}'
params: {
appName: 'func-${appName}-${environment}'
location: location
subnetId: subnetId
storageAccountId: storage.outputs.id
}
}
// Key Vault
module kv 'br:cmnregistry.azurecr.io/bicep/cmn-keyvault:1.2.0' = {
name: 'kv-${appName}'
params: {
name: 'kv-${appName}-${environment}'
location: location
enableRbacAuthorization: true
}
}
// Role assignment – Function MI -> KV Secrets User
module kvRole 'br:cmnregistry.azurecr.io/bicep/cmn-roleassignment:1.0.0' = {
name: 'role-${appName}-kv'
params: {
principalId: funcApp.outputs.identityPrincipalId
roleDefinitionId: '4633458b-17de-408a-b874-0445c86b69e6' // KV Secrets User
scope: kv.outputs.id
}
}Deploy přes stack:
az stack group create \
--name "stack-${APP_NAME}-${ENV}" \
--resource-group "rg-${APP_NAME}-${ENV}" \
--template-file main.bicep \
--parameters appName=${APP_NAME} environment=${ENV} \
--action-on-unmanage detachResources \
--deny-settings-mode denyDelete \
--deny-settings-excluded-principals "${ONCALL_TEAM_OID}"Pokud někdo později z main.bicep odebere module storage, příští az stack group create storage detachne (s logem). Operations team uvidí v az stack group show listu detached resources a může je manuálně smazat.
Migrace existujícího prostředí na stack: adoption
Pokud máte už deployed resources přes klasický deployment, nemusíte je recreate. Stack umí "adoption":
# Krok 1: dry-run, ověřte si, co se stane
az stack group validate \
--name "stack-app-prod" \
--resource-group rg-app-prod \
--template-file main.bicep
# Krok 2: vytvořte stack se stejnými parametry jako original deployment
az stack group create \
--name "stack-app-prod" \
--resource-group rg-app-prod \
--template-file main.bicep \
--parameters @prod.parameters.json \
--action-on-unmanage detachResources \
--deny-settings-mode none # poprvé bez deny – nejdřív ověřitPo prvním create Azure zaregistruje existující resources jako managed by stack. Při druhém update s --deny-settings-mode denyDelete přidáte deny assignments.
Pozor: adoption neignoruje drift. Pokud má existující Storage Account jiný SKU než šablona, stack udělá update na deklarovaný SKU. Vždy nejdřív what-if:
az stack group create \
--name "stack-app-prod" \
--resource-group rg-app-prod \
--template-file main.bicep \
--parameters @prod.parameters.json \
--what-if \
--action-on-unmanage detachResourcesCI/CD integrace: stack v GitHub Actions
# .github/workflows/deploy.yml
name: Deploy app stack
on:
push:
branches: [main]
paths: ['infra/**']
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Deploy stack
run: |
az stack group create \
--name "stack-${{ vars.APP_NAME }}-prod" \
--resource-group "rg-${{ vars.APP_NAME }}-prod" \
--template-file infra/main.bicep \
--parameters @infra/prod.parameters.json \
--action-on-unmanage detachResources \
--deny-settings-mode denyDelete \
--deny-settings-excluded-principals "${{ vars.ONCALL_OID }}" \
--yes--yes přeskočí interactive prompt o destruktivních změnách. V produkci: vždy mít předchozí krok s --what-if a manual approval gate v Environments.
Tři pasti, které stojí za zmínku
- DenyAssignments brání i schválené operace – pokud Bicep šablona obsahuje
roleAssignment, který existuje, deploy padne sRoleAssignmentExists. Musíte buď přidat principal do excluded list, nebo použít--deny-settings-excluded-actions - Stack name max 90 znaků – pro convention
stack-${appname}-${env}-${region}musíte hlídat. V Christie's jsme se na to spálili u dlouhých feature branch deploymentů detachResourcesbez monitoringu = orphan farm – pokud nemáte alert na detached resources, akumulují se. V Christie's máme weekly KQL query, který detected resources reportuje na Teams channel platform týmu
Cena: nula, ale watch out na quota
Deployment Stacks samy o sobě nic nestojí – jsou součástí ARM service. Ale: každý stack zabírá quota subscription deployments (default 800 per RG). Pro prostředí s 50+ aplikacemi v jedné RG (což byste neměli mít, ale...) se to může vyčerpat. Solution: jedna RG per app, ne jedna RG per environment.
Závěr
Deployment Stacks jsou v dubnu 2026 zralá technologie, která řeší tři dlouhodobé bolesti: orphan resources, drift z manuálních změn a destruktivní cleanup chyby. Cena migrace je nízká (adoption pattern), benefit vysoký (denyAssignments + automatic lifecycle).
V Christie's i Nespresso jsme všechny nové aplikační deploymenty od ledna 2026 dělá přes stacks. Migration legacy deploymentů běží rolling, aniž bychom recreate produkční resources.
Pokud zvažujete migration na Deployment Stacks ve vlastním prostředí, podívejte se na naše služby cloudové architektury nebo se ozvěte pro IaC review.
O autorovi

Martin Rylko
Senior Cloud Architect & DevOps Engineer
Více než 14 let v IT – od on-premises datacenter a Hyper-V clusteringu po cloudovou infrastrukturu v Microsoft Azure. Specializuji se na Landing Zones, IaC automatizaci, Kubernetes a bezpečnostní compliance.
Nejcastejsi dotazy
Co je Deployment Stack a v čem se liší od klasického ARM/Bicep deploymentu?▾
Kdy zvolit Deployment Stack místo klasického deploymentu?▾
Co znamená actionOnUnmanage a jak ho správně nastavit?▾
Lze migrovat existující resources do Deployment Stacku bez recreate?▾
Mohlo by vás zajímat
Bicep shared moduly: Skip-if-exists pattern místo --force
Jak v enterprise prostředí publikovat Bicep moduly do ACR bez rizika přepisu existujících verzí. Skip-if-exists pattern, PR validace a semver hygiena.
ČístBicep CI/CD: GitHub Actions pipeline pro Azure
Produkční deployment pipeline pro Bicep v GitHub Actions. What-if náhledy, schvalování prostředí, OIDC autentizace a rollback strategie.
ČístTerraform Azure moduly: Privátní registr a testování
Znovupoužitelné Terraform moduly pro Azure s publikací do privátního registru, automatizované testování Terratest a verzovaná spotřeba modulů v produkci.
Číst