Skip to content
ansible-automation logo

Ansible Automation

ansible-automation

Configuration management with Ansible. Use when automating server setup, application deployment, orchestrating multi-server tasks, or managing infrastructure configuration.

SKILL.md

Full skill instructions

Ansible Automation

Comprehensive guide for automating infrastructure configuration and application deployment with Ansible.

When to Use

  • Server provisioning and configuration
  • Application deployment automation
  • Multi-server orchestration
  • Configuration drift remediation
  • Secret management with Ansible Vault
  • Creating reusable automation roles

Core Concepts

Why Ansible

FeatureBenefit
AgentlessNo software to install on managed nodes
SSH-basedUses existing SSH infrastructure
IdempotentSafe to run multiple times
YAML syntaxHuman-readable, easy to learn
Extensible3000+ modules available

Project Structure

Standard Layout:

ansible/
├── ansible.cfg              # Configuration
├── inventory/
│   ├── production/
│   │   ├── hosts.yml        # Production inventory
│   │   └── group_vars/
│   │       ├── all.yml      # Variables for all hosts
│   │       └── webservers.yml
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
│
├── playbooks/
│   ├── site.yml             # Main playbook
│   ├── webservers.yml
│   └── databases.yml
│
├── roles/
│   ├── common/              # Base configuration
│   ├── nginx/
│   ├── postgresql/
│   └── app/
│
├── group_vars/
│   └── all/
│       ├── vars.yml
│       └── vault.yml        # Encrypted secrets
│
└── requirements.yml         # External roles

Inventory

YAML Inventory (Recommended):

# inventory/​production/​hosts.yml
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_host: 10.0.1.10
          nginx_worker_processes: 4
        web2.example.com:
          ansible_host: 10.0.1.11
          nginx_worker_processes: 4
      vars:
        ansible_user: deploy
        ansible_python_interpreter: /​usr/​bin/​python3

    databases:
      hosts:
        db1.example.com:
          ansible_host: 10.0.2.10
          postgresql_max_connections: 200
        db2.example.com:
          ansible_host: 10.0.2.11
          postgresql_max_connections: 200
      vars:
        ansible_user: postgres_admin

    loadbalancers:
      hosts:
        lb1.example.com:
          ansible_host: 10.0.0.10

  vars:
    ansible_ssh_private_key_file: ~/​.ssh/​ansible_key
    ntp_servers:
      - 0.pool.ntp.org
      - 1.pool.ntp.org

Dynamic Inventory (AWS Example):

# inventory/​aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
  - us-east-1
  - us-west-2

filters:
  tag:Environment: production
  instance-state-name: running

keyed_groups:
  - key: tags.Role
    prefix: role
  - key: placement.availability_zone
    prefix: az

hostnames:
  - private-ip-address

compose:
  ansible_host: private_ip_address

Playbooks

Basic Playbook:

# playbooks/​webservers.yml
---
- name: Configure web servers
  hosts: webservers
  become: true
  gather_facts: true

  vars:
    app_name: myapp
    app_port: 8080

  pre_tasks:
    - name: Update apt cache
      ansible.builtin.apt:
        update_cache: true
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

  roles:
    - common
    - nginx
    - app

  tasks:
    - name: Ensure app is running
      ansible.builtin.systemd:
        name: "{{ app_name }}"
        state: started
        enabled: true

  handlers:
    - name: Reload nginx
      ansible.builtin.systemd:
        name: nginx
        state: reloaded

  post_tasks:
    - name: Verify application health
      ansible.builtin.uri:
        url: "http://localhost:{{ app_port }}/​health"
        status_code: 200
      register: health_check
      retries: 5
      delay: 10
      until: health_check.status == 200

Multi-Play Playbook:

# playbooks/​site.yml
---
- name: Apply common configuration
  hosts: all
  become: true
  roles:
    - common
    - security-baseline

- name: Configure load balancers
  hosts: loadbalancers
  become: true
  roles:
    - haproxy

- name: Configure web servers
  hosts: webservers
  become: true
  roles:
    - nginx
    - app

