How to Use Ansible Template Module to Create Files Only When Destination Doesn’t Exist


2 views

When managing configuration files with Ansible, a common requirement is to create files from templates but prevent overwriting existing files. The default behavior of Ansible's template module will overwrite the destination file every time the playbook runs, which might not be desirable for certain configuration files.

The template module fundamentally works by:

- Processing Jinja2 templates
- Copying the rendered content to destination
- Overwriting by default (similar to copy module)
- Setting proper file permissions

The most straightforward solution is to use the creates parameter which makes the task idempotent:

tasks:
    - template:
        src: somefile.j2
        dest: /etc/somefile.conf
        creates: /etc/somefile.conf

For more complex scenarios, you might want to combine with the stat module:

tasks:
    - name: Check if file exists
      stat:
        path: /etc/somefile.conf
      register: file_status
    
    - name: Apply template only if file doesn't exist
      template:
        src: somefile.j2
        dest: /etc/somefile.conf
      when: not file_status.stat.exists

For production environments, consider these additional safeguards:

tasks:
    - name: Safe template application
      template:
        src: somefile.j2
        dest: /etc/somefile.conf
        backup: yes
        validate: '/usr/sbin/validator %s'
      when: not file_status.stat.exists

The creates approach is more efficient than using stat + conditional, as it:

  • Reduces module executions
  • Minimizes playbook complexity
  • Maintains better readability

When automating server configurations with Ansible, we often face situations where we want to create configuration files from templates, but only if they don't already exist on the target system. This is particularly important when:

  • Preserving manual configuration changes
  • Maintaining idempotency in playbooks
  • Avoiding overwriting user-modified files

Ansible provides several approaches to handle this scenario:


# Method 1: Using 'creates' parameter
- template:
    src: somefile.j2
    dest: /etc/somefile.conf
    creates: /etc/somefile.conf  # Won't run if file exists

# Method 2: Combining with stat module
- stat:
    path: /etc/somefile.conf
  register: config_status

- template:
    src: somefile.j2
    dest: /etc/somefile.conf
  when: not config_status.stat.exists

For more complex scenarios, consider this pattern:


- name: Check if config file exists
  stat:
    path: "{{ config_file_path }}"
  register: config_check

- name: Create initial config from template
  template:
    src: "templates/{{ config_template }}.j2"
    dest: "{{ config_file_path }}"
    owner: root
    group: root
    mode: '0644'
  when: not config_check.stat.exists
  tags:
    - initial-config

For production environments, you might want to add these safeguards:


- name: Verify template deployment
  assert:
    that:
      - config_check.stat.exists and config_check.stat.size > 0
    msg: "Configuration file was not created properly"
  when: not config_check.stat.exists

When dealing with multiple files, this optimized approach reduces playbook runtime:


- name: Gather existing config files
  find:
    paths: /etc
    patterns: "*.conf"
  register: existing_configs

- name: Deploy missing config templates
  template:
    src: "templates/{{ item }}.j2"
    dest: "/etc/{{ item }}.conf"
  loop: "{{ config_templates }}"
  when: "'{{ item }}.conf' not in existing_configs.files|map(attribute='path')|list"