Ansible

Dash App Icon
Dash for macOS
Instant access to all the cheat sheets, API docs and snippets you need!

Installation

Install Ansible via pip

pip install ansible

Install Ansible via apt (Ubuntu/Debian)

sudo apt update
sudo apt install ansible

Install Ansible via yum (RHEL/CentOS)

sudo yum install ansible

Verify installation

ansible --version

Basic Commands

Run ad-hoc command on all hosts

ansible all -m ping

Run ad-hoc command on specific group

ansible webservers -m ping

Run shell command on hosts

ansible all -m shell -a 'uptime'

Run playbook

ansible-playbook playbook.yml

Run playbook with inventory file

ansible-playbook -i inventory.ini playbook.yml

Run playbook with specific tags

ansible-playbook playbook.yml --tags "configuration,packages"

Skip specific tags

ansible-playbook playbook.yml --skip-tags "testing"

Check playbook syntax

ansible-playbook playbook.yml --syntax-check

Run playbook in dry-run mode

ansible-playbook playbook.yml --check

Run playbook with verbose output

ansible-playbook playbook.yml -v
ansible-playbook playbook.yml -vv
ansible-playbook playbook.yml -vvv
ansible-playbook playbook.yml -vvvv

Limit execution to specific hosts

ansible-playbook playbook.yml --limit "host1,host2"

Step through playbook interactively

ansible-playbook playbook.yml --step

Start at specific task

ansible-playbook playbook.yml --start-at-task="task name"

Configuration

ansible.cfg locations (in order of precedence)

  1. ANSIBLE_CONFIG environment variable
  2. ansible.cfg in current directory
  3. ~/.ansible.cfg in home directory
  4. /etc/ansible/ansible.cfg

Basic ansible.cfg structure

[defaults]
inventory = ./inventory
remote_user = ansible
host_key_checking = False
retry_files_enabled = False

[privilege_escalation]
become = True
become_method = sudo
become_user = root
become_ask_pass = False

Set custom config location

export ANSIBLE_CONFIG=/path/to/ansible.cfg

View current configuration

ansible-config dump

List all config options

ansible-config list

Inventory

INI format inventory

# inventory.ini
[webservers]
web1.example.com
web2.example.com

[databases]
db1.example.com
db2.example.com

[production:children]
webservers
databases

[webservers:vars]
http_port=80

YAML format inventory

# inventory.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
        web2.example.com:
      vars:
        http_port: 80
    databases:
      hosts:
        db1.example.com:
        db2.example.com:

List all hosts in inventory

ansible all --list-hosts

List hosts in specific group

ansible webservers --list-hosts

Get host variables

ansible-inventory --host hostname

View inventory graph

ansible-inventory --graph

Use dynamic inventory script

ansible-playbook -i ec2.py playbook.yml

Playbook Structure

Basic playbook structure

---
- name: Configure web servers
  hosts: webservers
  become: yes
  vars:
    http_port: 80

  tasks:
    - name: Install Apache
      apt:
        name: apache2
        state: present

    - name: Start Apache service
      service:
        name: apache2
        state: started
        enabled: yes

Multiple plays in one playbook

---
- name: Configure web servers
  hosts: webservers
  tasks:
    - name: Task 1
      ...

- name: Configure database servers
  hosts: databases
  tasks:
    - name: Task 2
      ...

Include tasks from file

tasks:
  - name: Include common tasks
    include_tasks: common-tasks.yml

Import tasks from file

tasks:
  - name: Import common tasks
    import_tasks: common-tasks.yml

Include variables from file

- name: Include variables
  include_vars: vars.yml

Common Modules

apt - Manage packages (Debian/Ubuntu)

- name: Install package
  apt:
    name: nginx
    state: present
    update_cache: yes

- name: Remove package
  apt:
    name: nginx
    state: absent

- name: Install multiple packages
  apt:
    name:
      - nginx
      - git
      - vim
    state: present

yum - Manage packages (RHEL/CentOS)

- name: Install package
  yum:
    name: httpd
    state: present

- name: Install specific version
  yum:
    name: httpd-2.4.6
    state: present

package - Generic package manager

- name: Install package (OS agnostic)
  package:
    name: htop
    state: present

service - Manage services

- name: Start and enable service
  service:
    name: nginx
    state: started
    enabled: yes

- name: Restart service
  service:
    name: nginx
    state: restarted

- name: Stop service
  service:
    name: nginx
    state: stopped

copy - Copy files to remote hosts

- name: Copy file
  copy:
    src: /local/path/file.txt
    dest: /remote/path/file.txt
    owner: root
    group: root
    mode: '0644'

- name: Copy with inline content
  copy:
    content: "Hello World"
    dest: /tmp/hello.txt

file - Manage files and directories

- name: Create directory
  file:
    path: /opt/myapp
    state: directory
    mode: '0755'

- name: Create symlink
  file:
    src: /path/to/source
    dest: /path/to/link
    state: link

