# Ansible Roles > Reusable, self-contained units of automation with a standard directory structure. ## Role Directory Structure ``` roles/ └── nginx/ ├── defaults/ │ └── main.yml # Low-precedence defaults (user overrides these) ├── vars/ │ └── main.yml # High-precedence vars (internal to role) ├── tasks/ │ └── main.yml # Entry point for tasks ├── handlers/ │ └── main.yml # Handlers triggered by notify ├── templates/ │ └── nginx.conf.j2 # Jinja2 templates ├── files/ │ └── index.html # Static files for copy module ├── meta/ │ └── main.yml # Dependencies, galaxy metadata └── README.md ``` All directories are optional — include only what the role needs. ### What Goes Where | Directory | Purpose | Precedence | |-------------|--------------------------------|-------------| | `defaults/` | Default variable values | **Lowest** — meant to be overridden | | `vars/` | Internal role variables | **High** — not easily overridden | | `tasks/` | Main task list | Entry point: `tasks/main.yml` | | `handlers/` | Restart/reload triggers | Run once at end of play | | `templates/` | Jinja2 files (`.j2`) | Referenced as `template: nginx.conf.j2` | | `files/` | Static files | Referenced as `copy: src=index.html` | | `meta/` | Role deps, platform info | Processed before role tasks run | ## Creating a Role ```bash # Scaffold with ansible-galaxy ansible-galaxy role init roles/nginx # Minimal manual structure mkdir -p roles/nginx/{tasks,defaults,templates,handlers,meta} ``` ## Using Roles ### In a Playbook ```yaml # site.yml - hosts: webservers roles: - nginx # simple - role: nginx # with params vars: nginx_listen_port: 8080 - role: nginx # conditional when: install_nginx | bool - role: nginx # tagged tags: [web, nginx] ``` ### With `include_role` / `import_role` ```yaml tasks: # Static import — processed at parse time - import_role: name: nginx # Dynamic include — processed at runtime (supports loops/conditionals) - include_role: name: "{{ item }}" loop: - nginx - certbot ``` | | `import_role` | `include_role` | |---|---|---| | When parsed | Playbook load | Runtime | | Tags/when | Applied to all tasks inside | Applied to include itself | | Loops | Not supported | Supported | | Handlers | Visible globally | Scoped to include | ## Role Dependencies ```yaml # roles/nginx/meta/main.yml dependencies: - role: common - role: firewall vars: firewall_allowed_ports: - 80 - 443 ``` Dependencies run **before** the role's own tasks. Duplicate dependencies are skipped unless `allow_duplicates: true` is set. ## Example Role: nginx ```yaml # roles/nginx/defaults/main.yml nginx_listen_port: 80 nginx_worker_processes: auto nginx_server_name: "_" nginx_root: /var/www/html nginx_access_log: /var/log/nginx/access.log ``` ```yaml # roles/nginx/tasks/main.yml - name: Install nginx apt: name: nginx state: present update_cache: true - name: Deploy configuration template: src: nginx.conf.j2 dest: /etc/nginx/nginx.conf owner: root group: root mode: "0644" notify: Reload nginx - name: Enable and start nginx service: name: nginx state: started enabled: true ``` ```yaml # roles/nginx/handlers/main.yml - name: Reload nginx service: name: nginx state: reloaded ``` ## Splitting Tasks ```yaml # roles/nginx/tasks/main.yml - import_tasks: install.yml - import_tasks: configure.yml - import_tasks: service.yml # Conditional platform tasks - import_tasks: debian.yml when: ansible_os_family == "Debian" - import_tasks: redhat.yml when: ansible_os_family == "RedHat" ``` ## Project Layout with Roles ``` ansible-project/ ├── ansible.cfg ├── site.yml # Master playbook ├── webservers.yml # Play for web tier ├── dbservers.yml # Play for db tier ├── inventory/ │ ├── hosts.yml │ ├── group_vars/ │ └── host_vars/ ├── roles/ │ ├── common/ # Shared baseline │ ├── nginx/ │ ├── postgresql/ │ └── requirements.yml # Galaxy dependencies └── collections/ └── requirements.yml # Collection dependencies ``` ```yaml # roles/requirements.yml - src: geerlingguy.docker version: "6.1.0" - src: geerlingguy.certbot ``` ```bash ansible-galaxy install -r roles/requirements.yml -p roles/ ``` ## Gotchas - `defaults/` is for users to override; `vars/` is for role internals — mixing them up breaks precedence - Handlers run at end of play, not after each task — use `meta: flush_handlers` if needed mid-play - Role names must be valid Python identifiers (no hyphens) when used as variable namespaces - `import_role` makes role tasks visible to `--list-tasks`; `include_role` does not - Dependencies declared in `meta/` run every time the role is referenced unless deduplicated - Template paths are relative to `templates/` — use `nginx.conf.j2`, not `templates/nginx.conf.j2` - File paths in `copy` are relative to `files/` — use `index.html`, not `files/index.html` ## See Also - `ansible` — core commands and ansible.cfg - `ansible-variables` — how defaults/ vs vars/ fits into precedence - `ansible-inventory` — inventory structure for role-consuming projects