AnsiblePilot — Master Ansible Automation

AnsiblePilot is the leading resource for learning Ansible automation, DevOps, and infrastructure as code. Browse over 1,400 tutorials covering Ansible modules, playbooks, roles, collections, and real-world examples. Whether you are a beginner or an experienced engineer, our step-by-step guides help you automate Linux, Windows, cloud, containers, and network infrastructure.

Popular Topics

About Luca Berton

Luca Berton is an Ansible automation expert, author of 8 Ansible books published by Apress and Leanpub including "Ansible for VMware by Examples" and "Ansible for Kubernetes by Example", and creator of the Ansible Pilot YouTube channel. He shares practical automation knowledge through tutorials, books, and video courses to help IT professionals and DevOps engineers master infrastructure automation.

Ansible unarchive Module: Extract tar.gz, zip Archives on Remote Hosts

By Luca Berton · Published 2024-01-01 · Category: installation

How to extract archives with Ansible unarchive module (ansible.builtin.unarchive). Extract tar.gz, zip, bz2 from local or remote URLs.

Ansible unarchive Module: Extract tar.gz, zip Archives on Remote Hosts

The ansible.builtin.unarchive module extracts compressed archives (tar, tar.gz, tar.bz2, tar.xz, zip) on remote hosts. It copies from the Ansible controller or downloads from a URL, extracts to a target directory, and handles permissions and ownership — all in a single task.

How to Extract an Archive in Ansible?

I'm going to show you a live Playbook and some simple Ansible code. I'm Luca Berton and welcome to today's episode of Ansible Pilot.

The full name is ansible.builtin.unarchive, part of the builtin collection shipped with Ansible. It's stable, works across Linux distributions, and handles .zip files using unzip as well as .tar, .tar.gz, .tar.bz2, .tar.xz, and .tar.zst files using gtar.

For Windows targets, use the community.windows.win_unzip module instead.

See also: Ansible win_unzip Module: Extract ZIP Archives on Windows (Guide)

Basic Usage

Extract Local Archive to Remote Host

By default, src is a path on the Ansible controller. The archive is copied to the remote host, then extracted:

- name: Extract application archive
  ansible.builtin.unarchive:
    src: files/myapp-2.1.0.tar.gz
    dest: /opt/myapp/
    owner: appuser
    group: appuser
    mode: '0755'
  become: true

Extract Archive Already on Remote Host

When the archive is already on the remote host, set remote_src: true:

- name: Extract remote archive
  ansible.builtin.unarchive:
    src: /tmp/myapp-2.1.0.tar.gz
    dest: /opt/myapp/
    remote_src: true
  become: true

Download and Extract from URL

Set remote_src: true with an HTTP/HTTPS URL as src to download and extract in one step:

- name: Download and extract from URL
  ansible.builtin.unarchive:
    src: https://github.com/prometheus/prometheus/releases/download/v2.52.0/prometheus-2.52.0.linux-amd64.tar.gz
    dest: /opt/
    remote_src: true
  become: true

Parameters Reference

| Parameter | Type | Description | Default | |-----------|------|-------------|---------| | src | string | Archive path (local, remote, or URL) | Required | | dest | string | Target extraction directory | Required | | remote_src | boolean | Source is on remote host or URL | false | | owner | string | Set owner of extracted files | — | | group | string | Set group of extracted files | — | | mode | string | Set permissions of extracted files | — | | creates | string | Skip if this path exists (idempotency) | — | | exclude | list | Files/dirs to exclude from extraction | — | | include | list | Files/dirs to include in extraction | — | | keep_newer | boolean | Don't overwrite newer files on remote | false | | extra_opts | list | Extra command-line options for tar/unzip | — | | list_files | boolean | Return list of extracted files in result | false | | validate_certs | boolean | Validate SSL certs for HTTPS URLs | true |

See also: Ansible Delete File & Remove File: file Module absent State Guide

Idempotent Extraction

Using creates Parameter

The creates parameter is the recommended way to make extraction idempotent:

- name: Extract application (idempotent)
  ansible.builtin.unarchive:
    src: https://github.com/prometheus/prometheus/releases/download/v2.52.0/prometheus-2.52.0.linux-amd64.tar.gz
    dest: /opt/
    remote_src: true
    creates: /opt/prometheus-2.52.0.linux-amd64/prometheus

Using stat + when Conditional

For more control, check with stat before extracting:

- name: Check if already extracted
  ansible.builtin.stat:
    path: /opt/myapp/bin/myapp
  register: app_binary

- name: Extract application ansible.builtin.unarchive: src: files/myapp-{{ app_version }}.tar.gz dest: /opt/myapp/ when: not app_binary.stat.exists

Include and Exclude Files

Extract Only Specific Files

- name: Extract only config files
  ansible.builtin.unarchive:
    src: files/myapp.tar.gz
    dest: /opt/myapp/
    include:
      - 'myapp/config/*'
      - 'myapp/README.md'

Exclude Files from Extraction

- name: Extract without tests and docs
  ansible.builtin.unarchive:
    src: files/myapp.tar.gz
    dest: /opt/myapp/
    exclude:
      - 'myapp/tests/*'
      - 'myapp/docs/*'
      - '*.md'

See also: Ansible get_url Module Complete Reference: Checksums, Auth, Proxies & Retries

Strip Top-Level Directory

Many archives contain a top-level directory (e.g., myapp-2.1.0/). Use extra_opts to strip it:

- name: Extract without top-level directory
  ansible.builtin.unarchive:
    src: files/myapp-2.1.0.tar.gz
    dest: /opt/myapp/
    extra_opts:
      - --strip-components=1
  # Archive: myapp-2.1.0/bin/app → extracts as /opt/myapp/bin/app

Real-World Deployment Patterns

Deploy Application from GitHub Release

- name: Deploy Prometheus
  hosts: monitoring
  become: true
  vars:
    prometheus_version: "2.52.0"
    prometheus_dir: /opt/prometheus

tasks: - name: Create prometheus user ansible.builtin.user: name: prometheus system: true shell: /usr/sbin/nologin create_home: false

- name: Create directories ansible.builtin.file: path: "{{ item }}" state: directory owner: prometheus group: prometheus loop: - "{{ prometheus_dir }}" - /var/lib/prometheus

- name: Download and extract Prometheus ansible.builtin.unarchive: src: "https://github.com/prometheus/prometheus/releases/download/v{{ prometheus_version }}/prometheus-{{ prometheus_version }}.linux-amd64.tar.gz" dest: "{{ prometheus_dir }}" remote_src: true owner: prometheus group: prometheus extra_opts: - --strip-components=1 creates: "{{ prometheus_dir }}/prometheus" notify: restart prometheus

Blue-Green Deployment with Symlink

- name: Download release
  ansible.builtin.get_url:
    url: "https://releases.example.com/myapp-{{ app_version }}.tar.gz"
    dest: "/tmp/myapp-{{ app_version }}.tar.gz"
    checksum: "sha256:{{ release_checksum }}"

- name: Create release directory ansible.builtin.file: path: "/opt/releases/{{ app_version }}" state: directory owner: deploy

- name: Extract release ansible.builtin.unarchive: src: "/tmp/myapp-{{ app_version }}.tar.gz" dest: "/opt/releases/{{ app_version }}/" remote_src: true owner: deploy extra_opts: - --strip-components=1 creates: "/opt/releases/{{ app_version }}/bin/app"

- name: Update symlink to new release ansible.builtin.file: src: "/opt/releases/{{ app_version }}" dest: /opt/myapp/current state: link force: true notify: restart myapp

Extract Multiple Archives in a Loop

- name: Extract tool archives
  ansible.builtin.unarchive:
    src: "{{ item.url }}"
    dest: "{{ item.dest }}"
    remote_src: true
    creates: "{{ item.creates }}"
  loop:
    - url: https://example.com/tool1.tar.gz
      dest: /opt/tool1/
      creates: /opt/tool1/bin/tool1
    - url: https://example.com/tool2.zip
      dest: /opt/tool2/
      creates: /opt/tool2/tool2