- name: Configure databases
  hosts: databases
  become: true
  roles:
    - postgresql

Roles

Role Structure:

roles/​nginx/
├── defaults/
│   └── main.yml          # Default variables (lowest priority)
├── vars/
│   └── main.yml          # Role variables (higher priority)
├── tasks/
│   └── main.yml          # Main task list
├── handlers/
│   └── main.yml          # Handlers
├── templates/
│   └── nginx.conf.j2     # Jinja2 templates
├── files/
│   └── ssl/              # Static files
├── meta/
│   └── main.yml          # Role metadata & dependencies
└── README.md

Role Tasks:

# roles/​nginx/​tasks/​main.yml
---
- name: Install nginx
  ansible.builtin.apt:
    name: nginx
    state: present
  notify: Start nginx

- name: Create nginx config directories
  ansible.builtin.file:
    path: "{{ item }}"
    state: directory
    owner: root
    group: root
    mode: '0755'
  loop:
    - /​etc/​nginx/​sites-available
    - /​etc/​nginx/​sites-enabled
    - /​etc/​nginx/​conf.d

- name: Deploy nginx configuration
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /​etc/​nginx/​nginx.conf
    owner: root
    group: root
    mode: '0644'
    validate: nginx -t -c %s
  notify: Reload nginx

- name: Deploy site configuration
  ansible.builtin.template:
    src: site.conf.j2
    dest: "/​etc/​nginx/​sites-available/​{{ nginx_site_name }}.conf"
    owner: root
    group: root
    mode: '0644'
  notify: Reload nginx

- name: Enable site
  ansible.builtin.file:
    src: "/​etc/​nginx/​sites-available/​{{ nginx_site_name }}.conf"
    dest: "/​etc/​nginx/​sites-enabled/​{{ nginx_site_name }}.conf"
    state: link
  notify: Reload nginx

- name: Remove default site
  ansible.builtin.file:
    path: /​etc/​nginx/​sites-enabled/​default
    state: absent
  notify: Reload nginx

Role Handlers:

# roles/​nginx/​handlers/​main.yml
---
- name: Start nginx
  ansible.builtin.systemd:
    name: nginx
    state: started
    enabled: true

- name: Reload nginx
  ansible.builtin.systemd:
    name: nginx
    state: reloaded

- name: Restart nginx
  ansible.builtin.systemd:
    name: nginx
    state: restarted

Role Defaults:

# roles/​nginx/​defaults/​main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_site_name: default
nginx_server_name: localhost
nginx_root: /​var/​www/​html
nginx_index: index.html

nginx_ssl_enabled: false
nginx_ssl_certificate: ""
nginx_ssl_certificate_key: ""

nginx_proxy_pass: ""
nginx_proxy_enabled: false

Role Meta (Dependencies):

# roles/​nginx/​meta/​main.yml
---
galaxy_info:
  author: Your Name
  description: Nginx web server role
  license: MIT
  min_ansible_version: "2.14"
  platforms:
    - name: Ubuntu
      versions:
        - focal
        - jammy
    - name: Debian
      versions:
        - bullseye
        - bookworm

dependencies:
  - role: common
  - role: ssl-certificates
    when: nginx_ssl_enabled

Templates (Jinja2)

Nginx Config Template:

# roles/​nginx/​templates/​nginx.conf.j2
user www-data;
worker_processes {{ nginx_worker_processes }};
pid /​run/​nginx.pid;

events {
    worker_connections {{ nginx_worker_connections }};
    multi_accept on;
    use epoll;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout {{ nginx_keepalive_timeout }};
    types_hash_max_size 2048;
    server_tokens off;

    include /​etc/​nginx/​mime.types;
    default_type application/​octet-stream;

    # Logging
    access_log /​var/​log/​nginx/​access.log;
    error_log /​var/​log/​nginx/​error.log;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/​plain text/​css text/​xml application/​json application/​javascript;

    # Include site configs
    include /​etc/​nginx/​conf.d/​*.conf;
    include /​etc/​nginx/​sites-enabled/​*;
}

