Convert a growing playbook into a reusable role with tasks, handlers, and templates. Deploy a simple Nginx site using a production-style structure that scales as your automation grows.
You are managing a growing set of playbooks and the structure is starting to sprawl. To keep things clean and reusable, you migrate to roles and implement a production pattern: tasks install and configure, handlers restart services only when needed, and templates render host-specific content.
Roles are the unit of reuse in Ansible. The goal is a layout you can drop into any repo, compose into a site playbook, and lint for consistency.
ansible-galaxy init.web group.ansible-lint.tasks/,
handlers/, templates/,
defaults/) and predictable entry points.
nginx.
ansible-galaxy init nginx
This creates the standard role layout. You will primarily work
in tasks/main.yml, handlers/main.yml,
and templates/.
nano nginx/tasks/main.yml
# OR
vim nginx/tasks/main.yml
- name: Install nginx
package:
name: nginx
state: present
- name: Ensure nginx is enabled and running
service:
name: nginx
state: started
enabled: yes
The package module keeps installs portable across
distros. The service task enforces runtime state.
nano nginx/handlers/main.yml
# OR
vim nginx/handlers/main.yml
- name: restart nginx
service:
name: nginx
state: restarted
Handlers are designed for change-driven operations. You only restart when something actually changed.
nano nginx/templates/index.html.j2
# OR
vim nginx/templates/index.html.j2
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Ansible Role Deployed</title>
</head>
<body>
<h1>Deployed by Ansible</h1>
<p>Host: {{ ansible_hostname }}</p>
</body>
</html>
This uses ansible_hostname from gathered facts to
render host-specific output.
nano nginx/tasks/main.yml
# OR
vim nginx/tasks/main.yml
- name: Install nginx
package:
name: nginx
state: present
- name: Ensure nginx is enabled and running
service:
name: nginx
state: started
enabled: yes
- name: Deploy custom index.html
template:
src: index.html.j2
dest: /usr/share/nginx/html/index.html
owner: root
group: root
mode: "0644"
notify: restart nginx
The handler runs only if the template task reports a change.
web group.
nano site.yml
# OR
vim site.yml
- name: Configure web servers
hosts: web
become: yes
roles:
- nginx
This gives you a clean entry point. As you add more roles,
keep site.yml readable by composing roles per
host group.
ansible-playbook -i ~/inventory site.yml
First run should show changes. Subsequent runs should converge
to mostly ok unless tasks or templates change.
ansible-lint site.yml
Linting is production hygiene. It helps keep roles consistent and reduces surprises when roles get shared across teams.
Install it with your Python tooling or distro package and keep it pinned in your environment so linting is reproducible.
Confirm the destination path matches your Nginx document root and that the service is running. Also confirm you are targeting the correct inventory and group.
Handlers only run when notified and a task reports a change. If the template is identical on disk, no restart will occur.
Ensure the role directory (for example ./nginx)
is in the playbook working directory or in your configured
roles_path.
Remove Nginx from the web group and keep your repo clean by removing the role directory if you are rebuilding from scratch.
ansible web -i ~/inventory -m package -a "name=nginx state=absent" -b
ansible web -i ~/inventory -m service -a "name=nginx state=stopped" -b
Nginx is absent on web hosts and re-running the playbook converges predictably with minimal changes.
ansible-galaxy init <role>
: Creates a standard role directory structure.
ansible-playbook -i <inventory> <playbook>
: Runs a playbook using the specified inventory.
-i <inventory>
: Points Ansible at a specific inventory file.
ansible-lint <playbook>
: Lints a playbook (and referenced roles) for best practices.
package:
: Installs or removes packages in an idempotent way.
state: present
: Ensures the package is installed.
state: absent
: Ensures the package is removed.
service:
: Manages service state and enablement.
state: started
: Ensures the service is running.
state: restarted
: Restarts the service.
enabled: yes
: Enables the service at boot.
template:
: Renders a Jinja2 template to a destination on the target host.
src:
: Template file inside the role templates/ directory.
dest:
: Destination path on the managed host.
notify:
: Triggers a handler when the task changes.
ansible <group> -i <inventory> -m package -a "<args>" -b
: Runs the package module ad-hoc against a target group.
-m package
: Selects the package module.
-a "<args>"
: Supplies module args (for example name=nginx state=absent).
-b
: Enables privilege escalation (become).
ansible <group> -i <inventory> -m service -a "<args>" -b
: Runs the service module ad-hoc against a target group.
-m service
: Selects the service module.
-a "<args>"
: Supplies module args (for example name=nginx state=stopped).
-b
: Enables privilege escalation (become).