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)
DirectionController → targetsTarget pulls from Git
Central controllerRequiredNot required
SSH accessController needs SSH to all targetsNot needed
ScalingController is bottleneckEach host runs independently
TimingOn-demand from controllerCron schedule or on-demand
NetworkController must reach all hostsHosts must reach Git repo
Use caseAd-hoc, orchestration, workflowsSelf-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:

  1. Clones the Git repository to a local directory
  2. Runs site.yml on localhost
  3. Cleans up after execution

Command Reference

ansible-pull [options] [playbook.yml]
FlagDescription
-U URLGit repository URL (required)
-C BRANCHGit branch, tag, or commit (default: HEAD)
-d DIRDirectory to clone into (default: ~/.ansible/pull/<hostname>)
-i INVENTORYInventory (use localhost, for local)
-e VARSExtra variables
--vault-password-fileVault password file
--accept-host-keyAccept SSH host key for Git
-fForce checkout (discard local changes)
--fullFull clone instead of shallow
-oOnly run if repository has changed
--purgePurge checkout after run
-s SECONDSSleep 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

  1. Use -o — only run when repo changes to save resources
  2. Use -s with cron — random sleep prevents all hosts hitting Git at once
  3. Use local.yml — conventional name, auto-detected
  4. HTTPS over SSH for Git — simpler auth, no SSH key management
  5. Log rotation — ansible-pull logs grow fast on cron
  6. Vault password file — deploy encrypted, mode: 0400
  7. Tag releases — use -C v1.2.3 for stable config versions
  8. Combine push + pull — use push for orchestration, pull for baseline config

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.