A single GitHub Action to compress and decompress files in your CI/CD
workflow β with password-protected zip encryption, verified SHA256
checksums, a job summary, and six archive formats (zip, tar,
tgz, tbz2, txz, tzst) in one step. Use it to package build artifacts,
encrypt release bundles, or unpack downloaded archives without hand-writing
tar/zip shell commands. Glob patterns, tunable compression levels, exclude
filters, and path stripping are all built in.
Key Features:
- Six Archive Formats -
zip,tar,tgz,tbz2,txz, andtzst(zstd) from one action - Job Summary - Sizes, ratio, file count, duration, and checksum rendered on the run page, no extra step
- Rich Outputs -
checksum,original_size,compressed_size,compression_ratio,file_count,duration - Verified Decompression - Pass
verify_checksumand a tampered or truncated archive is rejected before anything is written to disk - Zip-Slip Protection - Archives whose entries would escape the destination directory are refused
- Password-Protected Zip - Encrypt and decrypt
ziparchives with a secret, kept masked in the log - Compression Level Control - Tune from
0(store only) to9(maximum) for size vs. speed - Glob Pattern Support - Match multiple files with patterns like
**/*.doc - Symbolic Link Support - Automatically follows symlinks (Bazel, Buck, and build tool integration)
- Path Stripping - Remove path prefixes while preserving directory structure
- Flexible Options - Custom destinations, exclude patterns, and root control
| Input | Description | Required | Default |
|---|---|---|---|
command |
The operation to perform. It can be either "compress" or "decompress" | Yes | - |
source |
The source directory, file, or glob pattern to compress or decompress. Supports glob patterns like **/*.doc to match multiple files. |
Yes | - |
dest |
The destination directory for the output. If not provided, it defaults to the current working directory. | No | - |
destfilename |
The destination filename for the output. The format extension is appended unless the name already ends with it, so both archive and archive.zip produce archive.zip. If not provided, it defaults to the source's base name. |
No | - |
dedupeExtension |
Whether to skip appending the format extension when destfilename already ends with it. Set to false to always append, so archive.zip becomes archive.zip.zip. |
No | true |
exclude |
Filename (or pattern) to exclude from compression process. | No | - |
format |
The compression format to use. Supported formats are zip, tar, tgz, tbz2, txz, and tzst. |
Yes | - |
includeRoot |
Whether to include the root folder itself in the compressed file. | No | true |
includeHidden |
Whether to include dotfiles (.env, .git, .github, and nested ones like src/.keep) in the archive. Set to false to leave every hidden file out, identically across all formats. |
No | true |
preserveGlobStructure |
When using glob patterns, preserve the directory structure in the archive. If false, all matched files are flattened to the root level. | No | false |
stripPrefix |
Remove this prefix from file paths when preserving directory structure. Works only with glob patterns and preserveGlobStructure: true. Example: 'src/' changes src/app/main.py to app/main.py in the archive. |
No | "" |
fail_on_error |
Whether to fail the action if compression/decompression fails. | No | true |
compression_level |
Compression level from 0 (store only) to 9 (maximum). Applies to zip, tgz, tbz2, txz, and tzst. |
No | - |
password |
Password for zip encryption/decryption. Pass it via a secret. Only applies to the zip format. |
No | "" |
verbose |
Enable verbose logging for debugging purposes. | No | false |
verify_checksum |
Expected SHA256 of the archive. Decompression aborts before extracting anything when it does not match. Decompress only. | No | "" |
path_traversal_check |
Reject archives whose entries would extract outside the destination directory (zip slip). Decompress only. | No | true |
step_summary |
Write the result table to the workflow job summary ($GITHUB_STEP_SUMMARY). |
No | true |
| Output | Description |
|---|---|
file_path |
The path to the compressed archive, or the directory files were extracted to. |
checksum |
SHA256 checksum of the compressed archive (compress operation only). |
original_size |
Size in bytes of the source (compress) or of the archive (decompress). |
compressed_size |
Size in bytes of the produced archive. 0 on decompress. |
compression_ratio |
Percentage of the original size saved, e.g. 75.0. 0.0 on decompress. |
file_count |
Number of files compressed, or entries extracted. |
duration |
Seconds the operation took, e.g. 1.42. |
You can use this action in your GitHub workflow by specifying the action with its required inputs.
- Glob Pattern Guide - Match multiple files with patterns like
**/*.doc - Advanced Usage Guide - Custom paths, exclude patterns, matrix strategies, and more
- Troubleshooting Guide - Solutions for common issues and debugging tips
- Testing Guide - Test setup, structure, and running tests locally
Every run writes its result to the workflow job summary, so the archive size, ratio, and checksum are on the run page without opening the step log β nothing to configure:
Archive dist/release.tgzFormat tgzOriginal size 42.60 MB Compressed size 8.10 MB Compression ratio 81.0% Files 1204 Duration 2.13s SHA256 9f2cβ¦
Set step_summary: 'false' to turn it off.
Sizes, ratio, file count, and duration are exposed as step outputs, so a later step can gate on them or report them:
- name: Compress Build Output
id: pack
uses: somaz94/compress-decompress@v1
with:
command: compress
source: ./dist
format: tgz
dest: ./artifacts
- name: Report
run: |
echo "archive: ${{ steps.pack.outputs.file_path }}"
echo "files: ${{ steps.pack.outputs.file_count }}"
echo "size: ${{ steps.pack.outputs.compressed_size }} bytes"
echo "ratio: ${{ steps.pack.outputs.compression_ratio }}%"
echo "took: ${{ steps.pack.outputs.duration }}s"
echo "sha256: ${{ steps.pack.outputs.checksum }}"
- name: Fail On A Suspiciously Small Archive
if: ${{ fromJSON(steps.pack.outputs.file_count) < 10 }}
run: exit 1Pass the SHA256 you expect and the archive is checked before a single file is written. A mismatch fails the step with the expected and actual digests:
- name: Download Release Bundle
run: curl -sSLo bundle.tgz "$BUNDLE_URL"
- name: Unpack Only If It Matches
uses: somaz94/compress-decompress@v1
with:
command: decompress
source: ./bundle.tgz
format: tgz
dest: ./unpacked
verify_checksum: ${{ secrets.BUNDLE_SHA256 }}The pair of steps below is the round trip β the checksum output of a compress
step feeds the verify_checksum input of the decompress step:
- name: Compress
id: pack
uses: somaz94/compress-decompress@v1
with:
command: compress
source: ./dist
format: zip
- name: Decompress With Verification
uses: somaz94/compress-decompress@v1
with:
command: decompress
source: ${{ steps.pack.outputs.file_path }}
format: zip
dest: ./unpacked
verify_checksum: ${{ steps.pack.outputs.checksum }}Independently of verify_checksum, decompression refuses archives whose entries
would extract outside the destination directory (../../etc/cron.d/evil, the
"zip slip" pattern). Set path_traversal_check: 'false' to opt out.
tzst (tar + zstd) compresses close to tgz ratios at a fraction of the
time, which is what you want for a cache or a large build artifact:
- name: Compress Build Cache
uses: somaz94/compress-decompress@v1
with:
command: compress
source: ./build-cache
format: tzst
compression_level: '3'
dest: ./artifacts| Format | Codec | Typical use |
|---|---|---|
zip |
deflate | Cross-platform sharing, optional password |
tar |
none | Fast bundling when the payload is already compressed |
tgz |
gzip | The safe default, readable everywhere |
tbz2 |
bzip2 | Smaller than gzip, noticeably slower |
txz |
xz | Smallest output, slowest to produce |
tzst |
zstd | Near-gzip size at much higher speed |
This example demonstrates how to use custom destination and filename options:
name: Compress Files with Custom Path
on: [push]
jobs:
compress-job:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Compress Directory
uses: somaz94/compress-decompress@v1
with:
command: compress
source: ./data-folder
format: zip
dest: './custom_output'
destfilename: 'my_archive'
- name: Upload Artifact
uses: actions/upload-artifact@v4
with:
name: compressed-data
path: ./custom_output/my_archive.zipExclude specific files or directories from compression:
- name: Compress Repository Excluding Git Files
uses: somaz94/compress-decompress@v1
with:
command: compress
source: .
format: zip
dest: './artifacts'
destfilename: 'repo-backup'
exclude: '.git .github node_modules *.log'- Version control:
.git .svn - Dependencies:
node_modules vendor - Temporary files:
*.log *.tmp
π View Advanced Usage Guide β
This action supports glob patterns for matching multiple files across your repository. This is useful when you need to archive specific file types without compressing entire directories.
- name: Compress All Documentation Files
uses: somaz94/compress-decompress@v1
with:
command: compress
source: '**/*.md'
format: zip
dest: './artifacts'
destfilename: 'all-docs'**/*.ext- All files with extension in all subdirectoriesdir/**/*.ext- All files with extension in specific directory**/*.{ext1,ext2}- Multiple file types
- Files are collected into a flattened archive structure by default
- Use
preserveGlobStructure: trueto maintain directory structure - Use
stripPrefixto remove path prefixes (e.g.,'src/'removes src/ from all paths) - No matches will fail by default (use
fail_on_error: falseto override) - Enable
verbose: trueto see matched files
- name: Archive Logs with Directory Structure
uses: somaz94/compress-decompress@v1
with:
command: compress
source: '**/*.log'
format: zip
preserveGlobStructure: true # Preserves dir/subdir1/file.log structure- name: Archive Source Files Without Project Root
uses: somaz94/compress-decompress@v1
with:
command: compress
source: 'project/src/**/*.ts'
format: zip
preserveGlobStructure: true
stripPrefix: 'project/' # Changes project/src/app/main.ts to src/app/main.tsπ View Complete Glob Pattern Guide β
- name: Compress Directory
uses: somaz94/compress-decompress@v1
with:
command: compress
source: ./data-folder
format: zip- name: Decompress Archive
uses: somaz94/compress-decompress@v1
with:
command: decompress
source: ./data-folder.zip
format: zip
dest: './unpacked'Compression fails with "Source not found"?
- Verify source path exists:
ls -la ./data-folder - Use absolute paths:
${{ github.workspace }}/data-folder - Check workspace state:
ls -la ${{ github.workspace }} - See Troubleshooting Guide
Glob pattern not matching files?
- Verify files exist:
find . -name "*.doc" - Enable verbose mode:
verbose: true - Check pattern syntax: Use single quotes
'**/*.doc' - See Glob Pattern Guide
Archive size is too large?
- Use exclude patterns:
exclude: 'node_modules .git *.log' - Use better compression:
format: tbz2instead ofzip - Split into multiple archives
- See Troubleshooting Guide
Exclude patterns not working?
Correct syntax (space-separated):
exclude: 'node_modules .git *.log'Incorrect (comma-separated):
exclude: 'node_modules,.git,*.log' # Wrong!Decompression fails with "Checksum mismatch"?
The archive on disk is not the one the digest was taken from β it was re-created, truncated mid-download, or tampered with.
- Recompute the digest of what you actually have:
sha256sum ./bundle.tgz - Make sure the digest was taken from the compressed archive, not from its contents
- When chaining two steps, pass
${{ steps.<compress-step>.outputs.checksum }}directly - Nothing was extracted β the check runs before any file is written
Decompression fails with "Unsafe archive"?
The archive contains an entry that would be written outside dest
(../../something), which is the "zip slip" pattern. Inspect it before trusting it:
unzip -l archive.zip # or: tar -tf archive.tgzIf the layout is intentional and the source is trusted, opt out with
path_traversal_check: 'false'.
Nothing appears in the job summary?
- The summary is written on
compressanddecompressalike β check the step actually ran step_summary: 'false'disables it- Job summaries are a GitHub-hosted/self-hosted runner feature; on a runner without
$GITHUB_STEP_SUMMARYthe action skips it silently and the step still succeeds
β See full troubleshooting guide
Contributions welcome! Please:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-feature - Make your changes and test locally
- Commit:
git commit -am "feat: Add new feature" - Push and create a Pull Request
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements-dev.txt
python -m pytest tests/ -vβ See full testing guide | β See development and testing guide
This project is licensed under the MIT License - see the LICENSE file for details.
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Documentation: Full Documentation
Thanks to all contributors:
Made with efficiency for GitHub Actions workflows