Video Playbook Example

---
- name: unarchive module Playbook
  hosts: all
  become: false
  vars:
    myurl: "https://github.com/lucab85/ansible-pilot/archive/refs/heads/master.zip"
  tasks:
    - name: Ensure extractors are present
      ansible.builtin.yum:
        name:
          - unzip
          - tar
        state: present
      become: true

- name: Extract archive from URL ansible.builtin.unarchive: src: "{{ myurl }}" dest: "/home/devops/" remote_src: true validate_certs: true

code with ❤️ in GitHub

Windows: win_unzip

- name: Extract ZIP on Windows
  community.windows.win_unzip:
    src: C:\temp\app.zip
    dest: C:\Program Files\MyApp
    creates: C:\Program Files\MyApp\app.exe

Supported Archive Formats

| Format | Extension | Requirement | |--------|-----------|-------------| | tar | .tar | tar | | tar + gzip | .tar.gz, .tgz | tar + gzip | | tar + bzip2 | .tar.bz2 | tar + bzip2 | | tar + xz | .tar.xz | tar + xz | | tar + zstd | .tar.zst | tar + zstd | | zip | .zip | unzip + zipinfo |

> Note: The remote host needs the appropriate tools installed. tar and gzip are usually pre-installed on Linux. For zip archives, install unzip first.

FAQ

How do I extract a tar.gz file with Ansible?

Use the unarchive module with src pointing to the archive and dest to the target directory. For local archives: ansible.builtin.unarchive: src=files/app.tar.gz dest=/opt/app/. For archives on the remote host, add remote_src: true. For URLs, use remote_src: true with the URL as src.

How do I download and extract in one step?

Set remote_src: true and use an HTTP/HTTPS URL as src. Ansible downloads and extracts in a single task without intermediate steps.

How do I make unarchive idempotent?

Use the creates parameter pointing to a file that exists after extraction: creates: /opt/app/bin/myapp. If that file exists, the task is skipped entirely.

How do I strip the top-level directory from an archive?

Use extra_opts: ['--strip-components=1']. This removes the first directory level, so myapp-1.0/bin/app extracts as bin/app in the destination directory.

What is the difference between remote_src true and false?

With remote_src: false (default), src is a path on the Ansible controller — the archive is copied to the remote host then extracted. With remote_src: true, src is a path already on the remote host or a URL to download from.

Why does unarchive report "changed" every time?

The module checks file timestamps. Use creates to skip if already extracted, or use stat + when for more precise conditional extraction.

How do I extract a single .gz file (not tar.gz)?

Single gzip files aren't archives. Use the command module: command: gunzip /tmp/file.gz. The unarchive module only handles archive formats containing multiple files.

How do I extract password-protected archives?

Not supported natively by the unarchive module. Use command: unzip -P {{ password }} archive.zip -d /dest with no_log: true to prevent password leaking to logs.

"unzip not found" error?

Install unzip on the remote host first: ansible.builtin.package: name=unzip state=present. This is only needed for .zip files — tar archives don't require extra packages.

Conclusion

The ansible.builtin.unarchive module is essential for deploying applications from archives: • src + dest — Extract archive to target directory • remote_src: true — Archive is on remote host or a URL • creates: — Make extraction idempotent • extra_opts: [--strip-components=1] — Remove top-level directory • owner/group/mode — Set file ownership and permissions • include/exclude — Extract specific files only • Windows: Use community.windows.win_unzip instead

Related Articles

Ansible copy Module: Copy Files to Remote HostsAnsible get_url Module: Download FilesAnsible file Module: Create Files and DirectoriesAnsible Become Guide: Privilege Escalation

See also

Ansible unarchive Module: Extract tar, zip, gz Archives (Complete Guide)

Category: installation

Watch the video: Ansible unarchive Module: Extract tar.gz, zip Archives on Remote Hosts — Video Tutorial

Browse all Ansible tutorials · AnsiblePilot Home