Ansible add_host — Create Hosts Dynamically During Play
Introduction
ansible.builtin.add_host adds hosts to the in-memory inventory during playbook execution. This is essential when you provision infrastructure first (cloud instances, containers, VMs) and then need to configure it in the same playbook run. Without add_host, you'd need separate inventory files and multiple playbook runs.
Basic Usage
---
- name: Provision and configure
hosts: localhost
gather_facts: false
tasks:
- name: Add a host dynamically
ansible.builtin.add_host:
name: webserver01
ansible_host: 192.168.1.100
ansible_user: ubuntu
ansible_ssh_private_key_file: ~/.ssh/deploy_key
groups:
- webservers
- production
- name: Configure the new host
hosts: webservers
tasks:
- name: Install Nginx
ansible.builtin.package:
name: nginx
state: present
Cloud Provisioning Pattern
- name: Provision EC2 instances
hosts: localhost
gather_facts: false
tasks:
- name: Launch instances
amazon.aws.ec2_instance:
name: "web-{{ item }}"
instance_type: t3.micro
image_id: ami-0abcdef1234567890
key_name: deploy-key
state: running
wait: true
loop: [1, 2, 3]
register: ec2_results
- name: Add instances to inventory
ansible.builtin.add_host:
name: "web-{{ item.item }}"
ansible_host: "{{ item.instances[0].public_ip_address }}"
ansible_user: ec2-user
ansible_ssh_private_key_file: ~/.ssh/deploy-key.pem
groups:
- webservers
- "{{ 'primary' if item.item == 1 else 'replicas' }}"
instance_id: "{{ item.instances[0].instance_id }}"
loop: "{{ ec2_results.results }}"
- name: Wait for SSH
ansible.builtin.wait_for:
host: "{{ item.instances[0].public_ip_address }}"
port: 22
delay: 10
timeout: 300
loop: "{{ ec2_results.results }}"
- name: Configure web servers
hosts: webservers
become: true
tasks:
- name: Install packages
ansible.builtin.package:
name:
- nginx
- python3
state: present
- name: Configure primary
hosts: primary
tasks:
- name: Set up as primary
ansible.builtin.debug:
msg: "Configuring {{ inventory_hostname }} ({{ instance_id }}) as primary"
Docker Container Pattern
- name: Create and configure containers
hosts: localhost
gather_facts: false
tasks:
- name: Start containers
community.docker.docker_container:
name: "app-{{ item }}"
image: ubuntu:22.04
command: sleep infinity
state: started
loop: [1, 2, 3]
- name: Add containers to inventory
ansible.builtin.add_host:
name: "app-{{ item }}"
ansible_connection: community.docker.docker
groups: containers
loop: [1, 2, 3]
- name: Configure containers
hosts: containers
gather_facts: false
tasks:
- name: Install Python
ansible.builtin.raw: apt-get update && apt-get install -y python3
Setting Host Variables
- name: Add host with custom variables
ansible.builtin.add_host:
name: db-primary
ansible_host: 10.0.1.50
ansible_user: postgres
groups: databases
# Custom host variables (accessible as hostvars)
db_role: primary
db_port: 5432
db_name: myapp_production
backup_enabled: true
max_connections: 200
Multiple Groups
- name: Add host to multiple groups
ansible.builtin.add_host:
name: "{{ server_name }}"
ansible_host: "{{ server_ip }}"
groups:
- all_servers
- "{{ datacenter }}" # e.g., us-east-1
- "{{ server_role }}" # e.g., webservers
- "{{ environment }}" # e.g., production
- "{{ os_family | lower }}" # e.g., debian
Conditional Registration
- name: Discover services on network
ansible.builtin.command:
cmd: "nmap -sn 192.168.1.0/24 -oG -"
register: nmap_result
changed_when: false
delegate_to: localhost
- name: Add discovered hosts
ansible.builtin.add_host:
name: "host-{{ item.split()[1] }}"
ansible_host: "{{ item.split()[1] }}"
groups: discovered
loop: "{{ nmap_result.stdout_lines }}"
when: "'Up' in item"
add_host with run_once
# add_host always runs on localhost regardless of delegation
# Use run_once to avoid duplicates when targeting multiple hosts
- name: Register load balancer
ansible.builtin.add_host:
name: lb01
ansible_host: 10.0.0.1
groups: loadbalancers
run_once: true
changed_when: false # add_host always reports "changed"
Key Behaviors
| Behavior | Detail |
|---|---|
| Runs on | Controller (always, even with delegate_to) |
| Changed status | Always reports "changed" (use changed_when: false) |
| Scope | Current playbook run only (not persisted) |
| Duplicate names | Merges variables, adds to additional groups |
| Available when | Immediately — next play can target the host |
Troubleshooting
| Issue | Solution |
|---|---|
| Host unreachable in next play | Check ansible_host IP and SSH access; add wait_for for port 22 |
| Duplicate "changed" noise | Add changed_when: false |
| Host not in expected group | Group names are case-sensitive; check spelling |
| Variables not available | Access via hostvars[hostname].variable_name |
| SSH key rejected | Set ansible_ssh_private_key_file and ansible_user correctly |
Best Practices
- Always
wait_forSSH — cloud instances need time to boot - Set
changed_when: false—add_hostalways says "changed" - Use
run_once— avoid duplicate registrations in multi-host plays - Group strategically — assign roles, environments, and regions as groups
- Store instance IDs — as host variables for later operations (terminate, tag)
- Pair with
group_by— further organize dynamically added hosts
Conclusion
add_host is the bridge between provisioning and configuration in a single playbook run. Provision cloud instances, containers, or VMs in play 1, register them with add_host, then configure them in play 2. It keeps your automation in one file instead of splitting across inventory files and multiple runs.