- name: Delete file
  file:
    path: /tmp/file.txt
    state: absent

- name: Touch file
  file:
    path: /tmp/file.txt
    state: touch

template - Template files using Jinja2

- name: Template a configuration file
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'
  notify: restart nginx

lineinfile - Manage lines in files

- name: Add line to file
  lineinfile:
    path: /etc/hosts
    line: '192.168.1.100 myserver.example.com'

- name: Replace or add line
  lineinfile:
    path: /etc/ssh/sshd_config
    regexp: '^PermitRootLogin'
    line: 'PermitRootLogin no'

user - Manage user accounts

- name: Create user
  user:
    name: johndoe
    state: present
    shell: /bin/bash
    groups: sudo
    append: yes

- name: Remove user
  user:
    name: johndoe
    state: absent
    remove: yes

group - Manage groups

- name: Create group
  group:
    name: developers
    state: present

git - Manage git repositories

- name: Clone repository
  git:
    repo: https://github.com/user/repo.git
    dest: /opt/myapp
    version: main

- name: Pull latest changes
  git:
    repo: https://github.com/user/repo.git
    dest: /opt/myapp
    update: yes

shell - Execute shell commands

- name: Run shell command
  shell: echo "Hello" > /tmp/hello.txt
  args:
    creates: /tmp/hello.txt

command - Execute commands

- name: Run command
  command: ls -la /tmp
  args:
    chdir: /home/user

Note: command module does not support piping, redirection, or shell variables. Use shell module for those.

debug - Print debug messages

- name: Print variable
  debug:
    var: ansible_hostname

- name: Print message
  debug:
    msg: "The hostname is {{ ansible_hostname }}"

assert - Assertions

- name: Assert condition
  assert:
    that:
      - ansible_os_family == "Debian"
      - ansible_distribution_version >= "20.04"
    fail_msg: "This playbook requires Ubuntu 20.04 or later"

uri - Interact with HTTP/HTTPS

- name: Make HTTP request
  uri:
    url: https://api.example.com/health
    method: GET
    status_code: 200

wait_for - Wait for a condition

- name: Wait for port to be available
  wait_for:
    port: 80
    delay: 10
    timeout: 300

Variables and Facts

Define variables in playbook

vars:
  http_port: 80
  app_name: myapp

Use variables in tasks

- name: Install {{ app_name }}
  apt:
    name: "{{ app_name }}"
    state: present

Gather facts from hosts

- name: Gather facts
  hosts: all
  gather_facts: yes

Common facts: - ansible_hostname - ansible_os_family - ansible_distribution - ansible_distribution_version - ansible_architecture - ansible_default_ipv4.address

Disable fact gathering

- name: Play without facts
  hosts: all
  gather_facts: no

Set fact dynamically

- name: Set fact
  set_fact:
    my_var: "{{ ansible_hostname }}_backup"

Register task output as variable

- name: Get disk usage
  shell: df -h /
  register: disk_usage

- name: Print disk usage
  debug:
    var: disk_usage.stdout

Variable precedence (low to high)

  1. Role defaults
  2. Inventory variables
  3. Playbook variables
  4. Host facts
  5. Registered variables
  6. Set facts
  7. Role and include variables
  8. Block variables
  9. Task variables
  10. Extra variables (command line -e)

Pass extra variables via command line

ansible-playbook playbook.yml -e "http_port=8080"
ansible-playbook playbook.yml -e "@vars.yml"

Conditionals and Loops

Conditional execution with when

- name: Install Apache on Debian
  apt:
    name: apache2
    state: present
  when: ansible_os_family == "Debian"

- name: Install Apache on RedHat
  yum:
    name: httpd
    state: present
  when: ansible_os_family == "RedHat"

Multiple conditions

- name: Task with AND conditions
  debug:
    msg: "Condition met"
  when:
    - ansible_os_family == "Debian"
    - ansible_distribution_version >= "20.04"

- name: Task with OR conditions
  debug:
    msg: "Condition met"
  when: ansible_os_family == "Debian" or ansible_os_family == "RedHat"

Loop over list

- name: Install packages
  apt:
    name: "{{ item }}"
    state: present
  loop:
    - nginx
    - git
    - vim

Loop over dictionary

- name: Create users
  user:
    name: "{{ item.key }}"
    state: present
    groups: "{{ item.value.groups }}"
  loop: "{{ users | dict2items }}"
  vars:
    users:
      alice:
        groups: sudo
      bob:
        groups: developers

Loop with index

- name: Print items with index
  debug:
    msg: "Item {{ item.0 }}: {{ item.1 }}"
  loop: "{{ packages | zip(range(packages | length)) | list }}"

Loop until condition is met

- name: Retry until success
  shell: /usr/bin/check-service.sh
  register: result
  until: result.rc == 0
  retries: 5
  delay: 10

Handlers

Define and use handlers

tasks:
  - name: Update nginx config
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx

