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.

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?
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: trueExtract 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: trueDownload 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: trueParameters 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 |
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/prometheusUsing 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.existsSee also: ansible_date_time: Access Date, Time & Timestamp Facts in Ansible
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'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/appSee also: Automating Jenkins Installation with Ansible
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 prometheusBlue-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 myappExtract 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/tool2Video 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: trueWindows: 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.exeSupported 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 |
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 directoryremote_src: true— Archive is on remote host or a URLcreates:— Make extraction idempotentextra_opts: [--strip-components=1]— Remove top-level directoryowner/group/mode— Set file ownership and permissionsinclude/exclude— Extract specific files only- Windows: Use
community.windows.win_unzipinstead
Related Articles
- Ansible copy Module: Copy Files to Remote Hosts
- Ansible get_url Module: Download Files
- Ansible file Module: Create Files and Directories
- Ansible Become Guide: Privilege Escalation
See also
Category: installation
Watch the video: Ansible unarchive Module: Extract tar.gz, zip Archives on Remote Hosts — Video Tutorial