exporting clean releases without the .git directory
git archive turns any commit, tag, or tree into a plain file snapshot packaged as a zip or tar archive, with no commit history and no .git directory attached. For anyone still hand-copying files out of a checkout to build a release package, this built-in Git tool offers a deterministic, pipeline-friendly alternative.
Table of Contents
- 1. What sets git archive apart from a regular checkout
- 2. Output formats: zip, tar, and compressed tar.gz
- 3. A clean folder layout with --prefix
- 4. Excluding files deliberately with .gitattributes
- 5. Archiving a remote repository without a local checkout
- 6. Tying archives to Git tags for traceable releases
- 7. Reproducibility and checksums for release artifacts
- 8. Using git archive inside CI/CD pipelines
- 9. Limits of git archive: submodules and generated files
- 10. Summary
- 11. FAQ
1. What sets git archive apart from a regular checkout
A regular git clone always drags along the full .git directory, meaning the entire commit history, every branch, and every object ever created. For a release artifact that gets shipped to customers or packaged into a deployment, that is usually unwanted baggage, and depending on the history it can even be a privacy risk if an earlier commit once contained sensitive content that was later removed but never purged from history.
git archive solves exactly that problem by working directly against the object database and writing only the file contents of a single, precisely chosen commit, tag, or tree into an archive. Internally it reads the same tree that git ls-tree would show, and it needs neither a working directory nor a local checkout at all, a bare repository is entirely sufficient.
The result is deterministic in terms of file content: the same commit produces the exact same file listing with the exact same content on every invocation, regardless of which branch happens to be checked out or what else is sitting in the working directory.
# Minimal example: export the current state as a zip archive
git archive --format=zip HEAD -o release.zip
# Same snapshot as a tar archive, straight out of a bare repository
git --git-dir=/path/to/bare-repo.git archive HEAD -o release.tar
2. Output formats: zip, tar, and compressed tar.gz
Via the --format option, git archive supports zip and tar as its standard formats, with zip typically the better fit for Windows recipients while tar tends to be the common choice on Linux and inside containers. When the target filename is instead given through -o with a recognizable extension such as .zip, Git infers the format from that extension automatically, so the explicit --format flag becomes optional.
For compressed tar archives, the tar.gz format can be requested directly, and Git relies on its own built-in zlib compression, so no external gzip binary is required. Anyone who wants control over the compression level can append a number from 1 to 9, where higher values trade extra computation time for a smaller output file, and a one-off release build usually benefits from the highest setting.
# Compressed tar.gz at maximum compression
git archive --format=tar.gz -9 HEAD -o release.tar.gz
# Format is inferred automatically from the file extension
git archive HEAD -o release.zip
3. A clean folder layout with --prefix
Without any further options, an archive's files sit directly at the top level, which quickly turns into a messy folder once it is unpacked, mixing release files with whatever else already lives in the target directory. The --prefix option adds a leading path to every entry in the archive, so unpacking it automatically creates its own root folder.
It is common practice to build that prefix from the project name and version, for example myshop-2.4.1/, so that unpacking several releases side by side never overwrites files and it stays obvious at a glance which folder holds which version.
# Build an archive with its own root directory
git archive --format=tar.gz --prefix=myshop-2.4.1/ v2.4.1 -o myshop-2.4.1.tar.gz
4. Excluding files deliberately with .gitattributes
Not every file in a repository belongs in a release archive. Test directories, internal documentation, CI configuration files, or developer tooling often need to stay under version control without ever being shipped. Git covers exactly that case with the export-ignore attribute, set inside a .gitattributes file, which applies only when archiving and leaves a normal checkout completely untouched.
A complementary attribute is export-subst: it enables substitution of placeholders such as $Format:%H$ inside a marked file with the actual commit hash at archive time, which works nicely to keep a version file traceable even without access to .git.
# .gitattributes at the project root
tests/ export-ignore
internal-docs/ export-ignore
.gitlab-ci.yml export-ignore
VERSION export-subst
5. Archiving a remote repository without a local checkout
The --remote option lets you request an archive directly from a remote repository, in theory without first needing a local clone. The server itself builds the archive and only transfers the finished package, which can noticeably save bandwidth and time on very large repositories with a long history.
In practice, most hosted platforms such as GitHub and GitLab disable this feature server side by default for security reasons, since it would otherwise let an attacker request arbitrary commits without that showing up clearly in access logs. For everyday use that means a local checkout followed by a local git archive call remains the more reliable path.
# Only works if the server explicitly allows uploadarchive
git archive --remote=ssh://git@server/project.git --format=tar HEAD | tar -x
6. Tying archives to Git tags for traceable releases
Instead of always archiving whatever HEAD currently points at, a release artifact should be tied to a concrete, immutable tag. An annotated tag permanently marks a commit with a version number, and git archive accepts that tag name in exactly the same position a commit hash would go.
That makes for a simple, repeatable flow: as soon as a tag like v2.4.1 gets pushed, a pipeline job automatically builds the matching archive and uploads it as a named release artifact, with nobody needing to manually check which commit currently represents the release.
# Build a release archive directly from a tag
git archive --format=tar.gz --prefix=myshop-2.4.1/ tags/v2.4.1 \
-o myshop-2.4.1.tar.gz
7. Reproducibility and checksums for release artifacts
Since an archive's file content is derived unambiguously from the chosen commit, a checksum can be computed for every generated release package as reliable proof that a downloaded archive truly matches the tagged commit unchanged. Given the same Git version and identical options, the file content stays stable across repeated runs.
Minor differences in pure archive format metadata, such as timestamps inside the zip structure, are possible depending on the Git version in use, but they do not affect the actual file content or the integrity of the release package. A companion sha256sum file next to the archive belongs in any serious release pipeline.
# Keep a checksum alongside the release archive
sha256sum myshop-2.4.1.tar.gz > myshop-2.4.1.tar.gz.sha256
8. Using git archive inside CI/CD pipelines
Inside a pipeline, a single git archive call often replaces several lines of manual copy logic that used to exclude certain directories and pack the rest into an archive by hand. Since the checkout already exists on the runner anyway, one job step is enough to build the finished artifact and either upload it as a build artifact or push it into an artifact store.
One useful side effect: since the archive never contains a .git directory, a full commit history can never accidentally end up inside a publicly accessible release package, which meaningfully reduces a real risk for open source downloads and customer deployments alike.
# Example job step inside a CI pipeline
build-release:
script:
- git archive --format=tar.gz --prefix=app-${CI_COMMIT_TAG}/ \
${CI_COMMIT_TAG} -o app-${CI_COMMIT_TAG}.tar.gz
artifacts:
paths:
- app-*.tar.gz
9. Limits of git archive: submodules and generated files
Two limitations are worth knowing before relying on this in production. First, submodules are not automatically unpacked by git archive, the archive only contains the reference to the submodule's checked-out commit, not its actual content. Projects that use submodules need an additional tool such as the well known git-archive-all script, which walks recursively through every submodule and merges their contents afterward.
Second, the archive only ever contains versioned files. Generated or installed dependencies such as directories holding Composer or npm packages are normally not part of the repository at all, and therefore need to be produced in a separate build step after archiving, before the finished package is actually runnable.
| Criterion | git archive | git clone --depth 1 | Manual zip/tar |
|---|---|---|---|
| Contains a .git directory | No | Yes, even if shrunk | Depends on the script |
| Deterministic content | Yes, exact to the commit | Yes, but includes metadata | No, error prone |
| Server side without a checkout | Yes, if the remote allows it | No | No |
| Deliberate file exclusion | Yes, via .gitattributes | No, only manual cleanup afterward | Yes, but manually maintained |
| Fits automated pipelines | Yes, a single command | Somewhat, needs an extra cleanup step | No, high maintenance |
Mironsoft
Git workflows, branching strategies, and CI hooks
Chaotic Git history and unclear branching rules across the team?
We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.
Workflow Audit
Review the existing branching strategy and merge practice for weak spots.
Hook Automation
Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.
Team Training
Teach rebase, cherry-pick, and conflict resolution hands-on across the team.
10. Summary
Git Archive
Core idea
git archive produces a plain file snapshot of a commit as zip or tar, with no .git directory and no history at all.
Clean layout
--prefix creates a dedicated root folder inside the archive, .gitattributes with export-ignore excludes specific files.
Typical use
A CI job builds a named, checksummed release artifact from the tagged commit automatically on every tag push.
Known limit
Submodules and generated dependencies such as vendor or node_modules directories are not archived automatically.