Logo

Ansible: automation and configuration

Ansible: automation and configuration

Ansible is the most popular agentless configuration management tool: drives your servers over SSH with YAML playbooks. Idempotent, idempotent, idempotent. The perfect complement to Terraform.

Introduction

Ansible lets you:

  • Configure servers in YAML (playbooks)
  • Agentless: just SSH + Python on target
  • Idempotent: replay a playbook 100 times, same result
  • Modules for everything: apt, systemd, file, docker, k8s, postgres
  • Reusable roles (Ansible Galaxy)
  • Vault for secret encryption

Use cases: configure 50 identical servers, deploy an app, mass updates.

Prerequisites

  • Control host (workstation Linux / macOS, or VPS)
  • Targets: Linux VPS with SSH + Python 3
  • SSH keys configured to reach targets

Step 1: Installation

sudo apt update
sudo apt install -y ansible
ansible --version

Or via pip:

sudo apt install -y python3-pip
pip install ansible

Step 2: Inventory

hosts.ini:

[web]
web1 ansible_host=192.168.1.10
web2 ansible_host=192.168.1.11

[db]
db1 ansible_host=192.168.1.20

[all:vars]
ansible_user=admin
ansible_ssh_private_key_file=~/.ssh/id_ed25519
ansible_become=yes

Or YAML (inventory.yml):

all:
  vars:
    ansible_user: admin
    ansible_become: yes
  children:
    web:
      hosts:
        web1:
          ansible_host: 192.168.1.10
        web2:
          ansible_host: 192.168.1.11
    db:
      hosts:
        db1:
          ansible_host: 192.168.1.20

Step 3: Connectivity test (ad-hoc)

ansible -i hosts.ini all -m ping
web1 | SUCCESS => {"changed": false, "ping": "pong"}
web2 | SUCCESS => {"changed": false, "ping": "pong"}
db1 | SUCCESS => {"changed": false, "ping": "pong"}

Useful ad-hoc commands:

ansible -i hosts.ini web -m apt -a "name=nginx state=present" --become
ansible -i hosts.ini all -m shell -a "uptime"
ansible -i hosts.ini all -m setup

Step 4: First playbook

site.yml:

---
- name: Configure web servers
  hosts: web
  become: yes
  
  tasks:
    - name: Install packages
      apt:
        name:
          - nginx
          - htop
          - curl
        state: present
        update_cache: yes
    
    - name: Start nginx
      systemd:
        name: nginx
        state: started
        enabled: yes
    
    - name: Deploy index page
      copy:
        content: "Hello from {{ inventory_hostname }}"
        dest: /var/www/html/index.nginx-debian.html
        owner: www-data
        group: www-data
        mode: '0644'
      notify: Reload nginx
  
  handlers:
    - name: Reload nginx
      systemd:
        name: nginx
        state: reloaded

Run:

ansible-playbook -i hosts.ini site.yml

Step 5: Variables and templates

group_vars/web.yml:

nginx_port: 80
domain: mysite.com
admin_email: [email protected]

Template templates/nginx.conf.j2:

server {
    listen {{ nginx_port }};
    server_name {{ domain }};
    root /var/www/{{ domain }};
    
    location / {
        try_files $uri $uri/ =404;
    }
}

In playbook:

- name: Deploy nginx config
  template:
    src: templates/nginx.conf.j2
    dest: /etc/nginx/sites-available/{{ domain }}
  notify: Reload nginx

Step 6: Conditionals and loops

- name: Install packages per OS
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - mariadb-server
    - php-fpm
  when: ansible_os_family == "Debian"

- name: Install Apache if not Nginx
  apt:
    name: apache2
    state: present
  when: web_server == "apache"

Step 7: Roles

roles/
  webserver/
    tasks/
      main.yml
    handlers/
      main.yml
    templates/
      nginx.conf.j2
    defaults/
      main.yml
    files/
      logo.png
    meta/
      main.yml

roles/webserver/tasks/main.yml:

- name: Install nginx
  apt:
    name: nginx
    state: present

- name: Deploy config
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-available/default
  notify: Reload nginx

In site.yml:

- hosts: web
  become: yes
  roles:
    - webserver
    - common

Step 8: Ansible Vault (secrets)

ansible-vault create group_vars/all/vault.yml
mysql_root_password: "very-strong-secret"
api_key: "abc123xyz"
- name: Set mysql root password
  mysql_user:
    name: root
    password: "{{ mysql_root_password }}"

Run with vault:

ansible-playbook -i hosts.ini site.yml --ask-vault-pass

Or with password file:

echo "your-pass" > .vault_pass
chmod 600 .vault_pass
ansible-playbook -i hosts.ini site.yml --vault-password-file .vault_pass

Step 9: Ansible Galaxy

ansible-galaxy install geerlingguy.nginx
ansible-galaxy install geerlingguy.mysql
- hosts: web
  roles:
    - geerlingguy.nginx
    - geerlingguy.mysql

Browse: https://galaxy.ansible.com

Step 10: Dry-run and check mode

ansible-playbook -i hosts.ini site.yml --check
ansible-playbook -i hosts.ini site.yml --check --diff

Step 11: Tags

- name: Install nginx
  apt:
    name: nginx
    state: present
  tags: install

- name: Config nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/sites-enabled/default
  tags: config
ansible-playbook -i hosts.ini site.yml --tags config
ansible-playbook -i hosts.ini site.yml --skip-tags install

Step 12: Integration with Terraform

Typical workflow:

  1. Terraform provisions VPS and generates dynamic inventory
  2. Ansible configures provisioned VPS
terraform output -json | jq -r '...' > inventory.ini

Or use community.general.terraform_inventory.

Troubleshooting

"Failed to connect via SSH"

  • Network connectivity (ping, traceroute)
  • SSH key (ssh user@target manually)
  • Correct ansible_user
  • Python 3 installed on target

"Missing sudo password"

Add --ask-become-pass or set become_method: sudo in playbook.

Slow performance

ansible.cfg:

[ssh_connection]
pipelining = True

[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible-facts
fact_caching_timeout = 86400
forks = 50

"ERROR! Unexpected Exception"

YAML syntax (indentation, spaces):

ansible-playbook --syntax-check site.yml

Unresolved variables

Check precedence (cli > playbook > host_vars > group_vars > role defaults).

ansible -i hosts.ini web1 -m debug -a "var=hostvars[inventory_hostname]"

Useful commands

ansible all -i hosts.ini -m ping
ansible web -i hosts.ini -m apt -a "name=htop state=present" --become

ansible-playbook -i hosts.ini site.yml
ansible-playbook -i hosts.ini site.yml --limit web1
ansible-playbook -i hosts.ini site.yml --check --diff

ansible-playbook -i hosts.ini site.yml --tags config
ansible-playbook -i hosts.ini site.yml --skip-tags slow

ansible-vault create file.yml
ansible-vault edit file.yml
ansible-vault encrypt existing.yml
ansible-vault decrypt encrypted.yml

ansible-galaxy install role_name
ansible-galaxy collection install community.general

ansible web1 -i hosts.ini -m setup

Conclusion

Ansible gives you:

  • Agentless configuration management
  • Idempotence (safe replay)
  • Massive ecosystem (Galaxy)
  • Easy Terraform integration

Going further:

  • Use Ansible Tower / AWX for UI and CI/CD
  • Combine with Terraform for provisioning
  • For app deploys, consider ArgoCD (k8s) or Kamal (containers)

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members