Ansible Best Practices 2025: Project Structure, Security, and Performance

As Ansible continues to evolve, so do the best practices for using it effectively. This guide covers the essential best practices for Ansible in 2025, focusing on project structure, security, performance optimization, and maintainability. Whether you are new to Ansible or looking to improve existing automation, these practices will help you build robust and scalable infrastructure as code.

Project Structure

A well-organized project structure is crucial for maintainability and collaboration:

ansible-project/
├── ansible.cfg
├── requirements.yml
├── inventory/
│   ├── production/
│   │   ├── hosts.yml
│   │   ├── group_vars/
│   │   │   ├── all.yml
│   │   │   ├── webservers.yml
│   │   │   └── databases.yml
│   │   └── host_vars/
│   │       └── server1.yml
│   └── staging/
│       ├── hosts.yml
│       └── group_vars/
│           └── all.yml
├── playbooks/
│   ├── site.yml
│   ├── webservers.yml
│   └── databases.yml
├── roles/
│   ├── common/
│   ├── nginx/
│   ├── postgresql/
│   └── security/
└── collections/
    └── requirements.yml

Key Principles

  • Separate environments – Keep production and staging inventories isolated
  • Use group_vars – Avoid hardcoding values in playbooks
  • Host-specific configs – Use host_vars for unique settings
  • Version everything – Store in Git with meaningful commits

Writing Idempotent Playbooks

Idempotency means running a playbook multiple times produces the same result. This is critical for reliable automation.

Good Practice

- name: Ensure nginx is installed
  ansible.builtin.package:
    name: nginx
    state: present

- name: Ensure nginx config exists
  ansible.builtin.template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: "0644"
  notify: Reload nginx

- name: Ensure nginx is running
  ansible.builtin.service:
    name: nginx
    state: started
    enabled: true

Avoid These Patterns

# BAD: Not idempotent - runs every time
- name: Add line to file
  ansible.builtin.shell: echo "some config" >> /etc/myapp.conf

# GOOD: Idempotent - only changes if needed
- name: Ensure line exists in file
  ansible.builtin.lineinfile:
    path: /etc/myapp.conf
    line: "some config"
    state: present

Role Design Best Practices

Single Responsibility

Each role should do one thing well:

# Good role structure
roles/
├── nginx/           # Just web server
├── certbot/         # Just SSL certificates
├── firewall/        # Just firewall rules
└── users/           # Just user management

Use Defaults Wisely

# roles/nginx/defaults/main.yml
---
nginx_worker_processes: auto
nginx_worker_connections: 1024
nginx_keepalive_timeout: 65
nginx_server_tokens: "off"
nginx_ssl_protocols: "TLSv1.2 TLSv1.3"

Document Your Roles

# roles/nginx/README.md
# Nginx Role

## Requirements
- RHEL 8+ or Ubuntu 20.04+

## Role Variables
| Variable | Default | Description |
|----------|---------|-------------|
| nginx_worker_processes | auto | Number of worker processes |

## Example Playbook
```yaml
- hosts: webservers
  roles:
    - nginx
```

Security Best Practices

Use Ansible Vault for Secrets

Never store passwords or API keys in plain text:

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

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

# Run playbook with vault
ansible-playbook site.yml --ask-vault-pass

# Or use password file
ansible-playbook site.yml --vault-password-file ~/.vault_pass

Vault File Structure

# group_vars/all/vault.yml (encrypted)
---
vault_db_password: "supersecret123"
vault_api_key: "abc123xyz"

# group_vars/all/vars.yml (plain text, references vault)
---
db_password: "{{ vault_db_password }}"
api_key: "{{ vault_api_key }}"

Limit Privilege Escalation

# Only use become when necessary
- name: Install packages (needs root)
  ansible.builtin.package:
    name: nginx
    state: present
  become: true

- name: Deploy app config (no root needed)
  ansible.builtin.template:
    src: app.conf.j2
    dest: /home/appuser/app.conf
  become: false

Performance Optimization

ansible.cfg Tuning

[defaults]
# Increase parallelism
forks = 20

# Faster SSH
host_key_checking = False

# Reduce output noise
callback_whitelist = profile_tasks

# Use smart gathering
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /tmp/ansible_facts
fact_caching_timeout = 3600

[ssh_connection]
# Enable pipelining (major speed boost)
pipelining = True

# SSH optimizations
ssh_args = -o ControlMaster=auto -o ControlPersist=60s

Limit Fact Gathering

# Disable if not needed
- hosts: all
  gather_facts: false
  tasks:
    - name: Simple task not needing facts
      ansible.builtin.ping:

# Or gather only what you need
- hosts: all
  gather_facts: true
  gather_subset:
    - network
    - hardware

Use async for Long Tasks

- name: Run long backup job
  ansible.builtin.command: /usr/local/bin/backup.sh
  async: 3600
  poll: 0
  register: backup_job

- name: Check backup status
  ansible.builtin.async_status:
    jid: "{{ backup_job.ansible_job_id }}"
  register: job_result
  until: job_result.finished
  retries: 60
  delay: 60

Testing Your Playbooks

Syntax Check

# Always check syntax before running
ansible-playbook site.yml --syntax-check

Dry Run (Check Mode)

# See what would change without making changes
ansible-playbook site.yml --check --diff

Use Molecule for Role Testing

# Install molecule
pip install molecule molecule-docker

# Initialize role with molecule
molecule init role my_role

# Run tests
cd roles/my_role
molecule test

Use Collections

Collections are the modern way to distribute Ansible content:

# collections/requirements.yml
---
collections:
  - name: ansible.posix
    version: ">=1.5.0"
  - name: community.general
    version: ">=8.0.0"
  - name: amazon.aws
    version: ">=7.0.0"

# Install collections
ansible-galaxy collection install -r collections/requirements.yml

Conclusion

Following these best practices will make your Ansible automation more reliable, secure, and maintainable. The key principles are:

  • Organize projects consistently
  • Write idempotent playbooks
  • Keep roles focused and documented
  • Secure secrets with Vault
  • Optimize for performance
  • Test before deploying

As AI tools like Ansible Lightspeed become more prevalent, these fundamentals become even more important. Good structure and practices ensure that AI-generated code integrates smoothly into your existing automation.

Was this article helpful?

R

About Ramesh Sundararamaiah

Red Hat Certified Architect

Expert in Linux system administration, DevOps automation, and cloud infrastructure. Specializing in Red Hat Enterprise Linux, CentOS, Ubuntu, Docker, Ansible, and enterprise IT solutions.

🐧 Stay Updated with Linux Tips

Get the latest tutorials, news, and guides delivered to your inbox weekly.

Add Comment