Site Config Template:

# roles/​nginx/​templates/​site.conf.j2
{% if nginx_ssl_enabled %}
server {
    listen 80;
    server_name {{ nginx_server_name }};
    return 301 https://$server_name$request_uri;
}
{% endif %}

server {
{% if nginx_ssl_enabled %}
    listen 443 ssl http2;
    ssl_certificate {{ nginx_ssl_certificate }};
    ssl_certificate_key {{ nginx_ssl_certificate_key }};
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
{% else %}
    listen 80;
{% endif %}

    server_name {{ nginx_server_name }};
    root {{ nginx_root }};
    index {{ nginx_index }};

{% if nginx_proxy_enabled %}
    location / {
        proxy_pass {{ nginx_proxy_pass }};
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }
{% else %}
    location / {
        try_files $uri $uri/ =404;
    }
{% endif %}

    # Health check endpoint
    location /​health {
        access_log off;
        return 200 "healthy\n";
        add_header Content-Type text/​plain;
    }
}

Variables & Secrets

Variable Precedence (lowest to highest):

  1. Role defaults (roles/​x/​defaults/​main.yml)
  2. Inventory group_vars/​all
  3. Inventory group_vars/​group
  4. Inventory host_vars/​host
  5. Playbook vars
  6. Role vars (roles/​x/​vars/​main.yml)
  7. Block vars
  8. Task vars
  9. Extra vars (-e)

Ansible Vault (Secrets Management):

# Create encrypted file
ansible-vault create group_vars/​all/​vault.yml

# Edit encrypted file
ansible-vault edit group_vars/​all/​vault.yml

# Encrypt existing file
ansible-vault encrypt secrets.yml

# Decrypt file
ansible-vault decrypt secrets.yml

# View encrypted file
ansible-vault view group_vars/​all/​vault.yml

# Re-key (change password)
ansible-vault rekey group_vars/​all/​vault.yml

# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass
ansible-playbook site.yml --vault-password-file ~/​.vault_pass

Vault Best Practices:

# group_vars/​all/​vars.yml - Reference vault variables
db_host: "{{ vault_db_host }}"
db_name: "{{ vault_db_name }}"
db_user: "{{ vault_db_user }}"
db_password: "{{ vault_db_password }}"

# group_vars/​all/​vault.yml - Encrypted
vault_db_host: db.example.com
vault_db_name: production
vault_db_user: app_user
vault_db_password: supersecretpassword

Common Modules

Package Management:

# apt (Debian/​Ubuntu)
- name: Install packages
  ansible.builtin.apt:
    name:
      - nginx
      - postgresql-client
      - python3-pip
    state: present
    update_cache: true

# yum/​dnf (RHEL/​CentOS)
- name: Install packages
  ansible.builtin.dnf:
    name:
      - nginx
      - postgresql
    state: present

File Operations:

# Create directory
- name: Create app directory
  ansible.builtin.file:
    path: /​opt/​myapp
    state: directory
    owner: deploy
    group: deploy
    mode: '0755'

# Copy file
- name: Copy application
  ansible.builtin.copy:
    src: app.jar
    dest: /​opt/​myapp/​app.jar
    owner: deploy
    mode: '0644'

# Template
- name: Deploy config
  ansible.builtin.template:
    src: config.yml.j2
    dest: /​opt/​myapp/​config.yml
    owner: deploy
    mode: '0600'
  notify: Restart app

# Line in file
- name: Ensure line in config
  ansible.builtin.lineinfile:
    path: /​etc/​sysctl.conf
    regexp: '^net.ipv4.ip_forward'
    line: 'net.ipv4.ip_forward = 1'
  notify: Reload sysctl

Service Management:

- name: Manage service
  ansible.builtin.systemd:
    name: nginx
    state: started      # started, stopped, restarted, reloaded
    enabled: true       # Start on boot
    daemon_reload: true # If unit file changed

User Management:

