I’ve tried them all: Puppet, Chef, SaltStack. They all work. But Ansible is the one I keep coming back to. Here’s why.

The Pitch

Ansible is a configuration management tool that lets you define your infrastructure as code. But unlike its competitors, it:

  • Requires no agents — just SSH
  • Uses YAML — readable by humans
  • Is idempotent — run it 100 times, same result
  • Starts simple — but scales to thousands of servers

Your First Playbook

Let’s install Nginx on a server. Create nginx.yml:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
---
- name: Install and configure Nginx
  hosts: webservers
  become: yes
  
  tasks:
    - name: Install Nginx
      apt:
        name: nginx
        state: present
        update_cache: yes
    
    - name: Start Nginx
      service:
        name: nginx
        state: started
        enabled: yes
    
    - name: Copy custom config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
      notify: Reload Nginx
  
  handlers:
    - name: Reload Nginx
      service:
        name: nginx
        state: reloaded

Run it:

1
ansible-playbook -i inventory.ini nginx.yml

That’s it. Nginx is installed, running, and configured on every server in your webservers group.

The Inventory

Ansible needs to know what servers to target. Create inventory.ini:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
[webservers]
web1.example.com
web2.example.com
192.168.1.50

[databases]
db1.example.com ansible_user=dbadmin

[production:children]
webservers
databases

[all:vars]
ansible_python_interpreter=/usr/bin/python3

Groups let you organize servers logically. Run a playbook against webservers, databases, or production (which includes both).

Variables and Templates

Real power comes from variables. Define them in group_vars/webservers.yml:

1
2
3
4
5
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
app_domain: myapp.example.com
ssl_enabled: true

Then use Jinja2 templates. Create templates/nginx.conf.j2:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
worker_processes {{ nginx_worker_processes }};

events {
    worker_connections {{ nginx_worker_connections }};
}

http {
    server {
        listen 80;
        server_name {{ app_domain }};
        
        {% if ssl_enabled %}
        return 301 https://$server_name$request_uri;
        {% else %}
        location / {
            proxy_pass http://localhost:8080;
        }
        {% endif %}
    }
}

Same playbook, different configs per environment. Magic.

Roles: Reusable Components

Once your playbooks grow, organize them into roles:

rolnegsithtdv/naaeeaxsnmfr/kdpassllu//ealmmrttaasesii/snnmm..anayyigimmninll.n.yxym.mlclonf.j2

Then your playbook becomes:

1
2
3
4
5
6
7
8
---
- name: Configure web servers
  hosts: webservers
  become: yes
  roles:
    - nginx
    - certbot
    - app-deploy

You can share roles via Ansible Galaxy, or keep them in your own git repo.

Real-World Patterns

Check Mode (Dry Run)

See what would change without changing anything:

1
ansible-playbook site.yml --check --diff

Limit to Specific Hosts

Test on one server before rolling out:

1
ansible-playbook site.yml --limit web1.example.com

Vault for Secrets

Encrypt sensitive data:

1
2
ansible-vault encrypt secrets.yml
ansible-playbook site.yml --ask-vault-pass

Dynamic Inventory

For cloud environments, generate inventory from AWS, GCP, etc.:

1
ansible-playbook -i aws_ec2.yml site.yml

Idempotence: Why It Matters

Every Ansible task describes the desired state, not a sequence of commands:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Good: Idempotent
- name: Ensure user exists
  user:
    name: deploy
    state: present
    groups: sudo

# Bad: Not idempotent
- name: Add user
  command: useradd deploy  # Fails if user exists!

Run idempotent playbooks repeatedly. First run makes changes, subsequent runs verify everything is correct. This makes Ansible perfect for:

  • Continuous enforcement — cron job that ensures config stays correct
  • Disaster recovery — rebuild servers from scratch
  • Onboarding — new servers get full config automatically

When Not to Use Ansible

Ansible isn’t always the answer:

  • Container orchestration — use Kubernetes
  • Immutable infrastructure — use Packer to build images
  • Application deployment — consider dedicated CD tools
  • Real-time config — Ansible is push-based, not reactive

But for traditional server management? It’s hard to beat.

Getting Started

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Install
pip install ansible

# Test connectivity
ansible all -i inventory.ini -m ping

# Run ad-hoc commands
ansible webservers -i inventory.ini -m shell -a "uptime"

# Run your first playbook
ansible-playbook -i inventory.ini site.yml

The Bottom Line

Ansible hits a sweet spot: powerful enough for complex infrastructure, simple enough to start using today. No agents to install, no master server to maintain, no new language to learn.

Write YAML. Describe your infrastructure. Let Ansible make it so.


Using Ansible in production? Have questions? Find me on Twitter.