Date: March 4, 2025 /  Author: Ralf Eichinger

How to structure Ansible directory layout?

An organized directory structure can make a big difference in maintaining your Ansible projects.

Ansible directory layout

Here’s a commonly used layout (under an example directory ansible, you name it like you prefer …) that balances modularity, reusability, and readability:

ansible
├── ansible.cfg
├── inventories
│   ├── development
│   │   ├── inventory.yml
|   |   ├── group_vars
|   |   └── host_vars
│   ├── production
│   │   ├── inventory.yml
|   |   ├── group_vars
|   |   │   ├── all
|   |   │   ├── webservers
|   |   │   └── dbservers
|   |   └── host_vars
|   |       └── hostname1
│   └── staging
│   │   ├── inventory.yml
|   |   ├── group_vars
|   |   └── host_vars
├── playbooks
│   ├── site.yml
│   ├── webservers.yml
│   └── dbservers.yml
├── roles
│   ├── common
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── common_template.j2
│   ├── webserver
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── webserver_template.j2
│   └── database
│       ├── tasks
│       │   └── main.yml
│       └── templates
│           └── db_template.j2
└── files
    ├── ...

Here’s a breakdown of each component:

  • ansible.cfg: The configuration file for Ansible.
  • inventories: Directory for different environment inventories (e.g. development, staging, production).
  • group_vars: Directory for variables that apply to groups of hosts.
  • host_vars: Directory for variables that apply to individual hosts. Ansible automatically loads the variables from the host_vars directory for each host in your inventory.
  • playbooks: Directory for playbooks that define the tasks to be executed.
  • roles: Directory for roles, which are reusable and modular units that can contain tasks, handlers, files, templates, and variables.
  • templates: Each role can have its own templates directory to store Jinja templates. This directory will contain .j2 files that can be rendered with variables when the playbook runs.
  • files: Directory for files that need to be copied to remote hosts.

This structure helps to keep your files organized and makes it easier to manage and scale your infrastructure as code (IaC).

Configure groups and hosts in inventory

“Inventories organize managed nodes in centralized files that provide Ansible with system information and network locations. Using an inventory file, Ansible can manage a large number of hosts with a single command.”

Add groups and hosts

Let’s create an inventory file for all production hosts we want to manage using ansible. We prefer yml format over ini format:

$ nano inventories/production/inventory.yml
all:
  hosts:
    web1:
      ansible_host: 192.0.2.100
    web2:
      ansible_host: 192.0.2.110
  • First level: “all” is the group name (as we do not specify special groups, yet)
  • Third level: “web2” is an unique name describing the host
  • Fourth level: “ansible_host” variable: IP-address or fully qualified domain name (FQDN) of managed host

“Even if you do not define any groups in your inventory file, Ansible creates two default groups: all and ungrouped. The all group contains every host. The ungrouped group contains all hosts that don’t have another group aside from all. Every host will always belong to at least 2 groups (all and ungrouped or all and some other group).”

Move host variables to host_vars

To separate configuration of hosts even further, we can move host specific vars to a host specific file under host_vars directory.

Example: Move variable ansible_host for host web1 from inventories/production/inventory.yml to inventories/production/host_vars/web1.yml.

File inventories/production/host_vars/web1.yml:

ansible_host: 192.0.2.100

Do the same for host web2.

After moving all variables into specific host files, the final inventory just lists hosts in groups.

File inventories/production/inventory.yml:

all:
  hosts:
    web1:
    web2:

Use host variables in a task or template

In your playbook or role, you can reference the host variables directly in your tasks or templates. Ansible automatically loads the variables based on the hostname.

Example inventories/production/host_vars/web1.yml:

ansible_host: 192.0.2.100
app_port: 8080
app_env: production

Example Jinja Template playbooks/templates/app_config.j2:

server {
    listen {{ app_port }};
    server_name localhost;

    location / {
        proxy_pass http://localhost:{{ app_port }};
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Example task in a playbook:

- name: Deploy web application
  hosts: web1
  tasks:
    - name: Ensure application is running on the correct port
      template:
        src: templates/app_config.j2
        dest: /etc/myapp/config.conf
      notify: Restart app

    - name: Check application environment
      debug:
        msg: "The application environment is {{ app_env }}"

In this example, the task will ensure that the application uses the port and environment specified in the inventories/production/host_vars/web1.yml file. The debug task will output the application environment variable for verification.

Roles

For further abstraction you could implement roles.

Usage

With the directory layout in place we could do a ping to our production servers like this:

$ cd ansible
$ ansible -i inventories/production/inventory.yml all -m ping
web1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3.11"
    },
    "changed": false,
    "ping": "pong"
}
web2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/local/bin/python3.13"
    },
    "changed": false,
    "ping": "pong"
}

References:

 Tags:  topics devops

Previous
⏪ Deploy a Spring Boot Webapp to a VM with Ansible

Next
Build and Deploy an Angular Application to a NGINX Webserver ⏩