Learn-skills.dev ansible
Ansible automation for server configuration, deployment, and infrastructure management. Use when user mentions "ansible", "ansible-playbook", "ansible-vault", "inventory", "playbook", "ansible role", "ansible galaxy", "configuration management", "server provisioning", "infrastructure automation", "ansible task", "ansible template", or automating server setup and deployment.
install
source · Clone the upstream repo
git clone https://github.com/NeverSight/learn-skills.dev
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/NeverSight/learn-skills.dev "$T" && mkdir -p ~/.claude/skills && cp -r "$T/data/skills-md/1mangesh1/dev-skills-collection/ansible" ~/.claude/skills/neversight-learn-skills-dev-ansible && rm -rf "$T"
manifest:
data/skills-md/1mangesh1/dev-skills-collection/ansible/SKILL.mdsource content
Ansible
Agentless automation over SSH. No agents to install on managed nodes -- just Python on targets and SSH access. All modules are idempotent by default: running a playbook twice produces the same result.
Ad-Hoc Commands
# Ping all hosts ansible all -m ping # Run command on web servers ansible webservers -m command -a "uptime" ansible webservers -m shell -a "df -h | grep /dev/sda" # Copy file ansible webservers -m copy -a "src=./app.conf dest=/etc/app.conf owner=root mode=0644" # Install package ansible webservers -m apt -a "name=nginx state=present" --become # Manage service ansible webservers -m systemd -a "name=nginx state=restarted enabled=yes" --become # Create user ansible all -m user -a "name=deploy shell=/bin/bash groups=sudo" --become # Gather facts ansible webservers -m setup -a "filter=ansible_distribution*"
Inventory
Static Inventory (hosts.ini)
[webservers] web1.example.com ansible_host=10.0.1.10 web2.example.com ansible_host=10.0.1.11 [dbservers] db1.example.com ansible_host=10.0.2.10 ansible_port=2222 [production:children] webservers dbservers [webservers:vars] ansible_user=deploy ansible_python_interpreter=/usr/bin/python3 [all:vars] ansible_ssh_private_key_file=~/.ssh/deploy_key
YAML Inventory (inventory.yml)
all: children: webservers: hosts: web1.example.com: ansible_host: 10.0.1.10 http_port: 8080 web2.example.com: ansible_host: 10.0.1.11 dbservers: hosts: db1.example.com: ansible_host: 10.0.2.10 vars: db_port: 5432
Host and Group Variables
inventory/ hosts.yml group_vars/ all.yml # Applies to every host webservers.yml # Applies to webservers group production.yml host_vars/ web1.example.com.yml
Dynamic Inventory
# AWS EC2 ansible-inventory -i aws_ec2.yml --list # Plugin config (aws_ec2.yml)
plugin: amazon.aws.aws_ec2 regions: - us-east-1 filters: tag:Environment: production keyed_groups: - key: tags.Role prefix: role compose: ansible_host: private_ip_address
Playbook Structure
--- - name: Configure web servers hosts: webservers become: yes vars: app_port: 8080 app_user: www-data pre_tasks: - name: Update apt cache apt: update_cache: yes cache_valid_time: 3600 tasks: - name: Install nginx apt: name: nginx state: present - name: Deploy nginx config template: src: nginx.conf.j2 dest: /etc/nginx/sites-available/default owner: root mode: "0644" notify: Restart nginx - name: Ensure nginx is running systemd: name: nginx state: started enabled: yes handlers: - name: Restart nginx systemd: name: nginx state: restarted post_tasks: - name: Verify nginx is responding uri: url: "http://localhost:{{ app_port }}" status_code: 200
# Run playbook ansible-playbook site.yml ansible-playbook site.yml -i inventory/hosts.yml ansible-playbook site.yml --limit webservers ansible-playbook site.yml --check # Dry run ansible-playbook site.yml --diff # Show file changes ansible-playbook site.yml -e "app_port=9090" ansible-playbook site.yml --start-at-task="Deploy nginx config"
Common Modules
# Package management - apt: name: [nginx, curl, git] state: present - yum: name: httpd state: latest # Files and directories - file: path: /opt/app state: directory owner: deploy group: deploy mode: "0755" - file: path: /tmp/old_file state: absent # Copy files - copy: src: files/app.conf dest: /etc/app/app.conf owner: root mode: "0644" backup: yes - copy: content: "{{ lookup('template', 'config.j2') }}" dest: /etc/app/config.yml # Templates - template: src: templates/vhost.conf.j2 dest: /etc/nginx/conf.d/app.conf validate: "nginx -t -c %s" # Services - systemd: name: nginx state: restarted daemon_reload: yes enabled: yes # Users and groups - user: name: deploy shell: /bin/bash groups: [sudo, docker] append: yes generate_ssh_key: yes - authorized_key: user: deploy key: "{{ lookup('file', 'files/deploy.pub') }}" # Git checkout - git: repo: https://github.com/org/app.git dest: /opt/app version: main force: yes # Docker containers - docker_container: name: redis image: redis:7-alpine state: started restart_policy: unless-stopped ports: - "6379:6379" volumes: - redis_data:/data # Download files - get_url: url: https://example.com/app.tar.gz dest: /tmp/app.tar.gz checksum: sha256:abcdef1234567890 # Run commands - command: /opt/app/migrate.sh args: chdir: /opt/app creates: /opt/app/.migrated - shell: cat /etc/passwd | grep deploy register: deploy_check changed_when: false # Cron jobs - cron: name: "Daily backup" minute: "0" hour: "2" job: "/opt/scripts/backup.sh >> /var/log/backup.log 2>&1"
Variables and Facts
# Variable precedence (lowest to highest): # role defaults -> inventory vars -> playbook vars -> role vars # -> include_vars -> set_fact -> extra vars (-e) # Register output - command: whoami register: current_user - debug: msg: "Running as {{ current_user.stdout }}" # Set facts dynamically - set_fact: app_version: "{{ lookup('file', 'VERSION') }}" deploy_timestamp: "{{ ansible_date_time.iso8601 }}" # Access facts - debug: msg: "OS: {{ ansible_distribution }} {{ ansible_distribution_version }}" - debug: msg: "IP: {{ ansible_default_ipv4.address }}" - debug: msg: "Memory: {{ ansible_memtotal_mb }} MB" # Include vars from file - include_vars: file: "{{ ansible_distribution | lower }}.yml"
Jinja2 Templates
{# templates/nginx.conf.j2 #} server { listen {{ http_port | default(80) }}; server_name {{ server_name }}; {% if ssl_enabled | default(false) %} listen 443 ssl; ssl_certificate /etc/ssl/certs/{{ domain }}.crt; ssl_certificate_key /etc/ssl/private/{{ domain }}.key; {% endif %} {% for location in app_locations %} location {{ location.path }} { proxy_pass http://{{ location.upstream }}; } {% endfor %} access_log /var/log/nginx/{{ server_name }}_access.log; }
Common Jinja2 filters:
# Defaults "{{ variable | default('fallback') }}" # String "{{ name | upper }}" "{{ name | lower }}" "{{ path | basename }}" "{{ path | dirname }}" # Lists "{{ packages | join(', ') }}" "{{ users | map(attribute='name') | list }}" "{{ items | unique | sort }}" # Data "{{ dict_var | to_json }}" "{{ dict_var | to_yaml }}" "{{ 'password' | password_hash('sha512') }}" # Ternary "{{ 'yes' if enabled else 'no' }}"
Conditionals and Loops
# When conditional - apt: name: nginx when: ansible_distribution == "Ubuntu" - yum: name: httpd when: ansible_os_family == "RedHat" - service: name: app state: restarted when: deploy_result is changed - debug: msg: "Low disk" when: ansible_mounts | selectattr('mount', 'equalto', '/') | map(attribute='size_available') | first < 1073741824 # Loops - user: name: "{{ item }}" state: present loop: - alice - bob - carol - apt: name: "{{ item.name }}" state: "{{ item.state }}" loop: - { name: nginx, state: present } - { name: apache2, state: absent } # Loop with index - debug: msg: "{{ index }}: {{ item }}" loop: "{{ users }}" loop_control: index_var: index label: "{{ item.name }}" # Until retry loop - uri: url: http://localhost:8080/health status_code: 200 register: health until: health.status == 200 retries: 30 delay: 5
Error Handling
# Ignore errors - command: /opt/app/check.sh ignore_errors: yes register: check_result # Custom failure condition - command: /opt/app/status.sh register: status failed_when: "'ERROR' in status.stdout" changed_when: "'UPDATED' in status.stdout" # Block / rescue / always (try/catch/finally) - block: - name: Deploy application git: repo: https://github.com/org/app.git dest: /opt/app version: "{{ app_version }}" - name: Run migrations command: ./migrate.sh args: chdir: /opt/app rescue: - name: Rollback on failure command: ./rollback.sh args: chdir: /opt/app - name: Send failure alert mail: to: ops@example.com subject: "Deploy failed on {{ inventory_hostname }}" body: "Rolled back to previous version." always: - name: Restart application systemd: name: app state: restarted
Tags
- name: Install packages apt: name: nginx tags: [packages, nginx] - name: Configure nginx template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf tags: [config, nginx] - name: Deploy application git: repo: https://github.com/org/app.git dest: /opt/app tags: [deploy]
ansible-playbook site.yml --tags "deploy" ansible-playbook site.yml --skip-tags "packages" ansible-playbook site.yml --tags "config,nginx" ansible-playbook site.yml --list-tags
Roles
Directory Structure
roles/ webserver/ defaults/main.yml # Default variables (lowest precedence) vars/main.yml # Role variables (high precedence) tasks/main.yml # Task list handlers/main.yml # Handlers templates/ # Jinja2 templates files/ # Static files meta/main.yml # Role metadata and dependencies
Example Role
# roles/webserver/defaults/main.yml http_port: 80 server_name: localhost document_root: /var/www/html # 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 - name: Enable site file: src: /etc/nginx/sites-available/default dest: /etc/nginx/sites-enabled/default state: link # roles/webserver/handlers/main.yml --- - name: Reload nginx systemd: name: nginx state: reloaded # roles/webserver/meta/main.yml --- dependencies: - role: common - role: firewall vars: open_ports: [80, 443]
# Use role in playbook - hosts: webservers become: yes roles: - common - { role: webserver, http_port: 8080 } - role: ssl when: ssl_enabled | default(false)
Ansible Galaxy
# Install roles ansible-galaxy install geerlingguy.docker ansible-galaxy install -r requirements.yml # Install collections ansible-galaxy collection install community.docker ansible-galaxy collection install -r requirements.yml # Initialize new role ansible-galaxy role init my_role # List installed ansible-galaxy list ansible-galaxy collection list
# requirements.yml roles: - name: geerlingguy.docker version: "6.1.0" - name: geerlingguy.certbot - src: https://github.com/org/custom-role.git name: custom_role version: main collections: - name: community.docker version: ">=3.0.0" - name: amazon.aws version: "7.0.0"
Ansible Vault
# Encrypt a file ansible-vault encrypt vars/secrets.yml # Decrypt ansible-vault decrypt vars/secrets.yml # Edit encrypted file in-place ansible-vault edit vars/secrets.yml # View without decrypting ansible-vault view vars/secrets.yml # Change encryption password ansible-vault rekey vars/secrets.yml # Encrypt a single string ansible-vault encrypt_string 'SuperSecret123' --name 'db_password' # Run playbook with vault ansible-playbook site.yml --ask-vault-pass ansible-playbook site.yml --vault-password-file ~/.vault_pass
# vars/secrets.yml (encrypted content) db_password: !vault | $ANSIBLE_VAULT;1.1;AES256 6234613839383... # ansible.cfg - avoid typing password every time [defaults] vault_password_file = ~/.vault_pass
Docker and Container Management
# Requires: ansible-galaxy collection install community.docker - name: Deploy containerized app hosts: docker_hosts become: yes collections: - community.docker tasks: - name: Install Docker include_role: name: geerlingguy.docker - name: Pull image docker_image: name: myapp tag: "{{ app_version }}" source: pull - name: Run app container docker_container: name: myapp image: "myapp:{{ app_version }}" state: started restart_policy: unless-stopped ports: - "8080:8080" env: DATABASE_URL: "{{ db_url }}" REDIS_URL: "{{ redis_url }}" volumes: - app_data:/data networks: - name: app_network - name: Docker compose deployment docker_compose_v2: project_src: /opt/app state: present pull: always
Testing with Molecule
pip install molecule molecule-docker # Initialize cd roles/webserver molecule init scenario --driver-name docker # Test lifecycle molecule create # Create test instance molecule converge # Run role against instance molecule verify # Run tests molecule destroy # Clean up molecule test # Full cycle (create -> converge -> verify -> destroy)
# molecule/default/molecule.yml dependency: name: galaxy driver: name: docker platforms: - name: ubuntu image: ubuntu:22.04 pre_build_image: false command: /sbin/init privileged: true - name: rocky image: rockylinux:9 command: /sbin/init privileged: true provisioner: name: ansible verifier: name: ansible # molecule/default/verify.yml --- - name: Verify hosts: all tasks: - name: Check nginx is running command: systemctl is-active nginx changed_when: false - name: Check port 80 wait_for: port: 80 timeout: 5
Performance Tuning
# ansible.cfg [defaults] forks = 20 # Parallel host connections (default 5) gathering = smart # Cache facts, don't re-gather fact_caching = jsonfile fact_caching_connection = /tmp/ansible_facts fact_caching_timeout = 86400 host_key_checking = False stdout_callback = yaml # Readable output [ssh_connection] pipelining = True # Reduce SSH operations (requires requiretty off) ssh_args = -o ControlMaster=auto -o ControlPersist=60s
# Async tasks for long operations - name: Long running update apt: upgrade: dist async: 3600 # Max runtime in seconds poll: 10 # Check every 10 seconds (0 = fire and forget) # Limit fact gathering - hosts: webservers gather_facts: no tasks: - setup: gather_subset: - network - hardware # Free strategy (don't wait for all hosts per task) - hosts: webservers strategy: free tasks: - name: Independent task apt: name: curl
For Mitogen (3-7x speedup), install
mitogen and set strategy_plugins and strategy = mitogen_linear in ansible.cfg.
Common Patterns
Deploy Application
- hosts: webservers become: yes serial: "30%" # Rolling deploy max_fail_percentage: 10 pre_tasks: - name: Remove from load balancer uri: url: "http://lb.internal/api/remove/{{ inventory_hostname }}" method: POST roles: - app_deploy post_tasks: - name: Add back to load balancer uri: url: "http://lb.internal/api/add/{{ inventory_hostname }}" method: POST - name: Health check uri: url: "http://localhost:{{ app_port }}/health" status_code: 200 retries: 10 delay: 3
Setup Users and SSH
- name: Configure users hosts: all become: yes tasks: - name: Create users user: name: "{{ item.name }}" groups: "{{ item.groups | default([]) }}" shell: /bin/bash loop: "{{ admin_users }}" - name: Add SSH keys authorized_key: user: "{{ item.name }}" key: "{{ item.ssh_key }}" loop: "{{ admin_users }}" - name: Sudoers entry lineinfile: path: /etc/sudoers.d/{{ item.name }} line: "{{ item.name }} ALL=(ALL) NOPASSWD:ALL" create: yes mode: "0440" validate: "visudo -cf %s" loop: "{{ admin_users }}" when: item.sudo | default(false)