Azure Bicep: where manual infrastructure configuration fails
When resources are created manually through the web console, the environment state lives in the engineer's head, not in a file.
The same set of services in test and production environments almost always diverges: somewhere backup was not enabled, somewhere a firewall rule left open for debugging, somewhere a different redundancy level was chosen.
A few weeks later, no one can say exactly how the environments differ — which means the behavior after release cannot be predicted either.
The second failure point is implicit dependencies. The order in which resources are created lives in a document or in memory: network, subnet, interface, virtual machine.
Take a step in the wrong order, and the deployment fails midway, leaving a half-built environment that has to be dismantled manually.
The third problem is non-reproducible edits: someone changed a parameter directly in the console, the change is not recorded anywhere, and the next deployment either overwrites it or creates a duplicate resource.
In such a scheme, configuration errors are discovered only at the moment of failure, not during validation.
Bicep removes the root cause: infrastructure description becomes a file in version control that goes through review alongside application code.
The platform derives the resource creation order from dependencies itself, environment drift is visible line by line as a template diff, and repeated deployment into an empty subscription produces the same environment. Deployment reproducibility no longer depends on who exactly pressed the buttons and in what order.
Below, we break down how this maps to live infrastructure: from module structure to safely moving already running environments under code-based description.
What changes after moving to infrastructure descriptions
While environments are assembled manually through the portal, drift accumulates between tiers: a parameter is enabled in test but absent in production, a firewall rule was added in only one place, a certificate was replaced on one of three nodes.
This goes unnoticed until an incident, and every investigation starts with the question "who changed this and when" — the portal log usually has no answer.
Infrastructure descriptions in Bicep eliminate the category of manual edits itself.
The environment is assembled entirely from files, and a new site — a test tier, a stand for hypothesis validation — is brought up in tens of minutes instead of days: node sizes and instance counts differ by parameters, not the resource composition.
A unified environment standard rests on a shared base of descriptions and a separate parameter set for each environment.
The difference between tiers is visible as the difference between two small files and goes through the same review as application code: a change enters history, is validated before apply, and is rolled back by returning to the previous revision of the description.
Hence the drop in the share of rollbacks caused by human error — typical manual assembly mistakes simply stop reproducing.
Recovery time after a failure changes in nature: a lost environment is not rebuilt from memory but redeployed with the same description.
Downtime is limited to a single deployment run and does not stretch into approvals and manual operations — an identical copy is brought up alongside and traffic is switched to it.
Azure Bicep and alternative approaches: comparison
Infrastructure as code starts with choosing a language: it determines how changes are reviewed, how reusable ready-made modules are, and how much template maintenance costs when resources number in the hundreds.
Below are the options actually chosen between in Azure projects, with their limitations and support cost.
| Option | When it fits | Limitations and maintenance cost |
|---|---|---|
| ARM templates (JSON) | The environment is already described, and the stack cannot be changed | Verbose syntax; manual edits easily break dependencies |
| Azure Bicep | Infrastructure entirely or mostly in Azure, modules and readable review needed | Azure resources only; external services are described with other tools |
| Terraform | Multiple clouds and external providers in one description | Its own state and provider versions, updates to which change the plan |
| Pulumi | The team wants to describe infrastructure in a general-purpose programming language | Imperative constructs encourage hidden dependencies between resources |
What this gives in practice
Bicep remains declarative: the file compiles into an ARM template, and plan validation before apply shows which resources will change or be deleted.
Modules are moved to a shared registry and versioned, so environments are brought up with one set and different parameters — drift between stands stops accumulating.
The limitation of this approach is Azure lock-in: hybrid scenarios are assembled from two tools, and this is factored into the repository structure in advance.
Template maintenance is separate work: pipeline checks, change review, module updates as new platform capabilities are released.
How we implement infrastructure descriptions: work stages
Migrating infrastructure to descriptions in Azure Bicep starts not with code but with an analysis of what actually runs in the subscription.
Otherwise, templates reproduce a representation of the system rather than the system itself, and discrepancies surface during rollback or scaling.
- Resource inventory. We collect the actual composition of the subscription and resource groups, reconcile it with documentation, and separately flag everything configured manually: the resources themselves, access roles, connections between services.
- Dependency map. We record what depends on what and in what order it is created — networks, subnets, access rules, storage, applications. The map shows which connections we describe explicitly and which we pass via parameters.
- Infrastructure modules. We move similar blocks into reusable modules with versions and per-environment parameters, move secrets to a secure vault, and leave only references in templates.
- Change validation. We run every change through a preliminary calculation: it shows what will be created, changed, and deleted. We review deletions separately — that is where data and access are most often lost.
- Isolated environment. We apply the plan to a copy of the tier: we validate deployment from scratch, a repeat run with no changes, and rollback to the previous state.
- Handover. We provide a repository with modules, a dependency map, and the change release procedure; we run the first releases together with your team.
This order produces a reproducible result: any environment can be rebuilt from scratch and compared with what is described in the repository, rather than with the current portal state.
Configurations, diagrams, and policies you receive
The package is designed so that maintenance remains with your team: infrastructure does not turn into a "black box" but is handed over together with descriptions and policies.
Everything resides in your repository and goes through version control — edit history and authorship are preserved.
- Infrastructure description package — Azure Bicep modules by layer: network, storage, databases, applications. One codebase with different parameter sets deploys the development, pre-production, and production tiers.
- Environment map — a map of subscriptions, resource groups, and connections between services: what lives where, how environments differ, what data flows between them.
- Access rules — a matrix of roles and scopes: what an engineer changes directly, what requires approval, what is fully closed; separately, the permissions of deployment services.
- Change policy — the path from branch to production tier: who prepares the description, how validation works, what the preliminary change plan looks like, and how rollback is performed.
- Runbook instructions — deploy an environment from scratch, add a resource, update a component, reproduce a failure on a copy of the production tier.
- Drift protection — checks that prevent manual edits in the management panel from diverging from the description.
- External dependency list — domain names, certificates, secrets, network links: where they are stored and how they are rotated.
We hand over the package with a walkthrough on a call: your engineers go through each item with us and then maintain the infrastructure independently.
Case study: moving environments to infrastructure descriptions
Before moving to infrastructure descriptions, environments were assembled manually: the test tier was copied from production and parameters were adjusted "in place." Drift accumulated — stands intended to be identical differed in network settings, access rules, and dependency versions.
The cause of failures was sought in the application, even though the environment was breaking, and bringing up a new stand took 2-3 days of manual operations.
Three tiers — development, pre-production, and production — were migrated to a unified set of templates in Azure Bicep. The description is split into modules: network, storage, databases, application.
The tier is defined by a parameters file, so the difference between environments is visible in one file rather than smeared across hundreds of manual edits.
Metrics before and after
- Bringing up a new stand: used to take 2-3 days of manual operations with waiting for approvals — now 40-50 minutes from launch.
- Configuration-related incidents: about 5-6 per quarter — none new in six months; drift is detected by description validation before apply.
- Rollback to the previous state: used to be backup restore and manual reconciliation over several hours — now re-applying the previous description takes 10-15 minutes.
Configuration drift was addressed separately: a scheduled check compares the actual environment state with the description and shows discrepancies without deleting anything automatically.
Changes go through the same stages as code — diff review, run in an isolated tier, and only then production. A new environment is now not "roughly like production" but an exact copy of it by configuration.
How does a declarative description differ from manual scripts?
The difference shows when infrastructure needs to be changed rather than created from scratch. An imperative script is a sequence of commands: create a resource group, then a network, then nodes.
It describes the process, not the result, and therefore does not know that some resources already exist.
A declarative description defines the target state: we list resources, their connections, and parameters, and the tool takes on bringing the system to that state.
A description in Azure Bicep is validated at build time and does not create duplicates on a repeat run — the same file produces the same result.
| Criterion | Imperative scripts | Declarative description |
|---|---|---|
| Infrastructure state | determined by the number of past runs | defined in a file and reconciled during deployment |
| Repeat run | requires manual checks "does it already exist" | brings about the same state |
| Creation order | hardcoded in command order | derived from dependencies between resources |
| Change and review | line-by-line code diffs, logic reconstructed from memory | target and current state are compared |
Configuration readability determines what the next change costs. A file with declared resources is read top to bottom and reviewed line by line, while a script with hundreds of commands first has to be reconstructed in your head — especially if another engineer wrote it.
One approach does not replace the other. Scripts remain convenient for one-off actions: data migration, one-time checks, emergency work. Everything that must exist permanently is described declaratively — otherwise the infrastructure state is stored only in the run history.
Common technical concerns before starting a project
We address concerns before the start — they usually concern partial migration, moving between subscriptions, and support after handover. Below are answers to what most often delays the decision.
Part of the infrastructure is already deployed manually — will it have to be recreated?
No. Existing resources are described in code, and the state is brought to a unified form gradually: first an inventory of what already runs, then inclusion in the description without stopping services. Re-creation remains an extreme case — for resources that cannot otherwise be described.
How difficult is it to move configuration between subscriptions?
The description is parameterized: environment identifiers are moved into separate parameter sets, so changing a subscription is substituting another set and validating dependencies, not rewriting templates. Development, testing, and production environments get the same logic.
How can we make sure a change will not break anything?
Before apply, a preview is run: it shows which resources are created, changed, and deleted. Changes go through version control review, and apply proceeds in coordinated blocks — resource deletion is visible in advance and requires confirmation.
What happens after project handover?
We hand over the infrastructure description, dependency maps between resources, parameter sets, and instructions for typical operations. After that — support: incident analysis, module enhancements, training for your team.
Implementation timelines depend on scope: a pilot on one environment takes weeks, and expansion to the rest follows.
Let's discuss the task and come back with an estimate and plan
So that the estimate is based on facts rather than guesses, input data is enough to start — a detailed audit is not needed at this stage. We will collect it in one place: a list of subscriptions and environments, the current infrastructure state, access rules.
This is enough to see the scope of work and the order for moving resources into a code-based description.
- subscriptions, resource groups, and perimeter boundaries: where dev, stage, and prod are;
- how infrastructure currently runs — manual operations, scripts, export from the portal;
- roles and permissions: who changes environments, who approves releases;
- requirements for change approval and allowed maintenance windows;
- the set of services we will move into code first;
- constraints: security policy, mandatory tagging, audit requirements.
We hold the first conversation with the engineer who will do the work: we go through the diagram, contentious points, and what has already been tried.
As a result, we come with a phased implementation plan — what we do first, where the risks are, and how we roll back — and an estimate for each stage. Describe the task in free form: we will come back with an estimate and plan, and then you decide.