handlers:
  - name: restart nginx
    service:
      name: nginx
      state: restarted

Multiple handlers

tasks:
  - name: Update config
    template:
      src: config.j2
      dest: /etc/app/config
    notify:
      - restart app
      - clear cache

handlers:
  - name: restart app
    service:
      name: myapp
      state: restarted

  - name: clear cache
    file:
      path: /var/cache/myapp
      state: absent

Force handler execution

- name: Force handlers to run now
  meta: flush_handlers

Roles

Role directory structure

roles/
  common/
    tasks/
      main.yml
    handlers/
      main.yml
    templates/
      config.j2
    files/
      script.sh
    vars/
      main.yml
    defaults/
      main.yml
    meta/
      main.yml

Use role in playbook

- name: Configure web servers
  hosts: webservers
  roles:
    - common
    - nginx
    - php

Use role with parameters

- name: Configure web servers
  hosts: webservers
  roles:
    - role: nginx
      vars:
        http_port: 8080
        server_name: example.com

Install role from Ansible Galaxy

ansible-galaxy install geerlingguy.nginx

List installed roles

ansible-galaxy list

Create new role structure

ansible-galaxy init myrole

Install roles from requirements file

# requirements.yml
- src: geerlingguy.nginx
- src: geerlingguy.php

# Install
ansible-galaxy install -r requirements.yml

Ansible Vault

Create encrypted file

ansible-vault create secrets.yml

Encrypt existing file

ansible-vault encrypt vars.yml

Edit encrypted file

ansible-vault edit secrets.yml

Decrypt file

ansible-vault decrypt secrets.yml

View encrypted file

ansible-vault view secrets.yml

Rekey encrypted file

ansible-vault rekey secrets.yml

Run playbook with vault password

ansible-playbook playbook.yml --ask-vault-pass

Use vault password file

ansible-playbook playbook.yml --vault-password-file ~/.vault_pass

Encrypt string inline

ansible-vault encrypt_string 'secretpassword' --name 'db_password'

Output can be used directly in playbooks: yaml db_password: !vault | $ANSIBLE_VAULT;1.1;AES256 ...

Tags

Define tags on tasks

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

- name: Configure nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  tags:
    - configuration
    - nginx

Tag entire play or role

- name: Configure web servers
  hosts: webservers
  tags: webserver
  roles:
    - role: nginx
      tags: nginx

Run only tagged tasks

ansible-playbook playbook.yml --tags "configuration"
ansible-playbook playbook.yml --tags "nginx,php"

Skip tagged tasks

ansible-playbook playbook.yml --skip-tags "testing"

List all tags in playbook

ansible-playbook playbook.yml --list-tags

Special tags

  • always - Always run, even with --tags
  • never - Never run unless explicitly specified
  • tagged - Run all tagged tasks
  • untagged - Run all untagged tasks
  • all - Run everything (default)
- name: Always run this
  debug:
    msg: "This always runs"
  tags: always

Best Practices

Use descriptive names

# Good
- name: Install and configure nginx web server
  ...

# Bad
- name: Install stuff
  ...

Make tasks idempotent

Tasks should be safe to run multiple times without changing the result beyond the initial run.

# Idempotent
- name: Ensure nginx is installed
  apt:
    name: nginx
    state: present

# Not idempotent
- name: Append to file
  shell: echo "line" >> /etc/config

Use version control

  • Store playbooks, roles, and inventory in Git
  • Use meaningful commit messages
  • Review changes before applying
  • Tag releases

Organize with roles

  • Break complex playbooks into reusable roles
  • Keep roles focused on single responsibility
  • Use role dependencies when needed
  • Share common roles across projects

Test before production

# Syntax check
ansible-playbook playbook.yml --syntax-check

# Dry run
ansible-playbook playbook.yml --check

# Run on test hosts first
ansible-playbook playbook.yml --limit "test-servers"

Use become appropriately

# On entire play
- name: System configuration
  hosts: all
  become: yes
  tasks: ...

# On specific task
- name: Install package
  apt:
    name: nginx
  become: yes

Document your playbooks

  • Add comments explaining complex logic
  • Document required variables
  • Include README for roles
  • Provide example inventory files

Troubleshooting

Check connectivity

ansible all -m ping

Increase verbosity

ansible-playbook playbook.yml -v     # Level 1
ansible-playbook playbook.yml -vv    # Level 2
ansible-playbook playbook.yml -vvv   # Level 3
ansible-playbook playbook.yml -vvvv  # Level 4 (connection debug)

Debug variable values

- name: Show all variables
  debug:
    var: hostvars[inventory_hostname]

- name: Show specific variable
  debug:
    var: ansible_os_family

List all facts

ansible hostname -m setup

Test with single host

ansible-playbook playbook.yml --limit "hostname"

Enable callback plugins for better output

# ansible.cfg
[defaults]
stdout_callback = yaml
# or
stdout_callback = debug

Notes