Ansible Variable Precedence — The 22-Level Silent Override
A forgotten host_vars file overrode group_vars with zero warnings, breaking prod.
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
- ✓Basic programming fundamentals
- ✓A computer with internet access
- ✓Willingness to follow along with examples
- Ansible is agentless configuration management — it connects via SSH, pushes small modules, and cleans up after itself
- Three core components: Inventory (what servers), Modules (how to act), Playbooks (when to act)
- Idempotency means running the same playbook 100 times produces the same result as running it once
- Performance trade-off: agentless means zero maintenance on servers but higher control node load (forks control parallelism)
- Production trap: variable precedence has 22 levels — your dev environment works but prod breaks because host_vars silently overrides group_vars with no warning
- Biggest mistake: a host_vars file left over from a debugging session six months ago quietly overrides your group-level config in production — compiles fine, deploys fine, serves the wrong value
Managing 100 servers by logging into each one and typing commands is like calling 100 employees individually to give the same instruction. Ansible is like sending one company-wide email that everyone acts on simultaneously. You describe the desired state of your servers in plain English-like YAML, and Ansible connects over SSH and makes it happen — on all servers at once, with no software installed on them.
Think of it this way: if your server is a hotel room, Ansible is the housekeeping checklist pinned to the door. It doesn't live in the room. It walks in, checks what needs fixing, fixes only what's broken, and walks out. The room doesn't even know Ansible was there — it just ends up clean.
And unlike calling each employee individually, if you send the same company-wide email again tomorrow, nothing bad happens. Everyone already followed the instructions. They'll read the email, confirm nothing needs doing, and get back to work. That's idempotency — the property that makes Ansible safe to run on a schedule, in a CI pipeline, or in a panic at 2am.
Before configuration management tools, sysadmins maintained hundreds of servers by hand — logging in, running commands, hoping nothing went wrong. I lived this. In 2015, I managed a fleet of 80 web servers at a mid-size SaaS company, and every deploy night was a three-hour marathon of SSH sessions, copy-pasted commands, and prayer. One night, someone restarted the wrong database server. We lost four hours of customer data. That was the last straw.
Ansible was created by Michael DeHaan in 2012 and acquired by Red Hat in 2015 (now part of IBM). Today it runs infrastructure at NASA JPL, Capital One, and thousands of companies from Series A startups to Fortune 50 enterprises. Not because it's the most powerful automation tool, but because it's the simplest one that actually gets used.
What makes Ansible different from competitors like Chef and Puppet is that it is agentless. There is no daemon running on your managed servers, no SSL certificates to exchange, and no extra ports to open beyond standard SSH (or WinRM for Windows). Ansible runs from your control node, pushes small programs called Ansible Modules to the remote nodes, executes them, and then cleans up after itself.
One important nuance that comes up in almost every team adopting Ansible: Ansible and Terraform are not competitors — they solve different problems at different points in a server's life. Terraform creates infrastructure: it provisions the EC2 instance, creates the VPC, registers the DNS record. Ansible configures that infrastructure: it installs software, deploys application code, manages services, and corrects configuration drift on day 2, day 30, and day 300. Terraform's user_data and cloud-init can run a script at first boot, but they can't re-run idempotently when you need to update a config three months later. Ansible can. That's the real distinction — Terraform builds the house once, Ansible keeps it clean indefinitely.
In this guide, we'll break down Ansible's core architecture — inventories, playbooks, modules, and roles — cover ad-hoc commands for quick fleet operations, and build production-grade automation with real error handling, secret management, and reusable patterns. Every section includes the production detail that most tutorials skip.
How Ansible Variable Precedence Really Works
Ansible variable precedence is a 22-level hierarchy that determines which value wins when the same variable is defined in multiple places. At its core, it's a deterministic override chain: from lowest priority (command-line -e vars) to highest (role defaults). The mechanic is simple — the last definition in the chain wins — but the chain itself is long and easy to misread.
In practice, this means a variable set in group_vars/all (level 14) will be silently overridden by a host_vars entry (level 19), which in turn can be overridden by a --extra-vars flag (level 22). The hierarchy is fixed and cannot be modified. Most teams only use 5–7 levels, but the remaining 15 create invisible traps when variables collide across inventories, roles, playbooks, and includes.
You need this hierarchy to separate concerns: default values in roles, environment-specific overrides in inventory, and emergency overrides via CLI. Without understanding the full chain, you'll debug 'why is my variable wrong?' for hours — only to find a forgotten vars/main.yml in a nested role silently winning over your carefully set inventory variable.
group_vars/all is not the final value — it's just level 14 of 22. Any role, include, or CLI flag can override it without warning.group_vars/production but a nested role's vars/main.yml (level 20) silently overrode the database hostname, causing all production writes to hit a staging database.ansible-inventory --list to dump resolved variables before a run; never assume a variable's source is the one you set.ansible-inventory --list before trusting a playbook's behavior.Inventory, Playbooks, and Modules — The Three Core Concepts
Ansible's architecture relies on three primary building blocks. Get these right and everything else follows. Get any one of them wrong and you'll spend your time debugging instead of automating.
- The Inventory: A file (INI or YAML) that lists the servers you want to manage, organized into groups like [webservers] or [databases]. The inventory is your single source of truth about what exists. In production, you'll almost always use dynamic inventory — pulling host lists directly from AWS, GCP, or Azure APIs so your inventory stays accurate as servers are created and destroyed by autoscaling. Static inventories work for learning and small fixed fleets under 20 servers, but once you have autoscaling groups or spot instances, a static file becomes a liability. Stale IPs, terminated instances, missing new nodes — a static inventory in an elastic environment is a disaster on a timer.
- The Playbook: Your automation blueprint, written in YAML. A playbook maps groups of hosts to sequences of tasks and describes desired state rather than step-by-step instructions. This distinction matters operationally: if Nginx is already installed and running at the right version, Ansible confirms it and moves on. It doesn't reinstall. It doesn't restart unnecessarily. It checks and reports 'ok'.
- Modules: The tools in the toolbox. Instead of writing bash scripts, you use modules like apt, yum, service, copy, or template. These modules are idempotent — they check the current state of the server and only make changes when the server doesn't match your desired state. The shell and command modules are the notable exceptions. They run unconditionally every time, which is exactly why experienced Ansible engineers avoid them unless there is genuinely no dedicated module alternative.
For dynamic inventory specifically — here's what it looks like in practice. You create a plugin configuration file (aws_ec2.yml) that Ansible reads instead of a static hosts file. It queries the AWS EC2 API, groups instances by their tags, and returns a live host list. The inventory is never stale because it's rebuilt from the API on every run.
Your First Production Playbook — and the 22-Level Precedence Ladder
A playbook is a collection of plays. Each play targets a specific group from your inventory and executes a sequence of tasks in order, top to bottom. If a task fails on a specific host, Ansible stops executing for that host but continues for the others. To handle configuration changes — like restarting a web server only when a config file actually changes — Ansible uses Handlers: special tasks that only run when notified by another task that reported 'changed'.
The playbook below is a production pattern we actually use. Notice: update the package cache, install the binary, deploy a templated config, ensure the service is running. Every task is idempotent. Every task uses a dedicated module. No shell commands.
But here's what the Ansible documentation buries in a footnote that causes more production incidents than anything else: variable precedence has 22 levels, and Ansible enforces them silently. The most important levels to internalize — from highest to lowest priority:
- Extra vars (-e on the command line) — highest, overrides everything
- Task vars (set directly on a task)
- Block vars
- Role and include vars
- Set_facts and registered vars
- host_vars/hostname.yml — this is where the production incident in this article came from
- group_vars/groupname.yml
- group_vars/all.yml
- Playbook vars
- Role defaults (defaults/main.yml) — lowest, easily overridden by anything above
The rule that causes the most surprises: host_vars always overrides group_vars. Always. Without any warning. Without any log entry. If prod-web-01.yml exists in your host_vars directory, it wins over group_vars/all.yml, group_vars/webservers.yml, and everything you defined in your playbook's vars block — silently.
The diagnostic you need to run before every production deploy where variables are involved: ansible-inventory -i inventory.ini --host prod-web-01 --vars. This shows you the fully merged, fully resolved variable set that Ansible will actually use. Not what you think you set. Not what's in the playbook. The ground truth.
Ad-hoc Commands — Quick Fleet Operations Without a Playbook
Not everything needs a playbook. Sometimes you need to run a single command across your fleet right now — check disk space before a deploy, restart a hung service on 50 app servers, verify a kernel patch applied across the fleet, kill a runaway process that's consuming memory. That's what ad-hoc commands are for.
Ad-hoc commands are Ansible's underrated superpower for day-two operations. They're the reason senior SREs reach for Ansible instead of writing SSH for-loops. An SSH for-loop runs the command on every server sequentially and gives you raw unstructured output. Ansible ad-hoc runs in parallel across as many hosts as your forks setting allows, returns structured output per host, handles failures gracefully, and respects your inventory groups so you don't accidentally run something against the wrong environment.
Syntax: ansible <host-pattern> -i <inventory> -m <module> -a '<arguments>'
- -b or --become: run as root (sudo)
- -u or --user: specify the SSH username
- --limit 'web-01': restrict execution to a subset of the matched hosts — critical for safe fleet operations
- --check: dry run — show what would change without actually changing anything
- -f 50 or --forks 50: override the default parallelism for this single command
- -v, -vv, -vvv, -vvvv: increasing verbosity. -v shows task results. -vvv shows SSH connection details. -vvvv shows everything including the raw module arguments — use this when debugging SSH hangs.
In production I use ad-hoc commands daily. Checking disk space on 200 servers before a deploy: one-liner, 10 seconds, structured output. Restarting a hung worker process across 50 app servers: one-liner. Verifying that a security patch actually applied to every host in the fleet: one-liner. These replace what used to be 20-minute SSH marathons with copy-pasted commands and manually collated output.
Roles — Reusable Automation at Scale
Once your playbooks grow beyond 50 lines, you'll start copying tasks between files. That's when you need roles. A role is a self-contained unit of automation — tasks, handlers, templates, default variables, and static files — packaged in a standardized directory structure that Ansible knows how to load automatically. Roles are how Ansible scales from 'one playbook' to 'an entire infrastructure codebase that multiple teams can contribute to.'
The directory structure is Ansible's loading convention, not optional decoration. When you reference a role in a playbook, Ansible automatically loads tasks/main.yml, handlers/main.yml, defaults/main.yml, templates/, and files/ if they exist. The structure is the contract — deviate from it and things silently don't load.
Roles come from two sources: you write your own for application-specific automation, or you pull community roles from Ansible Galaxy (ansible-galaxy install geerlingguy.nginx). Galaxy has thousands of pre-built roles for common infrastructure software. For Nginx, Docker, PostgreSQL, certbot, Redis — a battle-tested community role saves hours and handles edge cases your first draft won't. For deploying your Java application, configuring your monitoring stack, or enforcing your company's specific security baseline — you write your own.
Critically, community roles must be version-pinned in a requirements.yml file. Not managed, not latest — a specific version tag. I've watched a Galaxy role change a default variable in a minor version update and restart PostgreSQL during a maintenance window without any warning. The role's changelog mentioned it. Nobody read the changelog because nobody expected a minor version to change default behavior. Pin the version. Test the upgrade in staging. Treat a Galaxy role update the same way you treat a library dependency upgrade — with the same caution and the same verification process.
Production Patterns — Error Handling, Vault, and Rolling Deploys
The playbook we built above works correctly for a single server in a controlled environment. Production is messier. Databases fail mid-migration. Network blips cause intermittent SSH timeouts. You need to deploy to 50 servers without taking all 50 offline simultaneously. And you absolutely cannot store database passwords in plain text YAML committed to Git — not because of policy, but because production credentials in version control is a breach waiting to happen.
Error Handling with block/rescue/always: Ansible has a try/catch equivalent. Wrap risky tasks in a block. If anything inside fails, the rescue section runs — rollback, alert, log. The always section runs regardless — cleanup, notifications. Without this pattern, a failed database migration leaves your server in a half-configured state with no automatic recovery and no notification that anything went wrong.
Rolling Deploys with serial: The serial keyword controls how many hosts Ansible processes simultaneously. serial: 3 means update 3 servers, verify they're healthy, then move to the next 3. Without serial, Ansible hits all hosts simultaneously — which is acceptable for config management but catastrophic for application deploys where you need zero downtime.
Ansible Vault for Secrets: Vault encrypts variables or entire files using AES256. Create an encrypted file with ansible-vault create group_vars/production/vault.yml, add your secrets, and commit the encrypted file to Git. Without the vault password, the file is gibberish — safe to store in version control. In CI/CD, pass the vault password via a file written from a CI secret: echo "$ANSIBLE_VAULT_PASSWORD" > /tmp/vault_pass, then ansible-playbook deploy.yml --vault-password-file /tmp/vault_pass. Never use --ask-vault-pass in CI — it expects interactive input and hangs silently.
For different environments, use different vault password files — one for staging, one for production. The vault file contents can be identical in structure but different in values (different database passwords per environment), while the passwords to decrypt them are stored separately in your CI secrets manager.
Key Features of Ansible — What Actually Matters in Production
Forget the marketing fluff. Here's what makes Ansible worth your time when you're firefighting at 3 AM.
Agentless. No daemons to install, no certificates to rotate, no agents to patch. Your managed nodes just need SSH or WinRM and Python. That's it. When a node goes belly-up, you don't debug a dead agent — you fix the node.
Idempotency isn't a feature, it's a contract. Ansible modules are built to declare state, not run commands. Run a playbook twice — the second run changes nothing if the system already matches your declaration. This isn't a nice-to-have; it's what stops you from crashing production with a forgotten restart.
Declarative YAML, not imperative scripts. You write what the end state looks — "Nginx should be installed and running on port 8080." Ansible figures out the how. This shifts your brain from "I need to write an if-else tower" to "I need to describe the target state." That's the difference between a script that rots and a playbook that survives.
Extensible via Python modules. Need to manage a proprietary API? Write a custom module. The framework is trivial — return a JSON dict with changed and msg. No special SDK to learn.
shell or command modules without creates/removes guards. Those are imperative escape hatches — treat them like surgery. Use them only when no module exists.Ansible Architecture — The Minimal Moving Parts You Must Understand
Ansible's architecture is brutally simple compared to Puppet or Chef. That's the point. Fewer moving parts means fewer failure modes.
Control Node. This is where you install Ansible. Your laptop. A bastion host. A CI runner. Ansible sends commands from here to managed nodes. Note: Windows cannot be a control node natively — use WSL or a Linux jump box.
Managed Nodes. The servers, containers, or network devices you control. They need SSH (Linux), WinRM (Windows), or a network API target. That's it. No agent, no daemon. You push commands to them, or they pull via ansible-pull if you're doing scale-out without a central server.
Inventory. A file listing your managed nodes, grouped logically. Static or dynamic — you can pull from AWS EC2, GCP, or a CMDB. An inventory can be a flat INI file or a YAML file with variables. Critical mistake: hardcoding IPs instead of using group variables.
Modules. The actual workhorses. Each module is a Python script that runs on the managed node, returns JSON, and exits. copy, file, service, template, uri, package — learn these cold. Everything else is syntactic sugar around these core primitives.
Playbooks. YAML files that orchestrate modules in order. They define which hosts, which tasks, what variables, and how to handle failures. A playbook without error handling is a fire drill waiting to happen.
Plugins. Extend Ansible's core — connection plugins, callback plugins, filter plugins. You'll rarely write one, but you'll use them daily: ansible.builtin.debug is a plugin. So is community.general.docker_container.
How Ansible Works — The SSH Handshake and Module Execution Path
Here's the cold, hard execution path when you run ansible-playbook deploy.yml:
- Parse the playbook. Ansible reads YAML, resolves variable precedence (remember the ladder?), compiles tasks into a list.
- Build the inventory. It resolves host patterns, applies group vars, and expands host ranges. This is where your
-llimit flag filters the host list. - SSH connection (default). Ansible opens an SSH connection to each managed node. It uses
controlpersistto reuse connections — that's why first-run is slow, subsequent runs are fast. For Windows, it uses WinRM viapywinrm. - Module transfer. Ansible serializes the module (a Python script) and its arguments into JSON. It
scps orsftps that module to the managed node, usually into/tmp/.ansible/.... Yes, it lands on disk temporarily. - Execute and collect. The control node runs the module script via SSH. The module executes, makes changes (e.g., writes a config file), and returns a JSON result dict:
{ "changed": true, "msg": "file created" }. - Cleanup. The module script is deleted from the managed node. Ansible stores the result in memory for use in later tasks (via
register: result). - Report. Ansible formats the results (with colors, if enabled), prints them to stdout, and writes them to log files if configured.
This happens per task, per host. That's why a 50-host fleet with 20 tasks takes 1000 SSH round trips. Mitigation? Use pipelining=True to reduce SSH overhead — cuts execution time by up to 40%.
MaxSessions on your control node. Default is 10. Bump it to 100 with ansible_ssh_common_args: '-o ControlMaster=auto -o ControlPersist=600s'. Otherwise Ansible will serialize connections and execute slower than a junior dev on Monday morning.Security and Compliance Enforcement — Automate Your Audits, Don't Just Check Boxes
Security isn't something you bolt on after deployment. It's either baked into your playbooks from the start or you're firefighting breaches. Compliance enforcement in Ansible means writing idempotent policies that fail closed, not open. The WHY: you need to prove to auditors that SELinux is enforcing, fail2ban is running, and SSH root login is disabled — without SSH'ing into every box manually.
The HOW: Use the assert module to gate your deployments. Check kernel parameters with sysctl, verify file permissions with stat, and enforce package versions with dpkg_selections. Combine this with failed_when conditions that halt execution if a security control is misconfigured. For compliance frameworks like CIS or PCI-DSS, write dedicated roles that map to control IDs. Then run these roles in check mode as part of your CI pipeline — your build should fail before a non-compliant node ever sees production.
Senior shortcut: Don't just check for the presence of a file. Verify its contents, owner, and permissions. Auditors love sha256sum comparisons. Give them receipts.
ignore_errors: true on security checks. If you silence compliance failures, you're hiding breaches. Let the playbook burn — you'll thank yourself during the post-mortem.Dynamic Inventories — Stop Hardcoding Server Lists in 2025
Hardcoding IP addresses in a static inventory file is a rookie move that scales to exactly zero production environments. The WHY: cloud instances auto-scale, containers get recycled, and on-prem servers get migrated. Your inventory must reflect reality, not a stale text file someone committed six months ago. Dynamic inventories query your infrastructure provider (AWS, GCP, vSphere) and return live groups and variables.
The HOW: Ansible ships with inventory scripts for AWS EC2, Azure, GCP, OpenStack, and VMware. You point the -i flag at a script or use the aws_ec2 plugin with a YAML config. The plugin tags become your group names. Want to target all production web servers with the tag Environment:prod and Role:web? Ansible builds that group automatically. No manual maintenance. If a new instance spins up with the right tags, it's in the next playbook run. Dead instances? Dropped automatically.
Senior shortcut: Use the keyed_groups plugin option to create nested groups from tags or custom variables. This lets you write targeted playbooks like rolling_update:frontend without touching inventory files.
ansible-inventory -i aws_ec2.yml --list before running any playbook. Catch missing tags or wrong filters when it costs nothing.Provisioning — Why Infrastructure Must Exist Before Automation Runs
Ansible is often used to configure running systems, but those systems must first exist. Provisioning is the act of creating infrastructure — VMs, containers, network interfaces, storage volumes — before any playbook touches them. Without provisioning, your automation is solving a problem on a machine that doesn't exist. Ansible provisions through cloud modules: amazon.aws.ec2_instance, azure.azcollection.azure_rm_virtualmachine, or community.general.digital_ocean. These modules send API calls to your cloud provider, wait for resource creation, and return facts like IP addresses. Do not hardcode IPs. Use add_host to dynamically insert new nodes into the in-memory inventory for downstream playbooks. Production pattern: separate provisioning into its own playbook or role, run it first, then target the fresh hosts with configuration. This keeps creation logic separate from configuration logic, making both auditable and reusable. Idempotency matters here: your provisioning playbook should detect existing resources and skip creation, not fail or duplicate.
Orchestration — Coordinating Multi-Node Workflows That Fail Gracefully
Orchestration is about sequencing and dependencies across multiple hosts, not just running the same command everywhere. When one service must start only after another database is ready, or when you need a rolling update across 50 web servers without dropping traffic, you need orchestration. Ansible orchestration uses serial, order, throttle, and wait_for. For example, a three-tier app: provision load balancer, then app servers, then databases — each stage waits for the previous to pass health checks. Use delegate_to to run tasks from one host that check another. Use run_once for idempotent setup tasks (e.g., creating database schemas) that must execute only once across a group. For rolling updates, set serial: 1 or serial: 20% and include wait_for after restarts to verify service health before proceeding to the next batch. This pattern prevents cascading failures. Orchestration fails safely when you design for retries: set retries: 5 with delay: 10 on critical health checks.
wait_for or uri module before proceeding to the next batch.Introduction
Ansible is a radically simple IT automation engine that eliminates manual toil and human error from infrastructure operations. Unlike configuration management tools that require agents installed on every node, Ansible operates over standard SSH—meaning your servers remain untouched until execution. This architecture makes Ansible uniquely suited for heterogeneous environments where installing a permanent daemon is impractical or prohibited by security policy. The core philosophy is 'mechanism, not magic': every operation is a straightforward YAML description of system state, not a cryptic DSL. For teams drowning in repetitive firewall updates, user account provisioning, or application deployments, Ansible offers a path to repeatability without complexity. Before evaluating playbooks or roles, understand that Ansible's primary value is reducing the cognitive load of fleet management. It transforms tribal knowledge into executable, version-controlled specifications. This article assumes you manage more than three servers—beyond that number, manual processes break. Ansible restores sanity by making automation a side effect of documentation.
When Not to Use Ansible
Ansible excels at configuration management, application deployment, and task automation—but it is not a universal hammer. Avoid using Ansible for real-time event-driven automation where sub-second latency matters; tools like SaltStack or event-driven frameworks are better suited. Similarly, Ansible is not a container orchestrator—Kubernetes handles pod lifecycle and scaling natively. For stateful services requiring continuous convergence (e.g., ensuring a process stays running indefinitely), Ansible's push model falls short compared to a daemon-based tool like Puppet or Chef. Lastly, Ansible's Python dependency on control nodes can be a constraint in minimal environments like embedded systems or restricted CI runners. The golden rule: if your task fits in a cron job or a single shell script, Ansible is overkill. If you are managing 100+ servers with versioned, auditable state, Ansible is the right tool. Choose purpose-built tools for purpose-built problems; Ansible fills the midrange sweet spot between shell scripts and full-blown Kubernetes.
The Variable Precedence Nightmare
- Variable precedence is not a suggestion — it is a hard 22-level ladder that Ansible enforces silently. Learn the top eight levels. host_vars overrides group_vars. Always. Without exception.
- ansible-inventory --host is your variable debug command. Run it against the specific failing host before touching the playbook. The resolved variable state is the ground truth — not what you think you set.
- Treat host_vars files as a code smell. Unless a host genuinely needs unique configuration that no other host in its group shares, keep variables at group level and delete host_vars files when the reason for them disappears.
- Your staging environment not mirroring production in inventory structure and variable sources is a disaster waiting to happen. The variable that breaks prod will always be the one that staging silently resolved differently.
ansible -i inventory.ini all -m ping -vvvssh -v -i ~/.ssh/your_key user@target_host echo connected| File | Command / Code | Purpose |
|---|---|---|
| precedence_demo.yml | - name: Demonstrate variable precedence | How Ansible Variable Precedence Really Works |
| io | [webservers] | Inventory, Playbooks, and Modules |
| io | - name: Deploy and Configure Nginx | Your First Production Playbook |
| io | ansible production -i inventory.ini -m ping | Ad-hoc Commands |
| io | - name: Install Nginx | Roles |
| io | - name: Deploy Application with Safety Rails | Production Patterns |
| idempotency_demo.yml | - name: Ensure Nginx is at the right state | Key Features of Ansible |
| minimal_architecture_inventory.yml | [webservers] | Ansible Architecture |
| pipelining_config.yml | [ssh_connection] | How Ansible Works |
| enforce-cis-benchmark.yml | - name: Enforce CIS Benchmark — SSH and File Permissions | Security and Compliance Enforcement |
| aws_ec2_inventory.yml | plugin: aws_ec2 | Dynamic Inventories |
| provision-aws-ec2.yml | - name: Provision EC2 instance and add to live inventory | Provisioning |
| rolling-update.yml | - name: Rolling update of web servers | Orchestration |
| inventory.yml | all: | Introduction |
| cli_example.sh | ansible all -i inventory.ini -m ansible.builtin.shell -a "uptime" | When Not to Use Ansible |
Key takeaways
Interview Questions on This Topic
Explain the agentless architecture of Ansible. How does it compare to agent-based tools like Puppet or Chef in terms of security footprint, operational overhead, and onboarding friction for new servers?
Frequently Asked Questions
20+ years shipping production infrastructure and CI/CD at scale. Lessons pulled from things that broke in production.
That's Ansible. Mark it forged?
14 min read · try the examples if you haven't