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.

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.