The Error
Your playbook hits a wall mid-run:
ERROR! conflicting action statements: copy, template
Or some variation depending on what's in your task:
ERROR! conflicting action statements: apt, yum
ERROR! conflicting action statements: file, copy
The module names differ, but the root cause never does: one task block is trying to run two action modules at once.
Why This Happens
Ansible enforces a strict rule โ one action module per task, full stop. When the parser encounters two module keys sitting at the same indentation level, it has no way to decide which one wins. So it doesn't guess. It stops.
Nine times out of ten this creeps in during copy-paste. You merge two task blocks and don't notice a duplicate key has snuck along for the ride:
- name: Deploy config file
copy:
src: files/app.conf
dest: /etc/app/app.conf
template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
Both copy and template are siblings here. Ansible raises conflicting action statements: copy, template and refuses to go any further.
Step-by-Step Fix
Step 1 โ Find the broken task
Ansible's error output is actually pretty helpful here. It names the conflicting modules and tells you exactly where to look:
ERROR! conflicting action statements: copy, template
The error appears to be in '/home/deploy/playbooks/site.yml': line 14, column 5
Open the file. Jump to line 14. The offending task will be right there.
Step 2 โ Pick the right module
Before deleting anything, decide which module actually belongs. Here's a quick cheat sheet for the most common collisions:
- copy vs template โ Static file with no variables? Use
copy. File has Jinja2 expressions like{{ db_host }}? Usetemplate. - apt vs yum โ Match the package manager to the target OS, or sidestep the whole debate with the generic
packagemodule. - file vs copy โ Managing permissions, ownership, or a symlink on an existing path? That's
file. Pushing a file from your control node to the remote? That'scopy.
Step 3 โ Split or trim
Option A: Keep one module, drop the other
# Before (broken)
- name: Deploy config file
copy:
src: files/app.conf
dest: /etc/app/app.conf
template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
# After (fixed) โ pick whichever you actually need
- name: Deploy config file
template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
owner: root
group: root
mode: '0644'
Option B: Both actions are genuinely needed โ split into two tasks
- name: Copy static base config
copy:
src: files/base.conf
dest: /etc/app/base.conf
mode: '0644'
- name: Render dynamic app config
template:
src: templates/app.conf.j2
dest: /etc/app/app.conf
mode: '0644'
Step 4 โ Hunt for hidden conflicts from YAML anchors
Sometimes the broken line isn't obvious. YAML anchors can expand a block that already carries an action key, burying the duplicate:
defaults: &defaults
copy:
src: files/default.conf
dest: /etc/app/default.conf
- name: Deploy config
<<: *defaults
template: # conflict โ 'copy' merged in from the anchor above
src: templates/app.conf.j2
dest: /etc/app/app.conf
Expand the anchor inline or restructure the tasks so the merge doesn't pull in a competing module key.
Verify the Fix
Don't run the full playbook blind. Start with a syntax check โ it costs nothing and catches problems before any host is touched:
ansible-playbook site.yml --check --syntax-check
Success looks like this โ just the playbook name, nothing else:
playbook: site.yml
Good. Now do a dry run against real hosts to see what would change:
ansible-playbook site.yml --check -v
Happy with the output? Run it for real:
ansible-playbook site.yml
Quick Reference: One Module Per Task
A valid task has exactly one action module. Everything else โ become, when, notify, register, loop, tags โ are task directives. They sit alongside the module and don't conflict with it:
- name: Descriptive name of what this task does
module_name: # exactly ONE action module
param1: value1
param2: value2
become: true # task directive โ fine to have alongside the module
when: some_condition
notify: restart app
Two action modules at the same level? Never. That's the only rule you need to remember.
Preventing This Going Forward
- Run
yamllintbefore every playbook run โ it flags duplicate keys before Ansible even sees the file. ansible-linthas a dedicated rule for conflicting module usage. Worth adding to your CI pipeline.- After merging role tasks or copy-pasting between playbooks, do a quick scan: each task block should have exactly one module key.
- The Ansible extension for VS Code highlights conflicting keys as you type, which catches the problem at the source.

