Ansible Role Dependencies
Introduction
When building automation with Ansible, you'll often need to create roles that depend on functionality provided by other roles. For example, a role that deploys a web application might depend on roles that install a web server, configure a database, and set up security measures.
Role dependencies let you build modular, composable automation by establishing relationships between roles, allowing them to be used together in a structured way. This approach promotes code reuse and helps maintain clean, organized playbooks.
In this guide, we'll explore how role dependencies work in Ansible, how to configure them correctly, and best practices for managing them effectively.
Understanding Role Dependencies
Role dependencies define relationships where one role requires another role to run before it. When Ansible executes a role with dependencies, it first runs the dependent roles in the order specified.
There are two main ways to define role dependencies in Ansible:
- Using the dependencieskeyword in the role'smeta/main.ymlfile
- Using the import_roleorinclude_rolemodules within tasks
Let's explore both approaches.
Defining Dependencies in meta/main.yml
The most common way to define role dependencies is through the meta/main.yml file in your role directory structure:
my_role/
├── defaults/
├── files/
├── handlers/
├── meta/
│     └── main.yml
├── tasks/
├── templates/
└── vars/
Inside the meta/main.yml file, you can specify dependencies using the dependencies key:
---
dependencies:
  - role: common
  - role: apache
    vars:
      apache_port: 8080
In this example, the role has two dependencies:
- The commonrole, with default variables
- The apacherole, with a custom port variable
How Dependencies Execute
When Ansible runs a role with dependencies:
- It first executes all dependency roles in the order listed
- Then it executes the main role's tasks
- Variables from the parent role are available to dependency roles
- Each dependency is only executed once, even if listed multiple times
Let's see a complete example:
# roles/database/meta/main.yml
---
dependencies: []  # No dependencies
# roles/webserver/meta/main.yml
---
dependencies:
  - role: common
# roles/webapp/meta/main.yml
---
dependencies:
  - role: webserver
  - role: database
    vars:
      database_name: app_production
When you include the webapp role in a playbook:
---
- hosts: app_servers
  roles:
    - role: webapp
Ansible will execute roles in this order:
- common(required by- webserver)
- webserver(required by- webapp)
- databasewith- database_nameset to- app_production(required by- webapp)
- webapp(the main role)
Using import_role and include_role
An alternative to the meta/main.yml approach is to use task inclusion within your role's tasks. This gives you more control over when dependent roles are executed.
Static Role Dependencies with import_role
You can use import_role to statically include another role:
# roles/webapp/tasks/main.yml
---
- name: Import database role
  import_role:
    name: database
  vars:
    database_name: app_production
- name: Setup application configuration
  template:
    src: app.conf.j2
    dest: /etc/app.conf
When using import_role:
- Dependencies are processed during playbook parsing
- Tags and conditionals apply to all tasks in the imported role
- Role is always imported, even if tasks are skipped
Dynamic Role Dependencies with include_role
For more flexibility, you can use include_role to dynamically include roles:
# roles/webapp/tasks/main.yml
---
- name: Check if database is needed
  stat:
    path: /etc/app/db.flag
  register: db_flag
- name: Include database role
  include_role:
    name: database
  vars:
    database_name: app_production
  when: db_flag.stat.exists
- name: Setup application configuration
  template:
    src: app.conf.j2
    dest: /etc/app.conf
When using include_role:
- Dependencies are processed at runtime
- You can use conditions to determine if a role should be included
- Tags only apply to the include statement itself, not tasks within the role
Practical Example: Building a Web Application Stack
Let's create a practical example of role dependencies for deploying a typical web application stack:
roles/
├── base/
│   ├── tasks/
│   │   └── main.yml
│   └── meta/
│       └── main.yml
├── nginx/
│   ├── tasks/
│   │   └── main.yml
│   └── meta/
│       └── main.yml
├── mysql/
│   ├── tasks/
│   │   └── main.yml
│   └── meta/
│       └── main.yml
└── webapp/
    ├── tasks/
    │   └── main.yml
    └── meta/
        └── main.yml
Here's how the dependencies might be defined:
# roles/base/meta/main.yml
---
dependencies: []  # No dependencies
# roles/nginx/meta/main.yml
---
dependencies:
  - role: base
# roles/mysql/meta/main.yml
---
dependencies:
  - role: base