- name: Create application user
  ansible.builtin.user:
    name: deploy
    shell: /​bin/​bash
    groups: www-data
    append: true
    create_home: true

- name: Add SSH key
  ansible.posix.authorized_key:
    user: deploy
    key: "{{ lookup('file', 'files/​deploy_key.pub') }}"
    state: present

Command Execution:

# Shell command
- name: Run database migrations
  ansible.builtin.shell: |
    cd /​opt/​myapp
    ./​manage.py migrate --noinput
  args:
    executable: /​bin/​bash
  become_user: deploy
  register: migration_result
  changed_when: "'No migrations to apply' not in migration_result.stdout"

# Command (no shell processing)
- name: Check app version
  ansible.builtin.command: /​opt/​myapp/​bin/​app --version
  register: app_version
  changed_when: false  # Read-only command

Control Flow

Conditionals:

- name: Install on Debian
  ansible.builtin.apt:
    name: nginx
  when: ansible_os_family == "Debian"

- name: Complex condition
  ansible.builtin.debug:
    msg: "Production web server"
  when:
    - inventory_hostname in groups['webservers']
    - env == 'production'
    - nginx_enabled | default(true)

Loops:

# Simple loop
- name: Create users
  ansible.builtin.user:
    name: "{{ item }}"
    state: present
  loop:
    - alice
    - bob
    - charlie

# Loop with dict
- name: Create users with groups
  ansible.builtin.user:
    name: "{{ item.name }}"
    groups: "{{ item.groups }}"
  loop:
    - { name: 'alice', groups: 'admin' }
    - { name: 'bob', groups: 'developers' }

# Loop with index
- name: Create numbered files
  ansible.builtin.file:
    path: "/​tmp/​file{{ ansible_loop.index }}"
    state: touch
  loop: "{{ range(1, 5) | list }}"

Blocks & Error Handling:

- name: Deploy application
  block:
    - name: Download artifact
      ansible.builtin.get_url:
        url: "{{ artifact_url }}"
        dest: /​tmp/​app.tar.gz

    - name: Extract artifact
      ansible.builtin.unarchive:
        src: /​tmp/​app.tar.gz
        dest: /​opt/​myapp
        remote_src: true

    - name: Start application
      ansible.builtin.systemd:
        name: myapp
        state: started

  rescue:
    - name: Rollback to previous version
      ansible.builtin.command: /​opt/​myapp/​rollback.sh

    - name: Notify failure
      ansible.builtin.debug:
        msg: "Deployment failed, rolled back"

  always:
    - name: Clean up temp files
      ansible.builtin.file:
        path: /​tmp/​app.tar.gz
        state: absent

Execution Commands

# Run playbook
ansible-playbook playbooks/​site.yml

# Specify inventory
ansible-playbook -i inventory/​production playbooks/​site.yml

# Limit to hosts/​groups
ansible-playbook site.yml --limit webservers
ansible-playbook site.yml --limit web1.example.com

# Run specific tags
ansible-playbook site.yml --tags "deploy,config"
ansible-playbook site.yml --skip-tags "slow"

# Check mode (dry run)
ansible-playbook site.yml --check

# Diff mode (show changes)
ansible-playbook site.yml --check --diff

# Extra variables
ansible-playbook site.yml -e "version=1.2.3 env=prod"
ansible-playbook site.yml -e "@vars.yml"

# Verbose output
ansible-playbook site.yml -v    # Basic
ansible-playbook site.yml -vvv  # Debug

# Ad-hoc commands
ansible all -m ping
ansible webservers -m shell -a "uptime"
ansible webservers -m ansible.builtin.apt -a "name=nginx state=present" --become

Best Practices

1. Idempotency - Always:

# BAD - Not idempotent
- name: Add to hosts file
  ansible.builtin.shell: echo "10.0.0.1 db.local" >> /​etc/​hosts

# GOOD - Idempotent
- name: Add to hosts file
  ansible.builtin.lineinfile:
    path: /​etc/​hosts
    line: "10.0.0.1 db.local"
    state: present

