Ansible server configuration: idempotency and automation

Manual server configuration leads to discrepancies between environments and last-minute release-specific edits. We automate configuration with Ansible as a turnkey solution: unified roles and repeatable results on any number of machines. Server preparation time is cut multiple times over, and failures due to 'forgot to fix it on one server' disappear.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Manual server configuration: where it breaks and why Ansible is needed

Manual configuration rarely breaks at execution time — it breaks later. An engineer connects to a machine, installs packages, edits configuration files, restarts services, and until something fails, these steps are recorded nowhere except command history and memory.

While there are two servers, the scheme works; by the tenth, configuration discrepancies appear that no one planned.

The cost of configuration discrepancies shows up at the most inconvenient moment. A defect reproduces on one environment and not on another because someone once raised limits manually on the first.

A new machine cannot be built as a copy of a neighboring one — it is unclear which edits were intentional and which were left over from one-off debugging. Recovery after a failure turns into archaeology, and knowledge of how the environment is structured lives in one person.

Ansible removes this dependency: server configuration stops being a set of actions and becomes a description of the target state.

Hence idempotency — the description is applied repeatedly, and the result remains the same: without duplicating rules, repeated restarts, and edits like “we’ll fix it manually in production.”

Environment reproducibility stops being a promise: a new environment is brought up with the same description as production, and every edit goes through version control and leaves a trace — who, when, and why changed the configuration.

Below, we break down how this is covered in practice: how to move already configured machines into descriptions without downtime and which checks catch discrepancies before they reach production.

What managed configuration provides: measurable effects

Managed configuration moves server state from verbal agreements into a description that can be run again. Ansible roles describe the target state rather than the order of manual steps — the tool itself brings the node to it. Hence repeatability and effects that are visible in numbers, not in words.

Effects visible in numbers

  • Deployment time. A new server is brought up with a single role run, dozens of machines are configured in parallel rather than one by one manually: the difference between the first and tenth node is minutes.
  • Share of manual edits. Changes go only through a role and review in the repository, manual editing on a production server stops being a way to fix something, and environment drift is caught on the next run.
  • Cost of downtime. Recovery comes down to running roles on a clean machine: downtime is measured in a run, not in searching for someone who remembers what is configured where.
  • Eliminated class of failures. “It worked in test” stops being a cause: with the same input, both environments are described by one codebase and reach the same state.
  • Repeatability and rollback. Configuration lives in the repository: you can see what changed and when, and rollback is a return to the previous role state, not manual repair.

What we check at acceptance

  • A control run returns zero changes (changed=0) — the description matches the actual state, and there is no configuration “drift.”
  • The role runs on a clean machine without relying on the history of previous settings — this is the repeatability check.

Ansible configuration delivery methods: comparison of approaches

Configuration delivery is not only the syntax of roles in Ansible, but the way the state description reaches the machine and how quickly divergence from reality will be noticed.

The same set of roles is applied from a control node, locally on the machine itself, or not applied at all — the result is baked into a prepared image. Below are four options that are actually chosen between, and what limits each one.

Option When it fits Limitations of the approach
Run from a control node manually or from a build system Fleet of up to several hundred machines, network access and a single key storage point are available Permanent access to nodes and write permissions; if connectivity is lost, some machines remain on the old version, and fleet state diverges
Scheduled run from a control node Regular state reconciliation, fleet changes slowly, drift control is needed Delay between a role change and application; simultaneous runs across a large fleet hit parallelism and credential limits
Local run on the node itself Machines outside the common network, isolated segments, nodes behind NAT The role and dependencies must be delivered to the node separately; application reporting is collected per machine, with no consolidated picture
Prepared image with the role already baked in Fast deployment of new machines, autoscaling, immutable infrastructure Updates require rebuilding the image and restarting machines; on long-lived nodes, configuration still drifts from the original

In practice, the options are combined: the image provides a predictable base machine, scheduled runs keep it in the described state, and local runs cover segments without shared access.

The choice is determined by where the source of truth lies and who is responsible for eliminating drift — this is what we discuss at the start.

Implementation stages: from inventory to handover to operations

Configuring servers “from memory” produces configurations that no one can reproduce: the next machine differs from the previous one, and the discrepancy surfaces at the moment of failure.

That is why we implement Ansible in fixed stages — each closes its own class of errors and ends with an artifact that can be handed over to the team.

  1. Infrastructure audit — we record the actual state: which nodes exist, what is installed on them, which services depend on each other. The result is a list of discrepancies between documentation and reality.
  2. Node inventory — we move machines into an inventory with groups and environment-specific variables so that addressing is by roles, not host names.
  3. Role design — we break tasks down into roles with explicit dependencies and default values, without duplicating logic between environments.
  4. Configuration — we describe the target state declaratively: a task either brings the node to the required form or reports that it is already in that state, and a repeated run breaks nothing.
  5. Dry run — we run it on a copy of the environment with changes disabled and see which steps will fire and what exactly they will affect.
  6. Result verification — we check service operation, access, and logs after application, not just the task return code.
  7. Handover to operations — we hand over the configuration repository, documentation, and analysis of typical failures so the team can change servers on its own.

The customer ends up with not a one-off result but a reproducible process: a new machine is brought up with the same roles in minutes, and discrepancies between environments are visible before rollout.

Ansible roles, inventory, and policies: what remains with the team

