Modular Bicep templates, bootstrap patterns, container deployments, secret management via Key Vault, and a production-grade portfolio site — all built to CAF naming conventions with zero stored credentials.
Replaced ad-hoc CLI scripts with a modular Bicep stack orchestrated by
main.bicep. A bootstrap shell script handles pre-flight
work that Bicep cannot — resource group creation, Key Vault secret seeding,
and subnet delegation — then hands off to Bicep for all infrastructure
resources. Every deploy is idempotent; --what-if runs before
every apply.
using 'main.bicep'
param adminPassword = az.getSecret(
'343a8a7e-...',
'rg-az104-dev-wus3-01',
'kv-az104-dev-wus3-01',
'vm-admin-password'
)
param tags = {
env: 'dev'
owner: 'geoste'
managed-by: 'bicep'
}
.bicepparam resolves
secrets from Key Vault at deploy time. Credentials never appear in template files, parameter files, or
CLI args.require-tag-env and
require-tag-owner deny assignments on the RG. inherit-tag-* policies push
tags from RG to all child resources automatically. Manual tagging is inconsistent and breaks.
deployBastionHost bool parameter controls
whether the host deploys. Standard SKU costs ~$140/mo; delete between lab phases.guid() seeded
with static param values, not module outputs. Module outputs aren't known at deployment start.Azure resource names can be any valid length but Windows OS hostnames are hard-limited to 15
characters. vm-win-dev-wus3-01 is 18 chars and fails at provisioning. Pass
computerName as a separate explicit parameter — never rely on
take(vmName, 15) for multi-VM deployments as it produces duplicate hostnames.
Cannot reference other parameters within the same .bicepparam file.
param tags = { env: env } fails — use literals only.
Built a complete container delivery pipeline: images built and pushed to
Azure Container Registry, deployed via Bicep to an internal Container Apps
Environment on a delegated /23 subnet. Two image versions deployed
simultaneously with an 80/20 traffic split — demonstrating blue/green
rollout patterns without downtime. Scale-to-zero configured with
minReplicas: 0.
az containerapp ingress traffic set \
--name ca-dev-wus3-01 \
--resource-group rg-az104-dev-wus3-01 \
--revision-weight \
latest=80 \
ca-dev-wus3-01--v1=20
Container Apps Environment sits on snet-capp-dev-wus3-01
(10.0.8.0/23, delegated to Microsoft.App/environments).
Internal ingress only — no public endpoint. ACR admin access disabled;
all pulls use AcrPull role on the managed identity.
snet-capp-dev-wus3-01 must be delegated to Microsoft.App/environments
before the Container Apps Environment can deploy. Minimum subnet size /23 (512 addresses). Delegation
cannot be added after subnet creation if resources are attached.
Deployed Key Vault with RBAC authorization model, assigned system-managed identities to both VM and App Service, and wired App Service configuration to pull secrets via KV reference strings — no credentials stored anywhere in code or config. Diagnostic logs routed to Log Analytics; KQL queries used to audit every secret access event.
@Microsoft.KeyVault( VaultName=kv-az104-dev-wus3-01; SecretName=vm-admin-password )
AzureDiagnostics
| where ResourceType == "VAULTS"
| where OperationName == "SecretGet"
| project TimeGenerated,
CallerIPAddress,
OperationName,
ResultType
| order by TimeGenerated desc
--enable-rbac-authorization true. Access Policies are deprecated for new vaults. RBAC
provides per-secret granularity with full audit trail in Entra ID.
Key Vault Secrets Officer to write secrets; (2) ARM needs
--enabled-for-template-deployment true to resolve getSecret() in Bicep.
Neither is implied by the other.
--assign-identity "[system]"
passed during resource creation. Post-creation assignment introduces a timing window where RBAC
assignment may fail before the identity GUID propagates in Entra ID.@ and parentheses in KV
reference values cause PowerShell parsing failures even inside quotes. Always use Cloud Shell Bash for
az webapp config appsettings set with KV references.
Deleted secrets are retained for 7 days by default. Attempting to reuse a secret name during the
retention window fails. Purge explicitly:
az keyvault secret purge --vault-name kv-az104-dev-wus3-01 --name secret-name
The capstone project is this site. A production-grade Azure stack
deployed entirely via Bicep: Front Door with WAF policy, Static Web App
with GitHub Actions CI/CD, Key Vault, storage account for static assets,
and Log Analytics with Application Insights. Custom domain
ostebovik.net with managed TLS certificate — all provisioned
from a single az deployment group create command.
az deployment group create \ --resource-group rg-geoste-prod-wus3-01 \ --template-file main.bicep \ --parameters prod.bicepparam \ --what-if
main →
GitHub Action → deployed in under 60 seconds.skipGithubActionWorkflowGeneration: false requires Azure write access to the repo at
deploy time. If not granted, the workflow file is never auto-generated. Added manually to
.github/workflows/ with deployment token in repo secrets.
certificateType: 'ManagedCertificate' on the
Front Door custom domain. Free, auto-renewed — no certificate management overhead.stprodwus301 and stgeostwus301 were both taken across all Azure tenants
globally. Always include an owner-specific string. Final working name: stgeostewus301.
--what-if first to see exactly what will change
before committing.\r\n. Cloud Shell expects
\n only. Symptom: $'\r': command not found on every line. Fix:
sed -i 's/\r//' script.sh. Prevention: set "files.eol": "\n" globally in VS Code.
az resource tag --is-incremental.@ symbol and parentheses in Key Vault reference values cause parsing
failures in local PowerShell and Git Bash. Always use Azure Cloud Shell for any command passing KV reference
strings as flag values.dependsOn is correct, not a code smell.securityRules: [...] on the NSG resource deploy
atomically with it. Separate child rule resources deploy in parallel and may not be complete when the
compliance check runs. The deployBastionHost bool condition flag exists for cost control
(~$140/mo Standard SKU) — not to work around a deployment ordering problem.