When it comes to transferring files to a remote system with Ansible, the copy
and template
modules are great tools for the job. So many things can be done in Linux using simple files. Copying essential configuration files to remote servers is an excellent use case for those who are just starting their Ansible journey. This article discusses some best practices for choosing between the Ansible copy
and template
modules.
Before I begin, let me show you the directory structure that I am using for this tutorial so you can follow along:
$ tree
.
├── copy.yml
├── files
│ └── etc
│ └── motd
├── inventory.yml
├── templates
│ └── etc
│ └── keepalived
│ └── keepalived.conf.j2
└── template.yml
5 directories, 5 files
I am executing these playbooks against a single host, as seen in my inventory file below:
$ cat inventory.yml
nyc1-webserver-1.example.com
The copy module
Ansible's copy
module does exactly what you expect: It copies a file from the local host to a remote server. The copy
module is easy to use and requires little explanation:
---
- hosts: nyc1-webserver-1.example.com
gather_facts: no
tasks:
- name: Copy MOTD into place
copy:
dest: /etc/motd
src: etc/motd
owner: root
group: root
mode: 0644
After running my Ansible playbook with ansible-playbook -i inventory.yml copy.yml
, I have an updated motd
file on the remote system:
root@nyc1-webserver-1:~# cat /etc/motd
This is an MOTD added via Ansible
The copy
module is perfect for those files that are always consistent across all of your systems. Some examples include:
- Login banners and messages of the day (motd)
- Base application configuration files that never vary between systems, such as an SSH configuration file that you want to keep consistent
- A binary application file that you want to copy exactly from your local system to remote systems
If you're using source code management, such as Git, for your Ansible playbooks, ensure that any files handled by the copy
module are appropriate to put in source code. You should never use a tool like Git to store files with sensitive information, such as passwords or private data.
The template module
The template
module also copies a file to a remote server, but it allows you to use Jinja2 to render a template to a file dynamically. This enables you to use variables, such as Ansible facts, to customize a particular file for a specific server. That might sound confusing to the new Ansible user, so I'll break it down a bit through an example.
The playbook below installs and configures keepalived
. If you've read my previous series about Linux high availability, then you're already familiar with the purpose of keepalived
. If you're not, then don't worry about it. Just realize that the playbook below installs the keepalived
application and writes a needed configuration file:
---
- hosts: nyc1-webserver-1.example.com
gather_facts: yes
vars:
keepalived_priority: 100
keepalived_state: MASTER
keepalived_vip: 192.168.1.1
keepalived_vrid: 10
keepalived_vrrp_instance_name: "HA_10"
tasks:
- name: Install Keepalived
package:
name: keepalived
state: present
- name: Add Keepalived config file
template:
dest: /etc/keepalived/keepalived.conf
src: etc/keepalived/keepalived.conf.j2
notify: Restart Keepalived
handlers:
- name: Restart Keepalived
service:
name: keepalived
state: restarted
The source file is stored at templates/etc/keepalived/keepalived.conf.j2
. Ansible is smart enough to automatically search for templates in specific known locations, such as templates/
, so that you don't have to worry about typing the entire path. This path contains the keepalived
configuration file. The file uses the .j2 suffix so that you know it is a Jinja2 template.
Notice that Jinja2 syntax is used to reference both built-in Ansible facts (ansible_default_ipv4['interface']
) and variables (keepalived_state
) that are provided via the vars key in the playbook YAML:
vrrp_instance {{ keepalived_vrrp_instance_name }} {
interface {{ ansible_default_ipv4['interface'] }}
state {{ keepalived_state }}
virtual_router_id {{ keepalived_vrid }}
priority {{ keepalived_priority }}
virtual_ipaddress {
{{ keepalived_vip }} dev {{ ansible_default_ipv4['interface'] }} label {{ ansible_default_ipv4['interface'] }}:VIP
}
}
Once you run this playbook via ansible-playbook -i inventory.yml template.yml
, the destination file on the remote system will have a completed configuration file, with all variables filled in, at /etc/keepalived/keepalived.conf
:
root@nyc1-webserver-1:~# cat /etc/keepalived/keepalived.conf
vrrp_instance HA_10 {
interface ens3
state MASTER
virtual_router_id 10
priority 100
virtual_ipaddress {
192.168.1.1 dev ens3 label ens3:VIP
}
}
Jinja2 templates are a very powerful way to render customized files out to hosts (or groups of hosts, if you want to use group variables that apply to multiple hosts). This is just a basic example. Complex templates using loops and advanced logic are possible with Jinja2, as described in the documentation.
What to choose?
So when would you use the template
module instead of the copy
module? Personally, I find that I use the template
module most of the time. There's almost always something about a configuration file that needs to be customized on a per-host basis. Some concrete examples include:
- Application configuration files with dynamic content, such as the NGINX server block that I discussed in my previous article about Ansible for web servers.
- Configurations that might need to be rendered an arbitrary number of times based on host or group variables. For example, you might dynamically configure multiple Filebeat settings based on log file paths that are passed in as variables. Not all servers have the same log file paths, so templates are the ideal solution for per-host customization.
- Files with sensitive data in them that shouldn't be stored in source control. You can even use Ansible Vault to store the secret and dynamically render it with Jinja2.
Using the template
module is a great way to enable reusability in your playbooks. Your playbooks are generalized because they don't contain host-specific information, and so they are portable between different hosts, projects, or even companies. Remember: Always try to write code that can be reused!
Final thoughts
Before I leave you, I'd like to cover two aspects of my playbooks that you might have noticed. First, I use a full-path directory structure for the source of files and templates (e.g., templates/etc/keepalived/keepalived.conf.j2
) instead of just throwing all files into the root directory (e.g., templates/keepalived.conf.j2
). This is a great habit to form. While this may grow your directory tree, it makes the destination of a file or template immediately obvious without actually reading the playbook code.
You probably also noticed that the syntax is nearly identical for the copy
and template
modules. This makes it easy to swap out one for the other as your needs change. You might start by copying over a static application configuration to all of the servers in your environment. As you continue to build your automation capabilities, you might find that certain parts of that configuration should be customized on a per-host basis. You can easily substitute the copy key for the template key in your playbook and benefit from the flexibility offered by the Jinja2 template language.
The adage that "everything is a file in Linux" generally holds true, and this makes file management one of the most critical tasks that you can automate with tools like Ansible. In this article, you learned the basics of the copy
and template
modules, including some use cases for each. Equipped with this information, you can begin to write powerful and flexible Ansible playbooks for file management in your environment.
[ Need more on Ansible? Take a free technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]
About the author
Anthony Critelli is a Linux systems engineer with interests in automation, containerization, tracing, and performance. He started his professional career as a network engineer and eventually made the switch to the Linux systems side of IT. He holds a B.S. and an M.S. from the Rochester Institute of Technology.
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech
Products
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud services
- See all products
Tools
- Training and certification
- My account
- Customer support
- Developer resources
- Find a partner
- Red Hat Ecosystem Catalog
- Red Hat value calculator
- Documentation
Try, buy, & sell
Communicate
About Red Hat
We’re the world’s leading provider of enterprise open source solutions—including Linux, cloud, container, and Kubernetes. We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.
Select a language
Red Hat legal and privacy links
- About Red Hat
- Jobs
- Events
- Locations
- Contact Red Hat
- Red Hat Blog
- Diversity, equity, and inclusion
- Cool Stuff Store
- Red Hat Summit