2. Use FQCN (Fully Qualified Collection Names):

# BAD
- apt:
    name: nginx

# GOOD
- ansible.builtin.apt:
    name: nginx

3. Name Every Task:

# BAD
- ansible.builtin.apt:
    name: nginx

# GOOD
- name: Install nginx web server
  ansible.builtin.apt:
    name: nginx

4. Use Tags:

- name: Install packages
  ansible.builtin.apt:
    name: nginx
  tags:
    - packages
    - nginx

- name: Deploy config
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /​etc/​nginx/​nginx.conf
  tags:
    - config
    - nginx

Troubleshooting

IssueSolution
SSH connection failedCheck ansible_host, ansible_user, SSH key
Permission deniedAdd become: true, check sudo config
Module not foundInstall collection: ansible-galaxy collection install
Variable undefinedCheck variable precedence, use default() filter
Idempotency brokenUse proper modules, check changed_when

Checklist

Before running:

  • Inventory is correct for target environment
  • Vault password available
  • SSH connectivity verified (ansible all -m ping)
  • Check mode tested (--check --diff)
  • Sensitive data in vault, not plain text
  • Tasks are idempotent
  • Handlers notify correctly

Integration

Works with:

  • /​devops - Infrastructure automation
  • /​terraform - Terraform provisions, Ansible configures
  • /​security - Security hardening playbooks
  • golden-paths skill - Standardized automation

More DevOps & CI/CD skills

Project scaffolding, deployment configuration, and CI/CD setup for Google ADK agents.

6K 472.6K
View

Set up tracing, logging, and monitoring for deployed ADK agents across Cloud Trace, BigQuery, and third-party platforms.

6K 472.6K
View

Enterprise Azure infrastructure architect generating Bicep or Terraform from workload descriptions.

1.5K 454.3K
View
azure-kubernetes logo
DevOps & CI/CD

azure-kubernetes

Plan and configure production-ready Azure Kubernetes Service clusters with Day-0 and Day-1 best practices.

1.5K 447.1K
View

Raw mechanical interfaces fusing Swiss typographic print with military terminal aesthetics. Rigid grids, extreme type scale contrast, utilitarian color, analog degradation effects. For data-heavy dashboards, portfolios, or editorial sites that need to feel like declassified blueprints.

92.7K 354.9K
View
just-scrape logo
DevOps & CI/CD

just-scrape

Web search, scraping, extraction, crawling, and monitoring via ScrapeGraph AI CLI.

69 245K
View

Skill for working with Firebase Hosting (Classic). Use this when you want to deploy static web apps, Single Page Apps (SPAs), or simple microservices. Do NOT use for Firebase App Hosting.

462 159.7K
View

Deploy and manage web apps with Firebase App Hosting. Use this skill when deploying Next.js/Angular apps with backends.

462 159.1K
View
deploy-to-vercel logo
DevOps & CI/CD

deploy-to-vercel

Deploy applications and websites to Vercel. Use when the user requests deployment actions like "deploy my app", "deploy and give me the link", "push this live", or "create a preview deployment".

31.9K 146.6K
View
programmatic-seo logo
DevOps & CI/CD

programmatic-seo

Build SEO-optimized pages at scale using templates, data, and proven playbook patterns.

53.3K 140.1K
View

Design and build isolated, reusable Convex backend components with clear boundaries and app-facing wrappers.

63 123.5K
View

Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".

31.9K 116.1K
View

Coding & apps AI tools

Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View
Imagica logo
Coding & apps

Imagica

Build a no-code AI app in minutes.

Freemium
View
E
Coding & apps

Emergent.sh

An IDE for code migration from legacy to modern frameworks through coding agents.

Freemium
View
Wonder Dynamics logo
Coding & apps

Wonder Dynamics

Automate CGI animation in live-action scenes

Paid
View
M
Coding & apps

Mixo

Launch a website in seconds with AI.

Paid
View
AI Code Convert logo
Coding & apps

AI Code Convert

Streamline Your Coding Experience with AI Code Helper

Free
View