Introduction
ansible-pull inverts Ansible's default push model — instead of a controller pushing playbooks to remote hosts, each host pulls a playbook repository from Git and runs it locally. This scales to thousands of hosts without a central controller bottleneck, and it's ideal for self-configuring nodes, edge devices, and auto-scaling cloud instances.
Push vs Pull
Push (ansible-playbook) | Pull (ansible-pull) | |
|---|---|---|
| Direction | Controller → targets | Target pulls from Git |
| Central controller | Required | Not required |
| SSH access | Controller needs SSH to all targets | Not needed |
| Scaling | Controller is bottleneck | Each host runs independently |
| Timing | On-demand from controller | Cron schedule or on-demand |
| Network | Controller must reach all hosts | Hosts must reach Git repo |
| Use case | Ad-hoc, orchestration, workflows | Self-configuration, edge, auto-scaling |
Quick Start
# On the target host, run a playbook from a Git repo
ansible-pull -U https://github.com/myorg/ansible-config.git -i localhost, site.yml
This:
- Clones the Git repository to a local directory
- Runs
site.ymlonlocalhost - Cleans up after execution
Command Reference
ansible-pull [options] [playbook.yml]
| Flag | Description |
|---|---|
-U URL | Git repository URL (required) |
-C BRANCH | Git branch, tag, or commit (default: HEAD) |
-d DIR | Directory to clone into (default: ~/.ansible/pull/<hostname>) |
-i INVENTORY | Inventory (use localhost, for local) |
-e VARS | Extra variables |
--vault-password-file | Vault password file |
--accept-host-key | Accept SSH host key for Git |
-f | Force checkout (discard local changes) |
--full | Full clone instead of shallow |
-o | Only run if repository has changed |
--purge | Purge checkout after run |
-s SECONDS | Sleep random seconds before running |
Example with All Common Options
ansible-pull \
-U git@github.com:myorg/ansible-config.git \
-C main \
-d /opt/ansible-pull \
-i localhost, \
-e "env=production node_role=webserver" \
--vault-password-file /etc/ansible/.vault_pass \
-o \
-s 60 \
local.yml
Repository Structure
ansible-config/
├── local.yml # Default playbook (ansible-pull looks for this)
├── site.yml # Alternative playbook name
├── requirements.yml # Collection/role dependencies
├── ansible.cfg # Configuration
├── group_vars/
│ └── all.yml
├── host_vars/
├── roles/
│ ├── common/
│ ├── webserver/
│ └── monitoring/
└── files/
local.yml (Default Playbook)
---
- name: Configure this host
hosts: localhost
connection: local
become: true
roles:
- common
- "{{ node_role | default('common') }}"
ansible-pull automatically looks for local.yml if no playbook is specified.
Set Up Cron Schedule
Manual Cron Entry
# Run every 30 minutes with random delay
echo "*/30 * * * * root /usr/bin/ansible-pull -U https://github.com/myorg/ansible-config.git -o -s 300 >> /var/log/ansible-pull.log 2>&1" \
> /etc/cron.d/ansible-pull
Bootstrap with ansible-pull Itself
ansible-pull has a built-in way to install its own cron job:
# First run: bootstraps and installs cron
ansible-pull -U https://github.com/myorg/ansible-config.git -i localhost, bootstrap.yml
# bootstrap.yml
---
- name: Bootstrap ansible-pull
hosts: localhost
connection: local
become: true
tasks:
- name: Install Ansible
ansible.builtin.apt:
name: ansible
state: present
update_cache: true
- name: Install ansible-pull cron job
ansible.builtin.cron:
name: "ansible-pull"
minute: "*/30"
job: >
/usr/bin/ansible-pull
-U https://github.com/myorg/ansible-config.git
-C main
-o
-s 300
>> /var/log/ansible-pull.log 2>&1
user: root
- name: Set up log rotation
ansible.builtin.copy:
dest: /etc/logrotate.d/ansible-pull
content: |
/var/log/ansible-pull.log {
weekly
rotate 4
compress
missingok
notifempty
}
Using Ansible to Deploy Pull Mode
---
- name: Convert hosts to pull mode
hosts: all
become: true
vars:
pull_repo: "https://github.com/myorg/ansible-config.git"
pull_branch: main
pull_interval: 30
tasks:
- name: Install Ansible
ansible.builtin.package:
name: ansible
state: present
- name: Create pull directory
ansible.builtin.file:
path: /opt/ansible-pull
state: directory
mode: '0750'
owner: root
- name: Deploy vault password file
ansible.builtin.copy:
content: "{{ vault_pull_password }}"
dest: /etc/ansible/.vault_pass
mode: '0400'
owner: root
no_log: true
- name: Install ansible-pull cron
ansible.builtin.cron:
name: ansible-pull
minute: "*/{{ pull_interval }}"
job: >
/usr/bin/ansible-pull
-U {{ pull_repo }}
-C {{ pull_branch }}
-d /opt/ansible-pull
-i localhost,
--vault-password-file /etc/ansible/.vault_pass
-o -s 300
>> /var/log/ansible-pull.log 2>&1
user: root
Use Cases
Auto-Scaling Cloud Instances
Include ansible-pull in your cloud-init or user data:
# cloud-init user data
#cloud-config
packages:
- ansible
runcmd:
- ansible-pull -U https://github.com/myorg/ansible-config.git -C main -i localhost, -e "node_role=webserver env=production"
Edge Devices / IoT
Devices in remote locations pull their config on schedule — no inbound SSH needed:
# local.yml for edge devices
---
- name: Configure edge device
hosts: localhost
connection: local
become: true
tasks:
- name: Ensure monitoring agent
ansible.builtin.package:
name: node-exporter
state: present
- name: Update application config
ansible.builtin.template:
src: app-config.yml.j2
dest: /opt/edge-app/config.yml
notify: restart edge-app
Laptop/Workstation Configuration
# Developers run this to configure their workstation
ansible-pull -U https://github.com/myorg/workstation-config.git -K
The -o Flag (Only If Changed)
# Only run playbook if the Git repo has new commits
ansible-pull -U https://github.com/myorg/config.git -o
This does a git fetch and compares the local HEAD with the remote. If nothing changed, it exits without running. Critical for cron jobs — avoids unnecessary playbook runs.
Troubleshooting
"Host key verification failed"
# Accept host key automatically
ansible-pull -U git@github.com:myorg/config.git --accept-host-key
Or use HTTPS instead of SSH for the Git URL.
Playbook Not Found
ansible-pull looks for local.yml by default. Specify explicitly:
ansible-pull -U https://github.com/myorg/config.git site.yml
Checking Pull Logs
# View recent runs
tail -100 /var/log/ansible-pull.log
# Check if cron is running
grep ansible-pull /var/log/syslog
Best Practices
- Use
-o— only run when repo changes to save resources - Use
-swith cron — random sleep prevents all hosts hitting Git at once - Use
local.yml— conventional name, auto-detected - HTTPS over SSH for Git — simpler auth, no SSH key management
- Log rotation — ansible-pull logs grow fast on cron
- Vault password file — deploy encrypted,
mode: 0400 - Tag releases — use
-C v1.2.3for stable config versions - Combine push + pull — use push for orchestration, pull for baseline config
Related Articles
Conclusion
ansible-pull scales Ansible to thousands of hosts by inverting the push model — each host clones a Git repo and runs its own playbook locally. Set up a cron job with -o (only if changed) and -s (random sleep) for efficient, decentralized configuration management. It's the right model for auto-scaling cloud instances, edge devices, and any environment where centralized SSH access isn't practical.