Ansible run_once: Execute Tasks Once Across All Hosts (Complete Guide)
By Luca Berton · Published 2024-01-01 · Category: database-automation
How to use Ansible run_once to execute a task on a single host. Combine with delegate_to, serial, and throttle for controlled execution.
Ansible run_once: Execute Tasks Once Across All Hosts (Complete Guide)
The run_once directive tells Ansible to execute a task only once for the entire play, regardless of how many hosts are targeted. The task runs on the first host in the batch, and the result is applied to all hosts.
See also: Ansible run_once vs delegate_to vs serial: Control Task Execution Scope
Basic Usage
- hosts: webservers # 10 hosts
tasks:
- name: Run database migration (once only)
ansible.builtin.command: /opt/myapp/migrate.sh
run_once: true
# Runs on webserver01 only, skipped on webserver02-10
run_once with delegate_to
A common pattern: run a task once on a specific host, regardless of the play's target hosts.
- hosts: webservers
tasks:
- name: Create database backup on DB server
ansible.builtin.command: pg_dump myapp > /backup/pre-deploy.sql
run_once: true
delegate_to: db01
- name: Deploy application
ansible.builtin.copy:
src: myapp.tar.gz
dest: /opt/myapp/
# Runs on ALL webservers
- name: Run database migration
ansible.builtin.command: /opt/myapp/migrate.sh
run_once: true
delegate_to: db01
- name: Clear CDN cache
ansible.builtin.uri:
url: https://api.cdn.example.com/purge
method: POST
headers:
Authorization: "Bearer {{ vault_cdn_token }}"
run_once: true
delegate_to: localhost
See also: Ansible run_once: Execute Task on Single Host (Complete Guide)
run_once with register
When run_once is combined with register, the variable is available on all hosts:
- hosts: webservers
tasks:
- name: Get latest release version
ansible.builtin.uri:
url: https://api.github.com/repos/myorg/myapp/releases/latest
return_content: true
register: release
run_once: true
delegate_to: localhost
- name: Deploy the release on all hosts
ansible.builtin.get_url:
url: "{{ (release.content | from_json).assets[0].browser_download_url }}"
dest: /opt/myapp/release.tar.gz
# release variable is available on ALL hosts
run_once with set_fact
- hosts: all
tasks:
- name: Generate deployment ID
ansible.builtin.set_fact:
deploy_id: "{{ lookup('ansible.builtin.pipe', 'date +%Y%m%d%H%M%S') }}-{{ 999 | random }}"
run_once: true
- name: Show deploy ID (same on all hosts)
ansible.builtin.debug:
msg: "Deploy ID: {{ deploy_id }}"
See also: Ansible ignore_errors: Error Handling Best Practices (Complete Guide)
run_once vs serial vs throttle
serial — Rolling Updates
# Process hosts in batches
- hosts: webservers
serial: 2 # 2 hosts at a time
tasks:
- name: Deploy and restart
ansible.builtin.service:
name: myapp
state: restarted
throttle — Limit Concurrent Tasks
- hosts: webservers
tasks:
- name: Download artifact (limit concurrent downloads)
ansible.builtin.get_url:
url: https://artifacts.example.com/release.tar.gz
dest: /opt/release.tar.gz
throttle: 3 # Max 3 simultaneous downloads
Comparison
| Directive | What It Does | Use Case |
|-----------|-------------|----------|
| run_once: true | Run on first host only | DB migrations, cache purges, one-time setup |
| serial: N | Process N hosts per batch | Rolling deployments, zero-downtime updates |
| throttle: N | Max N concurrent executions | Rate-limited APIs, bandwidth constraints |
| delegate_to: host | Run on specific host | Cross-host operations |
Common Patterns
Zero-Downtime Deployment
- hosts: webservers
serial: "30%"
tasks:
- name: Remove from load balancer
ansible.builtin.uri:
url: "https://lb.example.com/api/deregister/{{ inventory_hostname }}"
method: POST
delegate_to: localhost
- name: Deploy new version
ansible.builtin.unarchive:
src: /opt/releases/myapp-latest.tar.gz
dest: /opt/myapp/
remote_src: true
- name: Restart application
ansible.builtin.systemd:
name: myapp
state: restarted
- name: Wait for health check
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:8080/health"
status_code: 200
retries: 10
delay: 3
- name: Re-register in load balancer
ansible.builtin.uri:
url: "https://lb.example.com/api/register/{{ inventory_hostname }}"
method: POST
delegate_to: localhost
Pre/Post Deployment Tasks
- hosts: webservers
tasks:
- name: "[PRE] Notify deployment start"
community.general.slack:
token: "{{ vault_slack_token }}"
channel: '#deployments'
msg: "Deploying v{{ app_version }} to {{ ansible_play_hosts | length }} hosts"
run_once: true
delegate_to: localhost
- name: Deploy application
ansible.builtin.copy:
src: "myapp-{{ app_version }}.tar.gz"
dest: /opt/myapp/
- name: "[POST] Notify deployment complete"
community.general.slack:
token: "{{ vault_slack_token }}"
channel: '#deployments'
msg: "✅ v{{ app_version }} deployed to all hosts"
run_once: true
delegate_to: localhost
FAQ
What does run_once do in Ansible?
run_once: true executes a task only once for the entire play, on the first host in the batch. The result (including registered variables) is shared with all other hosts. Use it for tasks that should happen once per deployment, like database migrations.
Does run_once set registered variables on all hosts?
Yes. When you combine run_once: true with register, the registered variable is available on all hosts in the play, not just the host where the task ran.
What is the difference between run_once and delegate_to?
run_once controls how many times a task runs (once). delegate_to controls where a task runs (specific host). They're often combined: run_once: true + delegate_to: db01 means "run this once, on db01."
Can I use run_once with serial?
Yes, but be careful. With serial, run_once executes once per batch, not once for the entire play. If you have 10 hosts with serial: 5, the task runs twice (once per batch of 5).
When should I use run_once vs running a separate play?
Use run_once when the task is part of the same workflow (e.g., migrate DB before deploying app). Use a separate play when the task targets different hosts or needs different become/connection settings.
Conclusion
run_once is essential for deployment workflows where certain tasks (migrations, cache purges, notifications) should only happen once. Combine with delegate_to for cross-host operations and serial for rolling updates.
Related Articles
• Ansible delegate_to: Run Tasks on Different Hosts • Ansible Handlers: Run Tasks on Change • Ansible Playbook: Complete GuideCategory: database-automation