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

ParameterTypeRequiredDescription
destpathYesRemote absolute path
srcpathNo*Local file or directory path
contentstringNo*Inline content to write (instead of src)
modestringNoFile permissions (e.g., '0644')
ownerstringNoFile owner
groupstringNoFile group
backupboolNoCreate backup before overwriting
validatestringNoValidation command (%s = temp file)
remote_srcboolNoCopy from remote to remote (not local)
forceboolNoReplace even if file exists (default: yes)
directory_modestringNoPermissions for created directories
followboolNoFollow symlinks on remote
checksumstringNoExpected 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 of configs/. 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

TaskModule
Copy static file local → remotecopy
Copy file with Jinja2 variablestemplate
Copy file remote → localfetch
Download from URL → remoteget_url
Sync directoriessynchronize (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

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.