# roles/webapp/meta/main.yml
---
dependencies:
  - role: nginx
    vars:
      nginx_port: 80
      nginx_server_name: "myapp.example.com"
  - role: mysql
    vars:
      mysql_databases:
        - name: myapp
          encoding: utf8
      mysql_users:
        - name: myapp
          password: "{{ vault_mysql_password }}"
          priv: "myapp.*:ALL"
Now you can simply include the webapp role in your playbook:
---
- hosts: webservers
  vars_files:
    - vault.yml
  roles:
    - webapp
Ansible will automatically handle executing all the required roles in the correct order:
- base(required by both- nginxand- mysql)
- nginx(required by- webapp)
- mysql(required by- webapp)
- webapp(the main role)
Visualizing Role Dependencies
Let's visualize the dependencies in our web application stack:
This diagram shows that the webapp role depends on both the nginx and mysql roles, which in turn both depend on the base role.
Handling Role Dependency Variables
When working with role dependencies, variables are passed down from the parent role to the dependent roles. This allows you to customize the behavior of dependent roles.
There are several ways variables can be defined and overridden:
- Variables defined in the varssection of a dependency declaration
- Variables defined in the role's defaults/main.ymlfile
- Variables defined in the playbook
The order of precedence (from lowest to highest) is:
- Role defaults
- Dependency variables
- Playbook variables
For example:
# roles/nginx/defaults/main.yml
---
nginx_port: 8080
nginx_server_name: localhost
# roles/webapp/meta/main.yml
---
dependencies:
  - role: nginx
    vars:
      nginx_port: 80  # Overrides the default
Then in your playbook:
---
- hosts: webservers
  vars:
    nginx_server_name: "production.example.com"  # Highest precedence
  roles:
    - webapp
The effective values will be:
- nginx_port: 80(from dependency declaration)
- nginx_server_name: "production.example.com"(from playbook)
Best Practices for Role Dependencies
1. Keep Dependencies Minimal
Only include dependencies that are strictly necessary for your role to function. Too many dependencies create complex dependency trees that are difficult to understand and maintain.
2. Use Role Defaults Wisely
Define sensible defaults in your roles so they can work independently, but allow dependent roles to override these defaults when needed.
# roles/nginx/defaults/main.yml
---
nginx_port: 80
nginx_worker_processes: "{{ ansible_processor_vcpus | default(2) }}"
3. Document Role Dependencies
Clearly document your role's dependencies in the README file, including:
- Required roles
- Required variables
- Optional variables
4. Avoid Circular Dependencies
Ensure your roles don't create circular dependencies (where A depends on B, and B depends on A). Ansible will detect and prevent circular dependencies, but it's best to avoid them in your design.
5. Consider Using Collections
For complex projects, consider organizing related roles into Ansible Collections, which provide a way to package and distribute roles, plugins, and modules together.
Troubleshooting Role Dependencies
Common Issues
- 
Role Not Found: Ensure the dependent role is in the correct location (either in your project's roles/directory or in a configured role path).
- 
Variable Conflicts: Variables with the same name in different roles can cause conflicts. Use role-specific prefixes for variable names to avoid this. 
- 
Dependency Order Issues: If roles must execute in a specific order, make sure your dependency chain reflects this. 
Debugging Dependencies
To see the effective role execution order:
ansible-playbook --list-tasks playbook.yml
To get detailed information about role processing:
ANSIBLE_DEBUG=true ansible-playbook -vvv playbook.yml
Summary
Ansible role dependencies are a powerful feature that allows you to:
- Build modular, reusable automation components
- Create layered infrastructure deployment
- Pass configuration from parent roles to dependent roles
- Organize complex automation into manageable pieces
By properly leveraging role dependencies, you can create maintainable, scalable automation that follows good software engineering practices like modularity and reusability.
Additional Resources
- Ansible Roles Documentation
- Ansible Galaxy - A hub for sharing and finding Ansible roles
- Ansible Collections - For organizing related roles and content
Exercises
- Create a base role with common tasks like system updates and firewall configuration
- Create an application role that depends on the base role
- Add variables to the application role that can be passed to the base role
- Create a playbook that uses the application role and overrides some variables
- Experiment with import_rolevs.include_roleto understand the differences in behavior
💡 Found a typo or mistake? Click "Edit this page" to suggest a correction. Your feedback is greatly appreciated!