Ansible cheatsheet.

Install

pipx install ansible
ansible --version

Inventory

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

[db]
db1.example.com ansible_user=postgres

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

YAML:

all:
  hosts:
    web1.example.com:
  children:
    web:
      hosts:
        web1.example.com:
        web2.example.com:
    db:
      hosts:
        db1.example.com:
          ansible_user: postgres
  vars:
    ansible_python_interpreter: /usr/bin/python3

Ad-hoc commands

ansible all -i inventory -m ping
ansible web -i inventory -a "uptime"
ansible web -m service -a "name=nginx state=restarted" -b
ansible web -m copy -a "src=local dest=/etc/foo owner=root mode=0644" -b

-b = become root.

Playbook

- name: Configure web servers
  hosts: web
  become: yes
  
  vars:
    nginx_version: "1.27"
  
  tasks:
    - name: Install nginx
      apt:
        name: "nginx={{ nginx_version }}*"
        state: present
        update_cache: yes
    
    - name: Copy config
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        owner: root
        mode: '0644'
      notify: reload nginx
    
    - name: Start nginx
      service:
        name: nginx
        state: started
        enabled: yes
  
  handlers:
    - name: reload nginx
      service: { name: nginx, state: reloaded }
ansible-playbook -i inventory site.yml
ansible-playbook -i inventory site.yml --check       # dry run
ansible-playbook -i inventory site.yml --diff        # show changes
ansible-playbook -i inventory site.yml --limit web1  # one host
ansible-playbook -i inventory site.yml --tags nginx
ansible-playbook -i inventory site.yml --ask-vault-pass

Common modules

- file: { path: /tmp/foo, state: directory, mode: '0755' }
- copy: { src: a, dest: /etc/a, owner: root, mode: '0644' }
- template: { src: a.j2, dest: /etc/a }
- lineinfile: { path: /etc/foo, line: "KEY=value", regexp: "^KEY=" }
- replace: { path: /etc/foo, regexp: 'old', replace: 'new' }
- blockinfile: { path: /etc/foo, block: "..." }

- service: { name: nginx, state: restarted, enabled: yes }
- systemd: { name: nginx, state: started, daemon_reload: yes }

- apt: { name: ["nginx", "curl"], state: present }
- dnf: { name: nginx, state: latest }
- yum: { name: nginx, state: present }

- user: { name: alice, groups: [sudo,docker], append: yes }
- group: { name: devs }

- git: { repo: url, dest: /opt/app, version: main }
- cron: { name: backup, minute: 0, hour: 2, job: /opt/backup.sh }
- mount: { path: /mnt, src: /dev/sdb1, fstype: ext4, state: mounted }
- ufw: { rule: allow, port: 80 }
- shell: "command"
- command: "command"          # no shell

Conditionals

- service: { name: nginx, state: started }
  when: ansible_os_family == "Debian"

- name: install packages
  apt: { name: "{{ item }}" }
  loop:
    - nginx
    - curl
  when: ansible_distribution_major_version | int >= 20

Variables

vars:
  port: 8000

vars_files:
  - vars/main.yml

group_vars/web.yml and host_vars/web1.example.com.yml auto-loaded.

Templates (Jinja2)

# templates/nginx.conf.j2
worker_processes {{ ansible_processor_vcpus }};
server {
    listen {{ port | default(80) }};
    server_name {{ inventory_hostname }};
}

Roles

roles/
└── nginx/
    ├── tasks/main.yml
    ├── handlers/main.yml
    ├── templates/
    ├── files/
    ├── vars/main.yml
    ├── defaults/main.yml
    └── meta/main.yml
- hosts: web
  roles:
    - nginx
    - { role: db, when: install_db }

ansible-galaxy

ansible-galaxy init roles/myrole
ansible-galaxy install geerlingguy.nginx
ansible-galaxy collection install community.general

Vault (secrets)

ansible-vault create secrets.yml
ansible-vault edit secrets.yml
ansible-vault view secrets.yml
ansible-vault encrypt vars/secrets.yml
ansible-vault decrypt vars/secrets.yml
ansible-playbook -i inv site.yml --ask-vault-pass
ansible-playbook -i inv site.yml --vault-password-file ~/.vault-pass

Facts

- debug: var=ansible_facts
- debug: msg="OS: {{ ansible_distribution }} {{ ansible_distribution_version }}"

ansible-inventory --host hostname --list shows facts.

Skip gathering:

gather_facts: no

Saves time when not needed.

Loops

- apt: { name: "{{ item }}" }
  loop: [nginx, curl, git]

- user: { name: "{{ item.name }}", groups: "{{ item.groups }}" }
  loop:
    - { name: alice, groups: [sudo] }
    - { name: bob, groups: [devs] }

Handlers

- name: change config
  template: { src: a.j2, dest: /etc/a }
  notify: restart service

handlers:
  - name: restart service
    service: { name: svc, state: restarted }

Runs once at end if notified.

Idempotency

Modules should be idempotent: same playbook twice → no change second time. Avoid shell / command unless necessary, or use creates: / removes:.

- command: "make build"
  args:
    creates: /opt/app/build

Dynamic inventory

ansible-inventory -i aws_ec2.yml --list

Plugin pulls from AWS, GCP, Azure, etc.

Common mistakes

  • shell everywhere — not idempotent.
  • No become: yes for system tasks.
  • Hardcoding hosts in playbook (use inventory).
  • Mixing command and shell semantics.
  • Forgetting notify → service not restarted.

Read this next

If you want my Ansible starter (web + db roles), it’s at rajpoot.dev .


Building something AI-, backend-, or data-heavy and want a second pair of eyes? I do consulting and freelance work — see my projects and ways to reach me at rajpoot.dev .