The run_once: true directive tells Ansible to execute a task only on the first host in the current batch, skipping all other hosts. This is essential for tasks that should happen exactly once — like database migrations, API calls, or generating shared artifacts.
Basic Syntax
- name: Run database migration
ansible.builtin.command: /opt/app/migrate.sh
run_once: true
This runs on the first host in the play's host list and skips the rest.
Common Use Cases
Database Migrations
- name: Run database schema migration
ansible.builtin.command:
cmd: flask db upgrade
chdir: /opt/webapp
run_once: true
delegate_to: "{{ groups['dbservers'][0] }}"
Download a Shared Artifact
- name: Download application package
ansible.builtin.get_url:
url: "https://releases.example.com/app-{{ version }}.tar.gz"
dest: /tmp/app-{{ version }}.tar.gz
run_once: true
delegate_to: localhost
- name: Distribute to all servers
ansible.builtin.copy:
src: /tmp/app-{{ version }}.tar.gz
dest: /opt/app/
Send a Notification
- name: Notify Slack about deployment
ansible.builtin.uri:
url: "{{ slack_webhook }}"
method: POST
body_format: json
body:
text: "Deployment of {{ app_version }} started on {{ groups['webservers'] | length }} servers"
run_once: true
delegate_to: localhost
Generate Shared Config
- name: Generate cluster config
ansible.builtin.template:
src: cluster.conf.j2
dest: /tmp/cluster.conf
run_once: true
register: cluster_config
run_once with set_fact
Variables set with set_fact + run_once are applied to all hosts:
- name: Get cluster version
ansible.builtin.command: cluster-ctl version
register: version_output
run_once: true
- name: Store version for all hosts
ansible.builtin.set_fact:
cluster_version: "{{ version_output.stdout }}"
run_once: true
# This fact is now available on ALL hosts in the play
⚠️ run_once with strategy: free
When using strategy: free, Ansible runs tasks as fast as possible without synchronizing hosts. This means run_once may behave unpredictably — the "first host" might not be deterministic.
# WARNING: may run on different hosts each time
- hosts: all
strategy: free
tasks:
- name: This might not work as expected with free strategy
ansible.builtin.debug:
msg: "Am I the only one?"
run_once: true
Fix: Use delegate_to to specify exactly which host runs the task:
- name: Always run on the first webserver
ansible.builtin.command: /opt/app/migrate.sh
run_once: true
delegate_to: "{{ groups['webservers'][0] }}"
run_once vs when: inventory_hostname == ...
# Using run_once (simpler)
- name: Do something once
ansible.builtin.command: do_thing
run_once: true
# Using when (more explicit)
- name: Do something once
ansible.builtin.command: do_thing
when: inventory_hostname == groups['all'][0]
Both work, but run_once is cleaner and the standard approach.
Related Articles
- Ansible throttle — Control task concurrency
- Ansible Dry Run — Test playbooks safely
- Ansible Error Handling — Handle failures
- Ansible set_fact — Dynamic variables
Conclusion
Use run_once: true for any task that should execute exactly once per play — database migrations, notifications, shared artifact downloads. Always combine with delegate_to when using strategy: free to ensure deterministic behavior.