Infrastructure handed over as “server access and password” lives only as long as the author is nearby. We provide a package that lets the environment be deployed from scratch on a clean machine and changed according to clear rules. Below is what remains with the team and what we demonstrate at acceptance.

  • Inventory and node description: static and dynamic sources, separation by environments, variables by groups and individual nodes. A new server is added with one entry and one run — without manual configuration from memory.
  • Ansible role sets by area of responsibility: web layer, databases, monitoring, backups. A repeated run does not change state, so running it in production is safe.
  • Idempotency check: the role is run twice on a test node and on a clean node — any discrepancy in the report is considered a defect.
  • Access policies: separate accounts for automation, least-privilege permissions, restrictions on manual commands, and a run log with the author and list of changed nodes.
  • Secret storage: passwords, keys, and certificates are encrypted in the repository or moved to an external vault, and they do not appear in plaintext or in run logs.
  • Change policy: branches, mandatory review of edits by a second engineer, a run in the test environment, rollback of the role to the previous state; the edit history serves as the change log.
  • Documentation: a map of roles and nodes, where variables are stored, how to onboard a new engineer, and what to do in case of failure.

Acceptance is done by running in a clean environment: if everything deploys from scratch and a repeated run changes nothing, the package is complete.

Case study: configuration time and class of failures before and after

Before: manual assembly and discrepancies between environments

Preparing the environment for a new service took two to three days of engineering time: the engineer went through a server checklist, installed additional packages, fixed directory permissions, and configured services.

The checklist lived in a separate document and diverged from reality — some steps were kept in a specific person’s head. The development environment and production ended up similar but not identical, and the discrepancy surfaced already in operations.

The cost showed up in recurring failures: the wrong directory owner after an update, a forgotten permission setting, a service started with local edits.

Each investigation took from half an hour to several hours, and the cause had to be searched for again — no record remained of what exactly had been changed manually.

After: state description and predictable run

Server state was moved into declarative descriptions on Ansible: base packages, users and keys, network parameters, services and their dependencies.

Each role is responsible for one layer and does not depend on run order, so a repeated run changes nothing on an already configured node — this is idempotency in practice, not in description.

Environment preparation dropped to 40–60 minutes, almost all of which is the run itself. The class of failures “we forgot a step” and “versions diverged between environments” stopped reproducing: configuration cannot be skipped, it can only be left undescribed.

The share of incidents due to configuration discrepancies fell from about a third to isolated cases, and node recovery became one run on a clean server instead of manual assembly from memory.

Technical doubts before moving to managed configuration

Objections to managed configuration are usually the same: the fleet is heterogeneous, it is scary to put secrets in a repository, and after the run someone will still log in manually. Below are answers to the four engineering doubts that come up most often.

Heterogeneous fleet — will the role break everything?

A role describes the desired state, not a set of commands, so Ansible manages different system families with the same logic, and differences are moved into variables and conditions.

Non-standard nodes get separate roles and explicit exceptions — they do not break the rest of the fleet and are visible in the run report.

Where should secrets be stored?

Secrets do not sit in the repository as plaintext: encrypted values are decrypted only at the moment of application, and the key is stored outside version control.

A run without the key simply will not start sensitive tasks — this is protection against accidental leakage, not an unnecessary barrier.

What if configuration is edited manually after a run?

The next run will return the file to the described state and show the discrepancy in the report — the edit will not be lost silently. If the intervention was justified, it is moved into the role and then reproduced automatically on all similar nodes.

Can we move gradually, without downtime?

We start with reading: we gather facts about the current state and run roles in check mode, without changes on servers. Then we move one group of nodes, verify the result, and only then expand coverage. Rollback is reapplying the previous version of the role, so the risk is localized to one group.

How Ansible differs from custom scripts and images

All three approaches solve one task — bringing a server to the required state. The difference shows up on the second and third run: a custom script executes commands again and either fails on something already created or silently breaks what is configured.

A prepared image fixes the build result, while a declarative description compares fact with target and changes only discrepancies.

Approach Repeated run Change verifiability Configuration maintenance
Custom scripts Executes everything again, does not track state Only by reading in full, edit history “in the head” Branching for each host, blind edits
Prepared images Reproduces the build, does not see drift after startup By comparing rebuilds Change = recreating the node and reinstalling the service
Roles and playbooks on Ansible Brings to the target state, repeated run gives zero changes Check-mode run with a list of discrepancies One set of roles for all environments, differences in variables

Change verifiability is the main dividing line. A script does not have it: what it will do on a specific host is visible only by reading it in full, along with the edit history. With an image, verification comes down to rebuilding and comparing. Declarative roles have a check-mode run — it prints a list of discrepancies and applies nothing.

Configuration maintenance is the second dividing line. Scripts become overgrown with branches for each host, images require recreation on any change.

One set of Ansible roles works in the development environment, in test, and in production: only the inventory and variable values differ, while behavior on a repeated run remains predictable.

Let’s discuss the task: where to start and what we need from you

The first conversation is about where automation will have an immediate effect and where order in access and configuration is needed first. We clarify which servers and environments are in the scope, who makes changes, and which manual operations take the most time.

After the conversation, we come back with an estimate and work plan: stages, the result at each, and a clear starting order.

To make the estimate not a guess, we need source data about the infrastructure:

  • a list of servers and environments with role assignments — application, database, cache, load balancer
  • the current access scheme: accounts, keys, who has rights to what
  • how changes reach servers now — manual steps and what is already described in Ansible playbooks
  • where configuration and secrets are stored and how they are delivered to hosts
  • environment constraints: network rules, maintenance windows, compatibility requirements
  • acceptance criteria: which checks must pass after rollout and who accepts the result

On our side, we state the scope of work, checkpoints, and rollback procedure. The next step is to describe the task in free form and send what you have about the infrastructure: we will come back with an estimate, a work plan, and open questions.