Introduction
The ansible.builtin.copy module — commonly called the ansible copy module — transfers files from the Ansible controller to remote hosts. It handles permissions, ownership, SELinux context, backup, and validation in a single task — making it the standard way to deploy configuration files, scripts, and static content.
For the reverse (remote → local), use the fetch module. For Windows targets, use win_copy.
Module Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
dest | path | Yes | Remote absolute path |
src | path | No* | Local file or directory path |
content | string | No* | Inline content to write (instead of src) |
mode | string | No | File permissions (e.g., '0644') |
owner | string | No | File owner |
group | string | No | File group |
backup | bool | No | Create backup before overwriting |
validate | string | No | Validation command (%s = temp file) |
remote_src | bool | No | Copy from remote to remote (not local) |
force | bool | No | Replace even if file exists (default: yes) |
directory_mode | string | No | Permissions for created directories |
follow | bool | No | Follow symlinks on remote |
checksum | string | No | Expected SHA1 checksum |
*Either src or content is required when state: present.
Basic Examples
Copy a File
- name: Copy config file
ansible.builtin.copy:
src: report.txt
dest: /home/devops/report.txt
owner: devops
group: devops
mode: '0644'
Copy with Inline Content
- name: Create config from content
ansible.builtin.copy:
content: |
# Application config
database_host=db.example.com
database_port=5432
log_level=info
dest: /etc/myapp/config.conf
owner: myapp
group: myapp
mode: '0640'
Copy a Directory
- name: Copy entire config directory
ansible.builtin.copy:
src: configs/
dest: /etc/myapp/
owner: root
group: root
mode: '0644'
directory_mode: '0755'
Trailing slash matters:
src: configs/copies the contents ofconfigs/. Without the slash (src: configs), it copies the directory itself.
Practical Patterns
Deploy with Backup
- name: Deploy nginx config with backup
ansible.builtin.copy:
src: nginx.conf
dest: /etc/nginx/nginx.conf
owner: root
group: root
mode: '0644'
backup: true
notify: reload nginx
Creates a timestamped backup like nginx.conf.2024-01-15@10:30:00~ before overwriting.
Copy with Validation
- name: Deploy sudoers file (validated)
ansible.builtin.copy:
src: sudoers_deploy
dest: /etc/sudoers.d/deploy
owner: root
group: root
mode: '0440'
validate: 'visudo -cf %s'
The %s is replaced with the temp file path. If validation fails, the file is not deployed.
Deploy SSH Key
- name: Deploy authorized key
ansible.builtin.copy:
content: "{{ lookup('file', 'files/deploy_key.pub') }}"
dest: /home/deploy/.ssh/authorized_keys
owner: deploy
group: deploy
mode: '0600'
Deploy Script with Execute Permission
- name: Deploy health check script
ansible.builtin.copy:
src: healthcheck.sh
dest: /usr/local/bin/healthcheck.sh
owner: root
group: root
mode: '0755'
Deploy Multiple Files with Loop
- name: Deploy configuration files
ansible.builtin.copy:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
owner: root
group: root
mode: "{{ item.mode | default('0644') }}"
loop:
- { src: app.conf, dest: /etc/myapp/app.conf }
- { src: logging.conf, dest: /etc/myapp/logging.conf }
- { src: start.sh, dest: /usr/local/bin/start.sh, mode: '0755' }
Conditional Copy
- name: Copy development config
ansible.builtin.copy:
src: "config.{{ env }}.conf"
dest: /etc/myapp/config.conf
when: env is defined
Remote-to-Remote Copy
- name: Copy file on remote host
ansible.builtin.copy:
src: /tmp/backup.tar.gz
dest: /opt/backups/backup.tar.gz
remote_src: true
copy vs template vs fetch
| Task | Module |
|---|---|
| Copy static file local → remote | copy |
| Copy file with Jinja2 variables | template |
| Copy file remote → local | fetch |
| Download from URL → remote | get_url |
| Sync directories | synchronize (rsync) |
When to Use template Instead
# Use copy for static files
- ansible.builtin.copy:
src: motd.txt
dest: /etc/motd
# Use template when you need variables
- ansible.builtin.template:
src: motd.j2
dest: /etc/motd
Performance Tips
Large Files
The copy module transfers files via SSH, which is slower than rsync for large files:
# For large files or many files, use synchronize
- name: Sync large directory
ansible.posix.synchronize:
src: /local/big-directory/
dest: /remote/big-directory/
Avoid Unnecessary Copies
# force: false only copies if dest doesn't exist
- name: Copy only if file is missing
ansible.builtin.copy:
src: initial-config.conf
dest: /etc/myapp/config.conf
force: false
Common Mistakes
Missing Quotes on Mode
# WRONG — YAML interprets 0644 as integer 420
mode: 0644
# CORRECT — quote the mode string
mode: '0644'
src vs content
# WRONG — can't use both
- copy:
src: file.txt
content: "hello"
dest: /tmp/file.txt
# CORRECT — use one or the other
- copy:
src: file.txt
dest: /tmp/file.txt
Related Articles
- Copy Files from Remote Hosts: fetch Module
- Copy Files to Windows: win_copy
- Create Templates: template Module
- Edit Single Lines: lineinfile
- Edit Multi-Line Blocks: blockinfile
- Change File Permissions: file Module
Conclusion
The copy module is Ansible's workhorse for file deployment. Use src for files, content for inline text, backup: true for safety, validate for critical configs, and always quote mode values. For files with variables, switch to template. For large directories, use synchronize. For remote-to-local transfers, use fetch.