ecosyste-ms/package-manager-archives

Archive formats used by package managers, with edge cases and oddities.

4

3 commits

updated Dec 1, 2025

See the code

README

Package Manager Archive Formats

Archive formats used by package managers, with edge cases and oddities.

Language Package Managers

Ruby (.gem)

A .gem file is a tar archive containing nested compressed archives:

package.gem (tar)
├── checksums.yaml.gz
├── metadata.gz
├── data.tar.gz
├── checksums.yaml.gz.sig  (if signed)
├── metadata.gz.sig        (if signed)
└── data.tar.gz.sig        (if signed)

metadata.gz contains the gemspec as YAML. data.tar.gz contains the actual files (lib/, bin/, etc.). checksums.yaml.gz contains SHA256 and SHA512 hashes of the other archives.

Edge cases:

  • The outer tar has no compression, only the inner files are gzipped
  • Signatures use RSA encryption of SHA1 hashes
  • The checksums.yaml format changed from MD5 to SHA256/SHA512
  • RubyGems.org displays SHA-256 checksums but gems contain SHA512 internally
  • There's no standard library tar support in Ruby; RubyGems bundles its own Gem::Package::TarReader

References:

JavaScript (npm/yarn/pnpm .tgz)

npm packages are gzipped tarballs. The tarball contains a single package/ directory with the package contents.

package-1.0.0.tgz
└── package/
    ├── package.json
    ├── index.js
    └── ...

Edge cases:

  • npm uses pax extended headers for paths longer than 100 characters
  • The tar npm package (used by npm itself) has a noPax option, but using it truncates long paths
  • Integrity hashes changed from SHA1 to SHA512 in npm 5+; teams with mixed npm versions see constant lockfile churn as hashes flip between formats
  • The integrity field in package-lock.json is a Subresource Integrity string
  • npm pack always names the internal directory package/ regardless of the package name
  • Scoped packages (@scope/name) become scope-name-version.tgz filenames

References:

Python Wheel (.whl)

A wheel is a ZIP archive with a specific filename format and internal structure. The .whl extension is just a renamed .zip.

Filename format: {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl

Example: numpy-1.24.0-cp311-cp311-macosx_10_9_x86_64.whl

package-1.0.0-py3-none-any.whl (ZIP)
├── package/
│   ├── __init__.py
│   └── ...
├── package-1.0.0.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── RECORD.jws (optional signature)
└── package-1.0.0.data/ (optional)
    ├── scripts/
    ├── headers/
    └── ...

Edge cases:

  • RECORD must list every file with its hash, except RECORD itself (which cannot contain its own hash)
  • Only SHA256 or stronger hashes are allowed; MD5 and SHA1 are explicitly forbidden
  • The .data/ directory holds files that go outside site-packages (scripts, headers, etc.)
  • Wheels don't contain .pyc files since they need to work across Python versions
  • Wheels don't contain setup.py or setup.cfg
  • The filename's compatibility tags (python-abi-platform) can specify multiple values separated by dots

References:

Python Source Distribution (.tar.gz sdist)

Source distributions are gzipped tarballs with a PKG-INFO metadata file.

package-1.0.0.tar.gz
└── package-1.0.0/
    ├── pyproject.toml
    ├── PKG-INFO
    ├── setup.py (legacy)
    └── src/

Edge cases:

  • Modern sdists require pyproject.toml; legacy ones used setup.py
  • Must use POSIX.1-2001 pax tar format with UTF-8 filenames
  • PKG-INFO uses metadata version 2.2+, but historically did not include dependencies (that was setup.py's job)
  • The tarball must be readable by Python's standard tarfile module with 'r:gz'

References:

Conda (.conda, .tar.bz2)

Conda packages can contain any language runtime or library, not just Python. Two formats exist: modern .conda and legacy .tar.bz2.

.conda format (conda 4.7+):

package-1.0-py39_0.conda (ZIP, uncompressed outer container)
├── info-*.tar.zst      (metadata, compressed separately)
└── pkg-*.tar.zst       (package contents, compressed separately)

.tar.bz2 format (legacy):

package-1.0-py39_0.tar.bz2
├── info/
│   ├── index.json      (name, version, dependencies)
│   ├── paths.json      (file list with hashes)
│   ├── files           (simple file list)
│   ├── LICENSE.txt
│   └── recipe/         (build recipe)
├── bin/
└── lib/

Edge cases:

  • .conda separates metadata from contents for fast indexing without extracting the whole package
  • The outer .conda container is an uncompressed ZIP; inner tarballs use zstd
  • Only files are stored, not directories; empty directories cannot be created directly
  • info/has_prefix lists files with hardcoded build paths that need rewriting at install time
  • info/paths.json replaced separate files, has_prefix, no_link files (2016+)
  • File paths use / delimiter even on Windows
  • Build string (e.g., py39_0) encodes Python version and build number

References:

Java JAR (.jar)

A JAR is a ZIP archive with a manifest file. WAR and EAR files use the same format with different conventions.

package.jar (ZIP)
├── META-INF/
│   ├── MANIFEST.MF
│   ├── *.SF (signature files)
│   └── *.DSA/.RSA/.EC (signature block files)
└── com/
    └── example/
        └── MyClass.class

JAR variants (same format, different contents):

  • artifact.jar - binary JAR with compiled .class files
  • artifact-sources.jar - source JAR with .java files
  • artifact-javadoc.jar - documentation JAR with generated HTML

Maven uses classifier in coordinates (groupId:artifactId:version:classifier) to distinguish them. All three use identical ZIP/MANIFEST.MF structure.

Edge cases:

  • MANIFEST.MF must have a newline at the end of the file; otherwise the last header is ignored
  • Lines longer than 72 bytes must use continuation (space at start of next line)
  • Duplicate manifest entries: only the first is recognized
  • MANIFEST.MF should be the first entry in the ZIP, though the spec doesn't require it. JDK's getManifest() may return null if it isn't first
  • Header names are case-insensitive, but values are case-sensitive (except for certain "magic" values)
  • Package names in per-entry sections must end with /
  • Specification-Version must be ASCII digits and periods only, no other characters
  • Unknown attributes are silently ignored
  • Per-entry sections override main section headers

References:

.NET NuGet (.nupkg)

A NuGet package uses the Open Packaging Conventions (OPC) format, which is also used by Office documents (.docx, .xlsx). It's a ZIP archive with specific metadata files.

Package.1.0.0.nupkg (ZIP/OPC)
├── [Content_Types].xml
├── _rels/
│   └── .rels
├── package/
│   └── services/
│       └── metadata/
│           └── core-properties/
│               └── *.psmdcp
├── Package.nuspec
└── lib/
    └── net6.0/
        └── Package.dll

Edge cases:

  • OPC reserves three names: _rels/ directories, .rels files within them, and [Content_Types].xml
  • File paths use URL encoding; reserved characters must be percent-encoded
  • The .rels files define relationships between "parts" (files) using URIs
  • [Content_Types].xml maps file extensions to MIME types
  • Square brackets in filenames can cause issues with some tools
  • The .nuspec manifest is XML, not JSON

References:

Rust Cargo (.crate)

A .crate file is a gzipped tarball containing the package source.

package-1.0.0.crate (tar.gz)
└── package-1.0.0/
    ├── Cargo.toml
    ├── Cargo.toml.orig
    ├── Cargo.lock
    ├── .cargo_vcs_info.json
    └── src/

Edge cases:

  • Cargo.toml is normalized (workspace inheritance resolved, etc.); the original is preserved as Cargo.toml.orig
  • Cargo.lock is always included (minimized) unless --exclude-lockfile is used; cargo install --locked uses it
  • .cargo_vcs_info.json contains the git SHA and whether the worktree was dirty:
    {
      "git": { "sha1": "aac20b6e...", "dirty": false },
      "path_in_vcs": ""
    }
    
  • path_in_vcs is set for packages in subdirectories of a repository

References:

Go Module (.zip)

Go modules are ZIP archives with strict path requirements.

example.com/[email protected]
└── example.com/[email protected]/
    ├── go.mod
    └── ...

Edge cases:

  • All paths must start with <module>@<version>/
  • No two paths may differ only by case (case-folding under Unicode rules); this prevents issues on case-insensitive filesystems
  • go.mod must be exactly go.mod, not Go.mod or GO.MOD
  • Module paths cannot contain Windows reserved names (CON, NUL, etc.) or end with tilde+digits (EXAMPL~1)
  • Case-insensitive filename collisions are errors: if a repo has both README.md and readme.md, it cannot be a Go module
  • Module paths with uppercase letters are escaped with ! prefix in the download cache (e.g., GitHub.com becomes !github.com)

References:

PHP Composer

Composer packages are distributed as ZIP archives downloaded directly from source (usually GitHub/GitLab archives) or from Packagist.

Edge cases:

  • Composer itself doesn't define a package archive format; it downloads repository archives
  • composer archive creates a ZIP of your project
  • The vendor/ directory and vendor/autoload.php are generated locally, not distributed

References:

Elixir/Erlang Hex (.tar)

Hex packages are uncompressed tar archives containing metadata and a nested compressed tarball.

package-1.0.0.tar
├── VERSION           (ASCII: "3")
├── CHECKSUM          (SHA256 hex uppercase)
├── metadata.config   (Erlang term format)
└── contents.tar.gz   (actual package files)

Edge cases:

  • VERSION is a single ASCII digit (currently "3")
  • CHECKSUM is SHA256 of the other three files concatenated, hex-encoded uppercase
  • metadata.config uses Erlang's file:consult/1 format, not JSON or YAML; must be parsed with Erlang/Elixir term parser
  • The outer tar is uncompressed; only contents.tar.gz is compressed
  • Some packages only provide metadata.config without other metadata files

References:

Haskell Hackage (.tar.gz)

Hackage packages are standard gzipped tarballs produced by cabal sdist.

package-1.0.0.tar.gz
└── package-1.0.0/
    ├── package.cabal
    ├── LICENSE
    ├── Setup.hs (optional)
    └── src/

Edge cases:

  • The directory name must match package-version exactly
  • LICENSE file is required; cabal sdist fails without it
  • Only files listed in the .cabal file are included; add READMEs to extra-source-files
  • The .cabal file contains all metadata (no separate metadata file)
  • cabal sdist does validation; manually created tarballs may be rejected

References:

OCaml OPAM

OPAM doesn't define its own archive format; it downloads source archives from URLs specified in opam files.

opam repository structure:
packages/
└── package-name/
    └── package-name.version/
        ├── opam          (package definition)
        ├── files/        (patches, additional files)
        └── url           (legacy, now in opam file)

Edge cases:

  • The opam file is a custom DSL, not YAML/JSON
  • Source URLs point to external tarballs (GitHub releases, etc.)
  • Checksums are specified in the url section: url { src: "..." checksum: "sha256=..." }
  • The files/ directory contents are overlaid on the source after extraction
  • Repository indexes can be served over HTTP, git, or local filesystem

References:

Lua LuaRocks (.rock)

A .rock file is a ZIP archive containing a rockspec and source/binary files.

myrock-1.0-1.src.rock (ZIP)
├── myrock-1.0-1.rockspec
└── (source files or directories)

myrock-1.0-1.linux-x86_64.rock (ZIP)
├── myrock-1.0-1.rockspec
├── lua/
│   └── myrock.lua
└── lib/
    └── myrock.so

Filename format: {name}-{version}-{revision}.{platform}.rock

Edge cases:

  • Source rocks use .src.rock; binary rocks use platform like .linux-x86_64.rock
  • Pure Lua rocks use .all.rock (no native code, works everywhere)
  • The .rockspec file is valid Lua code (not YAML/JSON), though without access to Lua functions
  • Revision number is separate from version (e.g., 1.0-1 means version 1.0, rockspec revision 1)
  • Requires zip to be installed for creating rocks

References:

Perl CPAN (.tar.gz)

CPAN distributions are gzipped tarballs with specific metadata files.

Distribution-Name-1.0.tar.gz
└── Distribution-Name-1.0/
    ├── META.json        (v2+ metadata)
    ├── META.yml         (v1.4 metadata, legacy)
    ├── MANIFEST         (file listing)
    ├── Makefile.PL      (or Build.PL)
    └── lib/

Edge cases:

  • META.json and META.yml contain the same data in different formats; both should be included for compatibility
  • MANIFEST lists all files in the distribution; some tools validate against it
  • MYMETA.json/yml are generated at install time, not distributed; including them causes problems
  • Distribution name uses - (Distribution-Name), module names use :: (Distribution::Name)
  • META files historically didn't include runtime dependencies; that was in Makefile.PL/Build.PL

References:

Dart Pub

Pub packages are distributed via pub.dev; the package format is a standard directory structure uploaded as a tarball.

package/
├── pubspec.yaml
├── lib/
│   └── package.dart
├── bin/ (optional)
└── test/ (optional)

Edge cases:

  • Package names must be lowercase with underscores, valid Dart identifiers
  • pubspec.yaml is YAML with required name and version fields
  • Version is optional for local-only packages (defaults to 0.0.0)
  • SDK constraints specify minimum Dart/Flutter versions
  • dart pub publish performs secret detection and refuses to publish potential credential leaks

References:

C/C++ Conan

Conan uses internal .tgz archives for storage and transfer, but these are not meant for direct manipulation.

Recipe files (conan_export.tgz):
├── conanfile.py
├── conandata.yml (optional)
└── conanmanifest.txt

Binary package (conan_package.tgz):
├── conaninfo.txt
├── conanmanifest.txt
└── (compiled files)

Edge cases:

  • The .tgz format is internal and undocumented; not guaranteed stable
  • conanfile.py is Python code that defines how to build and package
  • conanfile.txt is a simplified INI-like format for consumers only
  • conanmanifest.txt contains checksums for verification
  • conaninfo.txt records settings used during build (compiler, OS, etc.)
  • Packages are identified by name/version@user/channel plus a package ID hash

References:


System Package Managers

Debian (.deb)

A .deb file is an ar archive containing exactly three members in order:

package_1.0-1_amd64.deb (ar archive)
├── debian-binary        (text: "2.0\n")
├── control.tar.xz       (or .gz, .zst)
└── data.tar.xz          (or .gz, .zst, .bz2, .lzma)

control.tar contains:

  • control (required) - package metadata
  • md5sums - checksums of installed files
  • conffiles - list of configuration files
  • preinst, postinst, prerm, postrm - maintainer scripts
  • triggers, shlibs, symbols - optional metadata

Edge cases:

  • Members must appear in exactly this order: debian-binary, control.tar, data.tar
  • The ar archive has a magic value !<arch> and uses the common ar format (no long filename extensions)
  • Filename limit is 15 characters (16 bytes minus trailing slash)
  • debian-binary must be first or the package is invalid
  • Supported compressions vary by dpkg version: xz since 1.15.6, zstd since 1.19.0
  • Supported tar formats: v7, pre-POSIX ustar, subset of GNU tar, POSIX ustar

References:

RPM (.rpm)

An RPM file has four sections: lead, signature, header, and payload.

package-1.0-1.x86_64.rpm
├── Lead (96 bytes magic + info)
├── Signature (digital signatures)
├── Header (all metadata)
└── Payload (compressed cpio archive)

The payload is a gzip-compressed cpio archive (SVR4/newc format with CRC).

Edge cases:

  • All integers are big-endian (network byte order)
  • cpio has a 4 GiB file size limit; RPM 4.12+ uses an extended format (07070X magic) for larger files
  • The extended format stores only file index in cpio headers; actual metadata is in the RPM header
  • Metadata in cpio headers must match the RPM header
  • The lead is largely vestigial but must be present

References:

Alpine APK (.apk)

An Alpine package is a concatenation of three gzipped tar segments:

package-1.0-r0.apk (concatenated tar.gz segments)
├── signature.tar.gz (optional, contains .SIGN.*)
├── control.tar.gz   (contains .PKGINFO, etc.)
└── data.tar.gz      (package contents)

Edge cases:

  • Tar segments, not a single tar: segments lack the standard end-of-archive null records, allowing concatenation
  • Control files are prefixed with . for historical reasons (.PKGINFO, .pre-install, etc.)
  • Signature file must be first if present, named .SIGN.<type>.<keyname> (type is DSA or RSA)
  • APKINDEX.tar.gz (repository index) contains DESCRIPTION and APKINDEX files
  • APKINDEX entries use single-letter prefixes (P: for package name, V: for version, etc.)
  • SHA256 hashes are base64 encoded with Q1 prefix (not hex)
  • Three format versions exist: v1 (deprecated), v2 (current), v3 (in development)

References:

FreeBSD pkg (.pkg, .txz)

A FreeBSD package is a tar archive (compressed with xz by default) with JSON manifests.

package-1.0.pkg (tar.xz)
├── +COMPACT_MANIFEST
├── +MANIFEST
└── usr/local/...

Edge cases:

  • Special files start with + and must come first
  • +COMPACT_MANIFEST comes before +MANIFEST (both are JSON)
  • +COMPACT_MANIFEST omits file lists, checksums, scripts; used for repository catalogs to avoid transferring large file lists over slow connections
  • +MANIFEST includes SHA256 checksums for all files
  • Supported compressions: xz (default), zstd, bzip2, gzip, or uncompressed

References:

Snap (.snap)

A snap is a SquashFS filesystem image.

package_1.0_amd64.snap (SquashFS image)
├── meta/
│   └── snap.yaml
├── bin/
├── lib/
└── usr/

Edge cases:

  • SquashFS is a read-only compressed filesystem, not an archive; it's mounted at runtime
  • Compression can be xz (smaller) or lzo (faster startup)
  • snap.yaml in meta/ contains package metadata
  • Unpacking requires unsquashfs, not standard archive tools
  • The filesystem layout mirrors a typical Linux root

References:

Flatpak

Flatpak uses OSTree, a git-like content-addressed storage system for binaries.

Edge cases:

  • Not a traditional archive format; uses OSTree repositories
  • Refs follow <app|runtime>/<id>/<arch>/<version> naming (e.g., app/org.gnome.gedit/x86_64/stable)
  • Single-file bundles exist but are OSTree static deltas
  • Content is deduplicated via content-addressed storage
  • The summary file is like rpm's repodata; must be regenerated when packages change

References:

Homebrew Bottles

Bottles are gzipped tarballs with formula-specific naming.

Filename format: <formula>-<version>.<os_version>.bottle[.<rebuild>].tar.gz

Examples:

  • wget-1.21.3.arm64_monterey.bottle.tar.gz
  • openssl-3.0.7.ventura.bottle.1.tar.gz (rebuild 1)

Edge cases:

  • Exactly one hyphen between formula name and version; brew bottle sometimes generates two hyphens which must be fixed
  • Contains the formula definition at <formula>/<version>/.brew/<formula>.rb
  • Bottles are built for specific macOS versions (ventura, sonoma, etc.) and architectures
  • Cache files use SHA256 hash prefixes: 544b4d73...--wget-1.21.3.monterey.bottle.tar.gz

References:

Arch Linux Pacman (.pkg.tar.zst)

Pacman packages are zstd-compressed tarballs with metadata files.

package-1.0-1-x86_64.pkg.tar.zst
├── .PKGINFO        (package metadata)
├── .BUILDINFO      (build reproducibility info)
├── .MTREE          (file hashes and permissions)
├── .INSTALL        (optional install scripts)
├── .Changelog      (optional)
└── usr/
    └── bin/

Edge cases:

  • Metadata files are prefixed with . and must come first
  • .PKGINFO is key=value format with duplicate keys for multiple values (simple to parse)
  • .MTREE is gzip-compressed inside the already-compressed package; contains SHA256 hashes, permissions, timestamps
  • .BUILDINFO enables reproducible builds; records exact build environment
  • Compression changed: gzip (historical) -> xz (2010) -> zstd (2019)
  • The -1 in 1.0-1 is the pkgrel (packaging revision), separate from upstream version

References:

Gentoo Portage (.tbz2, .gpkg.tar)

Gentoo has two binary package formats: legacy XPAK (.tbz2) and modern GPKG (.gpkg.tar).

XPAK format (.tbz2):

package-1.0.tbz2
└── [bzip2 tarball] + [XPAK metadata trailer]

GPKG format (.gpkg.tar) (default since Portage 3.0.36):

package-1.0.gpkg.tar
├── gpkg-1                       (empty, format marker)
├── Manifest                     (checksums)
├── package-1.0/
│   ├── metadata.tar{.xz}        (ebuild, environment)
│   └── image.tar{.xz}           (installed files)
└── *.sig                        (optional OpenPGP signatures)

Edge cases:

  • XPAK is a trailer format: metadata is appended after the tarball, not inside it
  • XPAK stores the ebuild, USE flags, environment, CFLAGS/CXXFLAGS used to build
  • GPKG is extractable with plain tar; XPAK requires special tools (qtbz2, qxpak)
  • GPKG supports OpenPGP signatures for integrity verification
  • Use BINPKG_FORMAT=gpkg or BINPKG_FORMAT=xpak in make.conf to choose format

References:

Void Linux XBPS (.xbps)

XBPS packages are compressed archives with plist metadata.

package-1.0_1.x86_64.xbps
├── props.plist     (package properties)
├── files.plist     (file list with hashes)
├── INSTALL         (optional install script)
├── REMOVE          (optional remove script)
└── (package files)

Edge cases:

  • Metadata uses plist format (Apple property lists), parsed with proplib
  • props.plist includes installed_size, dependencies, etc.
  • files.plist contains SHA256 hashes for all files
  • Supported compressions: gzip, bzip2, lz4, xz, zstd (default)
  • Package database is stored in /var/db/xbps/pkgdb-0.38.plist
  • .xbps.sig files contain RSA signatures

References:

Nix (NAR - Nix Archive)

NAR is a custom archive format designed for deterministic, reproducible builds. It is not based on tar or zip.

Edge cases:

  • Not tar or zip: custom binary format with length-prefixed strings
  • Strips non-deterministic metadata: no timestamps, no owner/group IDs, no permissions except executable bit
  • Designed for content-addressing: the hash of a NAR is used as its store path
  • Format: nix-archive-1 header, then recursive file/directory/symlink serialization
  • Strings are 64-bit little-endian length-prefixed, padded to 8-byte boundary
  • Directory entries must be sorted by name (for determinism)
  • Use nix-store --dump and nix-store --restore to create/extract
  • No compression in NAR itself; compression applied separately

References:


Container and Infrastructure Formats

OCI Container Images

OCI images are layered tarballs with JSON manifests, used by Docker, Podman, and other container runtimes.

image/
├── index.json              (points to manifests)
├── oci-layout              ({"imageLayoutVersion": "1.0.0"})
├── blobs/
│   └── sha256/
│       ├── abc123...       (manifest.json)
│       ├── def456...       (config.json)
│       ├── 789abc...       (layer.tar.gz)
│       └── ...

Manifest points to config and layers:

{
  "schemaVersion": 2,
  "config": { "digest": "sha256:...", "mediaType": "...+json" },
  "layers": [
    { "digest": "sha256:...", "mediaType": "...tar+gzip" }
  ]
}

Edge cases:

  • Layers are applied in order: index 0 is base, subsequent layers overlay
  • Layers are tar archives (optionally gzip/zstd compressed) containing filesystem diffs
  • Whiteout files (.wh.<filename>) mark deleted files from previous layers
  • Opaque whiteouts (.wh..wh..opq) hide all files in a directory from lower layers
  • Layer media types: application/vnd.oci.image.layer.v1.tar, .tar+gzip, .tar+zstd
  • schemaVersion must be 2 for backwards compatibility with Docker
  • Content is addressed by SHA256 digest

References:

Helm Charts (.tgz)

Helm charts are gzipped tarballs with a specific directory structure for Kubernetes deployments.

mychart-1.0.0.tgz
└── mychart/
    ├── Chart.yaml          (chart metadata, required)
    ├── values.yaml         (default configuration)
    ├── values.schema.json  (optional JSON Schema)
    ├── charts/             (dependency charts)
    ├── crds/               (Custom Resource Definitions)
    └── templates/
        ├── deployment.yaml
        ├── service.yaml
        └── _helpers.tpl

Edge cases:

  • Chart.yaml is required; contains name, version, appVersion, dependencies
  • templates/ files are Go templates, not plain YAML; rendered with Helm
  • values.yaml provides defaults; users override via --set or -f values.yaml
  • Files named _*.tpl are template helpers, not rendered directly
  • charts/ contains dependencies as nested .tgz files or unpacked directories
  • Unknown fields in Chart.yaml are silently dropped (can't pass arbitrary data to templates)
  • Template files should use .yaml extension; .tpl for helpers that produce no output

References:

Terraform Providers (.zip)

Terraform providers are ZIP archives containing a single Go binary and metadata.

terraform-provider-aws_4.0.0_linux_amd64.zip
└── terraform-provider-aws_v4.0.0_x5

Registry release artifacts:
├── terraform-provider-NAME_VERSION_OS_ARCH.zip     (multiple)
├── terraform-provider-NAME_VERSION_manifest.json
├── terraform-provider-NAME_VERSION_SHA256SUMS
└── terraform-provider-NAME_VERSION_SHA256SUMS.sig

Edge cases:

  • Binary name format: terraform-provider-{NAME}_v{VERSION} (note the v prefix in binary, not in zip)
  • ZIP name format: terraform-provider-{NAME}_{VERSION}_{OS}_{ARCH}.zip
  • manifest.json specifies protocol versions: {"version": 1, "metadata": {"protocol_versions": ["6.0"]}}
  • Provider protocol uses MessagePack or JSON for wire format
  • SHA256SUMS must be GPG-signed for registry publication
  • Terraform caches providers in two layouts: packed (.zip) and unpacked (extracted)
  • Unpacked path: HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET/

References:


Common Gotchas

Nested archives: gem (tar containing gzipped tars), deb (ar containing tars), Hex (tar containing tar.gz)

Concatenated archives: Alpine apk (multiple tar.gz segments concatenated)

Trailer metadata: Gentoo XPAK appends metadata after the tarball, not inside it

Not actually archives: Snap (SquashFS image), Flatpak (OSTree repo), Nix NAR (custom format)

ZIP-based but not plain ZIP: NuGet (OPC with relationship metadata), wheel (with required RECORD file), LuaRocks .rock

Order matters: deb members, FreeBSD +MANIFEST files, JAR MANIFEST.MF position, Pacman metadata files

Case sensitivity: Go modules reject case-only filename differences; npm/pip don't care

Hash algorithm transitions: npm SHA1 to SHA512, gem MD5 to SHA256/512

Filename encoding: tar variants handle UTF-8 differently; pax standardizes it

Compression format changes over time: Pacman (gz -> xz -> zstd), deb (gz -> xz -> zstd options)

Config files that are code: LuaRocks .rockspec (Lua), Conan conanfile.py (Python), Helm templates (Go templates)

Erlang-specific formats: Hex metadata.config uses Erlang term format, not JSON/YAML

Layered formats: OCI images layer tarballs with whiteout files for deletions

Determinism requirements: Nix NAR strips timestamps/ownership, Go modules forbid case collisions


Underlying Archive Formats

Most package managers build on tar or zip, but the variants matter.

Tar Variants

The tar format has several incompatible variants with different limitations:

v7 (Original)

  • 99-character filename limit
  • 8 GiB file size limit
  • No user/group names, only numeric IDs

UStar (POSIX.1-1988)

  • 255-character filename limit (split between prefix and name fields, so effective limit varies)
  • 8 GiB file size limit
  • Adds user/group names

GNU tar

  • Unlimited filename/path length (uses extension headers with typeflag L/K)
  • Files over 8 GiB use base-256 encoding in size field (sets high bit)
  • Incompatible with strict POSIX implementations

PAX (POSIX.1-2001)

  • Unlimited filename/path length via extended headers
  • Unlimited file size
  • UTF-8 metadata encoding
  • Sub-second timestamp precision
  • Backwards compatible with ustar readers (they see extended headers as regular files)

npm uses pax headers for long filenames. Most modern package managers default to or support pax.

References:


License

CC0 1.0 Universal

archives
package-management
package-manager

ecosyste-ms/package-manager-archives

Archive formats used by package managers, with edge cases and oddities.

4

3 commits

updated Dec 1, 2025

See the code

README

Package Manager Archive Formats

Archive formats used by package managers, with edge cases and oddities.

Language Package Managers

Ruby (.gem)

A .gem file is a tar archive containing nested compressed archives:

package.gem (tar)
├── checksums.yaml.gz
├── metadata.gz
├── data.tar.gz
├── checksums.yaml.gz.sig  (if signed)
├── metadata.gz.sig        (if signed)
└── data.tar.gz.sig        (if signed)

metadata.gz contains the gemspec as YAML. data.tar.gz contains the actual files (lib/, bin/, etc.). checksums.yaml.gz contains SHA256 and SHA512 hashes of the other archives.

Edge cases:

  • The outer tar has no compression, only the inner files are gzipped
  • Signatures use RSA encryption of SHA1 hashes
  • The checksums.yaml format changed from MD5 to SHA256/SHA512
  • RubyGems.org displays SHA-256 checksums but gems contain SHA512 internally
  • There's no standard library tar support in Ruby; RubyGems bundles its own Gem::Package::TarReader

References:

JavaScript (npm/yarn/pnpm .tgz)

npm packages are gzipped tarballs. The tarball contains a single package/ directory with the package contents.

package-1.0.0.tgz
└── package/
    ├── package.json
    ├── index.js
    └── ...

Edge cases:

  • npm uses pax extended headers for paths longer than 100 characters
  • The tar npm package (used by npm itself) has a noPax option, but using it truncates long paths
  • Integrity hashes changed from SHA1 to SHA512 in npm 5+; teams with mixed npm versions see constant lockfile churn as hashes flip between formats
  • The integrity field in package-lock.json is a Subresource Integrity string
  • npm pack always names the internal directory package/ regardless of the package name
  • Scoped packages (@scope/name) become scope-name-version.tgz filenames

References:

Python Wheel (.whl)

A wheel is a ZIP archive with a specific filename format and internal structure. The .whl extension is just a renamed .zip.

Filename format: {distribution}-{version}(-{build})?-{python}-{abi}-{platform}.whl

Example: numpy-1.24.0-cp311-cp311-macosx_10_9_x86_64.whl

package-1.0.0-py3-none-any.whl (ZIP)
├── package/
│   ├── __init__.py
│   └── ...
├── package-1.0.0.dist-info/
│   ├── METADATA
│   ├── WHEEL
│   ├── RECORD
│   └── RECORD.jws (optional signature)
└── package-1.0.0.data/ (optional)
    ├── scripts/
    ├── headers/
    └── ...

Edge cases:

  • RECORD must list every file with its hash, except RECORD itself (which cannot contain its own hash)
  • Only SHA256 or stronger hashes are allowed; MD5 and SHA1 are explicitly forbidden
  • The .data/ directory holds files that go outside site-packages (scripts, headers, etc.)
  • Wheels don't contain .pyc files since they need to work across Python versions
  • Wheels don't contain setup.py or setup.cfg
  • The filename's compatibility tags (python-abi-platform) can specify multiple values separated by dots

References:

Python Source Distribution (.tar.gz sdist)

Source distributions are gzipped tarballs with a PKG-INFO metadata file.

package-1.0.0.tar.gz
└── package-1.0.0/
    ├── pyproject.toml
    ├── PKG-INFO
    ├── setup.py (legacy)
    └── src/

Edge cases:

  • Modern sdists require pyproject.toml; legacy ones used setup.py
  • Must use POSIX.1-2001 pax tar format with UTF-8 filenames
  • PKG-INFO uses metadata version 2.2+, but historically did not include dependencies (that was setup.py's job)
  • The tarball must be readable by Python's standard tarfile module with 'r:gz'

References:

Conda (.conda, .tar.bz2)

Conda packages can contain any language runtime or library, not just Python. Two formats exist: modern .conda and legacy .tar.bz2.

.conda format (conda 4.7+):

package-1.0-py39_0.conda (ZIP, uncompressed outer container)
├── info-*.tar.zst      (metadata, compressed separately)
└── pkg-*.tar.zst       (package contents, compressed separately)

.tar.bz2 format (legacy):

package-1.0-py39_0.tar.bz2
├── info/
│   ├── index.json      (name, version, dependencies)
│   ├── paths.json      (file list with hashes)
│   ├── files           (simple file list)
│   ├── LICENSE.txt
│   └── recipe/         (build recipe)
├── bin/
└── lib/

Edge cases:

  • .conda separates metadata from contents for fast indexing without extracting the whole package
  • The outer .conda container is an uncompressed ZIP; inner tarballs use zstd
  • Only files are stored, not directories; empty directories cannot be created directly
  • info/has_prefix lists files with hardcoded build paths that need rewriting at install time
  • info/paths.json replaced separate files, has_prefix, no_link files (2016+)
  • File paths use / delimiter even on Windows
  • Build string (e.g., py39_0) encodes Python version and build number

References:

Java JAR (.jar)

A JAR is a ZIP archive with a manifest file. WAR and EAR files use the same format with different conventions.

package.jar (ZIP)
├── META-INF/
│   ├── MANIFEST.MF
│   ├── *.SF (signature files)
│   └── *.DSA/.RSA/.EC (signature block files)
└── com/
    └── example/
        └── MyClass.class

JAR variants (same format, different contents):

  • artifact.jar - binary JAR with compiled .class files
  • artifact-sources.jar - source JAR with .java files
  • artifact-javadoc.jar - documentation JAR with generated HTML

Maven uses classifier in coordinates (groupId:artifactId:version:classifier) to distinguish them. All three use identical ZIP/MANIFEST.MF structure.

Edge cases:

  • MANIFEST.MF must have a newline at the end of the file; otherwise the last header is ignored
  • Lines longer than 72 bytes must use continuation (space at start of next line)
  • Duplicate manifest entries: only the first is recognized
  • MANIFEST.MF should be the first entry in the ZIP, though the spec doesn't require it. JDK's getManifest() may return null if it isn't first
  • Header names are case-insensitive, but values are case-sensitive (except for certain "magic" values)
  • Package names in per-entry sections must end with /
  • Specification-Version must be ASCII digits and periods only, no other characters
  • Unknown attributes are silently ignored
  • Per-entry sections override main section headers

References:

.NET NuGet (.nupkg)

A NuGet package uses the Open Packaging Conventions (OPC) format, which is also used by Office documents (.docx, .xlsx). It's a ZIP archive with specific metadata files.

Package.1.0.0.nupkg (ZIP/OPC)
├── [Content_Types].xml
├── _rels/
│   └── .rels
├── package/
│   └── services/
│       └── metadata/
│           └── core-properties/
│               └── *.psmdcp
├── Package.nuspec
└── lib/
    └── net6.0/
        └── Package.dll

Edge cases:

  • OPC reserves three names: _rels/ directories, .rels files within them, and [Content_Types].xml
  • File paths use URL encoding; reserved characters must be percent-encoded
  • The .rels files define relationships between "parts" (files) using URIs
  • [Content_Types].xml maps file extensions to MIME types
  • Square brackets in filenames can cause issues with some tools
  • The .nuspec manifest is XML, not JSON

References:

Rust Cargo (.crate)

A .crate file is a gzipped tarball containing the package source.

package-1.0.0.crate (tar.gz)
└── package-1.0.0/
    ├── Cargo.toml
    ├── Cargo.toml.orig
    ├── Cargo.lock
    ├── .cargo_vcs_info.json
    └── src/

Edge cases:

  • Cargo.toml is normalized (workspace inheritance resolved, etc.); the original is preserved as Cargo.toml.orig
  • Cargo.lock is always included (minimized) unless --exclude-lockfile is used; cargo install --locked uses it
  • .cargo_vcs_info.json contains the git SHA and whether the worktree was dirty:
    {
      "git": { "sha1": "aac20b6e...", "dirty": false },
      "path_in_vcs": ""
    }
    
  • path_in_vcs is set for packages in subdirectories of a repository

References:

Go Module (.zip)

Go modules are ZIP archives with strict path requirements.

example.com/[email protected]
└── example.com/[email protected]/
    ├── go.mod
    └── ...

Edge cases:

  • All paths must start with <module>@<version>/
  • No two paths may differ only by case (case-folding under Unicode rules); this prevents issues on case-insensitive filesystems
  • go.mod must be exactly go.mod, not Go.mod or GO.MOD
  • Module paths cannot contain Windows reserved names (CON, NUL, etc.) or end with tilde+digits (EXAMPL~1)
  • Case-insensitive filename collisions are errors: if a repo has both README.md and readme.md, it cannot be a Go module
  • Module paths with uppercase letters are escaped with ! prefix in the download cache (e.g., GitHub.com becomes !github.com)

References:

PHP Composer

Composer packages are distributed as ZIP archives downloaded directly from source (usually GitHub/GitLab archives) or from Packagist.

Edge cases:

  • Composer itself doesn't define a package archive format; it downloads repository archives
  • composer archive creates a ZIP of your project
  • The vendor/ directory and vendor/autoload.php are generated locally, not distributed

References:

Elixir/Erlang Hex (.tar)

Hex packages are uncompressed tar archives containing metadata and a nested compressed tarball.

package-1.0.0.tar
├── VERSION           (ASCII: "3")
├── CHECKSUM          (SHA256 hex uppercase)
├── metadata.config   (Erlang term format)
└── contents.tar.gz   (actual package files)

Edge cases:

  • VERSION is a single ASCII digit (currently "3")
  • CHECKSUM is SHA256 of the other three files concatenated, hex-encoded uppercase
  • metadata.config uses Erlang's file:consult/1 format, not JSON or YAML; must be parsed with Erlang/Elixir term parser
  • The outer tar is uncompressed; only contents.tar.gz is compressed
  • Some packages only provide metadata.config without other metadata files

References:

Haskell Hackage (.tar.gz)

Hackage packages are standard gzipped tarballs produced by cabal sdist.

package-1.0.0.tar.gz
└── package-1.0.0/
    ├── package.cabal
    ├── LICENSE
    ├── Setup.hs (optional)
    └── src/

Edge cases:

  • The directory name must match package-version exactly
  • LICENSE file is required; cabal sdist fails without it
  • Only files listed in the .cabal file are included; add READMEs to extra-source-files
  • The .cabal file contains all metadata (no separate metadata file)
  • cabal sdist does validation; manually created tarballs may be rejected

References:

OCaml OPAM

OPAM doesn't define its own archive format; it downloads source archives from URLs specified in opam files.

opam repository structure:
packages/
└── package-name/
    └── package-name.version/
        ├── opam          (package definition)
        ├── files/        (patches, additional files)
        └── url           (legacy, now in opam file)

Edge cases:

  • The opam file is a custom DSL, not YAML/JSON
  • Source URLs point to external tarballs (GitHub releases, etc.)
  • Checksums are specified in the url section: url { src: "..." checksum: "sha256=..." }
  • The files/ directory contents are overlaid on the source after extraction
  • Repository indexes can be served over HTTP, git, or local filesystem

References:

Lua LuaRocks (.rock)

A .rock file is a ZIP archive containing a rockspec and source/binary files.

myrock-1.0-1.src.rock (ZIP)
├── myrock-1.0-1.rockspec
└── (source files or directories)

myrock-1.0-1.linux-x86_64.rock (ZIP)
├── myrock-1.0-1.rockspec
├── lua/
│   └── myrock.lua
└── lib/
    └── myrock.so

Filename format: {name}-{version}-{revision}.{platform}.rock

Edge cases:

  • Source rocks use .src.rock; binary rocks use platform like .linux-x86_64.rock
  • Pure Lua rocks use .all.rock (no native code, works everywhere)
  • The .rockspec file is valid Lua code (not YAML/JSON), though without access to Lua functions
  • Revision number is separate from version (e.g., 1.0-1 means version 1.0, rockspec revision 1)
  • Requires zip to be installed for creating rocks

References:

Perl CPAN (.tar.gz)

CPAN distributions are gzipped tarballs with specific metadata files.

Distribution-Name-1.0.tar.gz
└── Distribution-Name-1.0/
    ├── META.json        (v2+ metadata)
    ├── META.yml         (v1.4 metadata, legacy)
    ├── MANIFEST         (file listing)
    ├── Makefile.PL      (or Build.PL)
    └── lib/

Edge cases:

  • META.json and META.yml contain the same data in different formats; both should be included for compatibility
  • MANIFEST lists all files in the distribution; some tools validate against it
  • MYMETA.json/yml are generated at install time, not distributed; including them causes problems
  • Distribution name uses - (Distribution-Name), module names use :: (Distribution::Name)
  • META files historically didn't include runtime dependencies; that was in Makefile.PL/Build.PL

References:

Dart Pub

Pub packages are distributed via pub.dev; the package format is a standard directory structure uploaded as a tarball.

package/
├── pubspec.yaml
├── lib/
│   └── package.dart
├── bin/ (optional)
└── test/ (optional)

Edge cases:

  • Package names must be lowercase with underscores, valid Dart identifiers
  • pubspec.yaml is YAML with required name and version fields
  • Version is optional for local-only packages (defaults to 0.0.0)
  • SDK constraints specify minimum Dart/Flutter versions
  • dart pub publish performs secret detection and refuses to publish potential credential leaks

References:

C/C++ Conan

Conan uses internal .tgz archives for storage and transfer, but these are not meant for direct manipulation.

Recipe files (conan_export.tgz):
├── conanfile.py
├── conandata.yml (optional)
└── conanmanifest.txt

Binary package (conan_package.tgz):
├── conaninfo.txt
├── conanmanifest.txt
└── (compiled files)

Edge cases:

  • The .tgz format is internal and undocumented; not guaranteed stable
  • conanfile.py is Python code that defines how to build and package
  • conanfile.txt is a simplified INI-like format for consumers only
  • conanmanifest.txt contains checksums for verification
  • conaninfo.txt records settings used during build (compiler, OS, etc.)
  • Packages are identified by name/version@user/channel plus a package ID hash

References:


System Package Managers

Debian (.deb)

A .deb file is an ar archive containing exactly three members in order:

package_1.0-1_amd64.deb (ar archive)
├── debian-binary        (text: "2.0\n")
├── control.tar.xz       (or .gz, .zst)
└── data.tar.xz          (or .gz, .zst, .bz2, .lzma)

control.tar contains:

  • control (required) - package metadata
  • md5sums - checksums of installed files
  • conffiles - list of configuration files
  • preinst, postinst, prerm, postrm - maintainer scripts
  • triggers, shlibs, symbols - optional metadata

Edge cases:

  • Members must appear in exactly this order: debian-binary, control.tar, data.tar
  • The ar archive has a magic value !<arch> and uses the common ar format (no long filename extensions)
  • Filename limit is 15 characters (16 bytes minus trailing slash)
  • debian-binary must be first or the package is invalid
  • Supported compressions vary by dpkg version: xz since 1.15.6, zstd since 1.19.0
  • Supported tar formats: v7, pre-POSIX ustar, subset of GNU tar, POSIX ustar

References:

RPM (.rpm)

An RPM file has four sections: lead, signature, header, and payload.

package-1.0-1.x86_64.rpm
├── Lead (96 bytes magic + info)
├── Signature (digital signatures)
├── Header (all metadata)
└── Payload (compressed cpio archive)

The payload is a gzip-compressed cpio archive (SVR4/newc format with CRC).

Edge cases:

  • All integers are big-endian (network byte order)
  • cpio has a 4 GiB file size limit; RPM 4.12+ uses an extended format (07070X magic) for larger files
  • The extended format stores only file index in cpio headers; actual metadata is in the RPM header
  • Metadata in cpio headers must match the RPM header
  • The lead is largely vestigial but must be present

References:

Alpine APK (.apk)

An Alpine package is a concatenation of three gzipped tar segments:

package-1.0-r0.apk (concatenated tar.gz segments)
├── signature.tar.gz (optional, contains .SIGN.*)
├── control.tar.gz   (contains .PKGINFO, etc.)
└── data.tar.gz      (package contents)

Edge cases:

  • Tar segments, not a single tar: segments lack the standard end-of-archive null records, allowing concatenation
  • Control files are prefixed with . for historical reasons (.PKGINFO, .pre-install, etc.)
  • Signature file must be first if present, named .SIGN.<type>.<keyname> (type is DSA or RSA)
  • APKINDEX.tar.gz (repository index) contains DESCRIPTION and APKINDEX files
  • APKINDEX entries use single-letter prefixes (P: for package name, V: for version, etc.)
  • SHA256 hashes are base64 encoded with Q1 prefix (not hex)
  • Three format versions exist: v1 (deprecated), v2 (current), v3 (in development)

References:

FreeBSD pkg (.pkg, .txz)

A FreeBSD package is a tar archive (compressed with xz by default) with JSON manifests.

package-1.0.pkg (tar.xz)
├── +COMPACT_MANIFEST
├── +MANIFEST
└── usr/local/...

Edge cases:

  • Special files start with + and must come first
  • +COMPACT_MANIFEST comes before +MANIFEST (both are JSON)
  • +COMPACT_MANIFEST omits file lists, checksums, scripts; used for repository catalogs to avoid transferring large file lists over slow connections
  • +MANIFEST includes SHA256 checksums for all files
  • Supported compressions: xz (default), zstd, bzip2, gzip, or uncompressed

References:

Snap (.snap)

A snap is a SquashFS filesystem image.

package_1.0_amd64.snap (SquashFS image)
├── meta/
│   └── snap.yaml
├── bin/
├── lib/
└── usr/

Edge cases:

  • SquashFS is a read-only compressed filesystem, not an archive; it's mounted at runtime
  • Compression can be xz (smaller) or lzo (faster startup)
  • snap.yaml in meta/ contains package metadata
  • Unpacking requires unsquashfs, not standard archive tools
  • The filesystem layout mirrors a typical Linux root

References:

Flatpak

Flatpak uses OSTree, a git-like content-addressed storage system for binaries.

Edge cases:

  • Not a traditional archive format; uses OSTree repositories
  • Refs follow <app|runtime>/<id>/<arch>/<version> naming (e.g., app/org.gnome.gedit/x86_64/stable)
  • Single-file bundles exist but are OSTree static deltas
  • Content is deduplicated via content-addressed storage
  • The summary file is like rpm's repodata; must be regenerated when packages change

References:

Homebrew Bottles

Bottles are gzipped tarballs with formula-specific naming.

Filename format: <formula>-<version>.<os_version>.bottle[.<rebuild>].tar.gz

Examples:

  • wget-1.21.3.arm64_monterey.bottle.tar.gz
  • openssl-3.0.7.ventura.bottle.1.tar.gz (rebuild 1)

Edge cases:

  • Exactly one hyphen between formula name and version; brew bottle sometimes generates two hyphens which must be fixed
  • Contains the formula definition at <formula>/<version>/.brew/<formula>.rb
  • Bottles are built for specific macOS versions (ventura, sonoma, etc.) and architectures
  • Cache files use SHA256 hash prefixes: 544b4d73...--wget-1.21.3.monterey.bottle.tar.gz

References:

Arch Linux Pacman (.pkg.tar.zst)

Pacman packages are zstd-compressed tarballs with metadata files.

package-1.0-1-x86_64.pkg.tar.zst
├── .PKGINFO        (package metadata)
├── .BUILDINFO      (build reproducibility info)
├── .MTREE          (file hashes and permissions)
├── .INSTALL        (optional install scripts)
├── .Changelog      (optional)
└── usr/
    └── bin/

Edge cases:

  • Metadata files are prefixed with . and must come first
  • .PKGINFO is key=value format with duplicate keys for multiple values (simple to parse)
  • .MTREE is gzip-compressed inside the already-compressed package; contains SHA256 hashes, permissions, timestamps
  • .BUILDINFO enables reproducible builds; records exact build environment
  • Compression changed: gzip (historical) -> xz (2010) -> zstd (2019)
  • The -1 in 1.0-1 is the pkgrel (packaging revision), separate from upstream version

References:

Gentoo Portage (.tbz2, .gpkg.tar)

Gentoo has two binary package formats: legacy XPAK (.tbz2) and modern GPKG (.gpkg.tar).

XPAK format (.tbz2):

package-1.0.tbz2
└── [bzip2 tarball] + [XPAK metadata trailer]

GPKG format (.gpkg.tar) (default since Portage 3.0.36):

package-1.0.gpkg.tar
├── gpkg-1                       (empty, format marker)
├── Manifest                     (checksums)
├── package-1.0/
│   ├── metadata.tar{.xz}        (ebuild, environment)
│   └── image.tar{.xz}           (installed files)
└── *.sig                        (optional OpenPGP signatures)

Edge cases:

  • XPAK is a trailer format: metadata is appended after the tarball, not inside it
  • XPAK stores the ebuild, USE flags, environment, CFLAGS/CXXFLAGS used to build
  • GPKG is extractable with plain tar; XPAK requires special tools (qtbz2, qxpak)
  • GPKG supports OpenPGP signatures for integrity verification
  • Use BINPKG_FORMAT=gpkg or BINPKG_FORMAT=xpak in make.conf to choose format

References:

Void Linux XBPS (.xbps)

XBPS packages are compressed archives with plist metadata.

package-1.0_1.x86_64.xbps
├── props.plist     (package properties)
├── files.plist     (file list with hashes)
├── INSTALL         (optional install script)
├── REMOVE          (optional remove script)
└── (package files)

Edge cases:

  • Metadata uses plist format (Apple property lists), parsed with proplib
  • props.plist includes installed_size, dependencies, etc.
  • files.plist contains SHA256 hashes for all files
  • Supported compressions: gzip, bzip2, lz4, xz, zstd (default)
  • Package database is stored in /var/db/xbps/pkgdb-0.38.plist
  • .xbps.sig files contain RSA signatures

References:

Nix (NAR - Nix Archive)

NAR is a custom archive format designed for deterministic, reproducible builds. It is not based on tar or zip.

Edge cases:

  • Not tar or zip: custom binary format with length-prefixed strings
  • Strips non-deterministic metadata: no timestamps, no owner/group IDs, no permissions except executable bit
  • Designed for content-addressing: the hash of a NAR is used as its store path
  • Format: nix-archive-1 header, then recursive file/directory/symlink serialization
  • Strings are 64-bit little-endian length-prefixed, padded to 8-byte boundary
  • Directory entries must be sorted by name (for determinism)
  • Use nix-store --dump and nix-store --restore to create/extract
  • No compression in NAR itself; compression applied separately

References:


Container and Infrastructure Formats

OCI Container Images

OCI images are layered tarballs with JSON manifests, used by Docker, Podman, and other container runtimes.

image/
├── index.json              (points to manifests)
├── oci-layout              ({"imageLayoutVersion": "1.0.0"})
├── blobs/
│   └── sha256/
│       ├── abc123...       (manifest.json)
│       ├── def456...       (config.json)
│       ├── 789abc...       (layer.tar.gz)
│       └── ...

Manifest points to config and layers:

{
  "schemaVersion": 2,
  "config": { "digest": "sha256:...", "mediaType": "...+json" },
  "layers": [
    { "digest": "sha256:...", "mediaType": "...tar+gzip" }
  ]
}

Edge cases:

  • Layers are applied in order: index 0 is base, subsequent layers overlay
  • Layers are tar archives (optionally gzip/zstd compressed) containing filesystem diffs
  • Whiteout files (.wh.<filename>) mark deleted files from previous layers
  • Opaque whiteouts (.wh..wh..opq) hide all files in a directory from lower layers
  • Layer media types: application/vnd.oci.image.layer.v1.tar, .tar+gzip, .tar+zstd
  • schemaVersion must be 2 for backwards compatibility with Docker
  • Content is addressed by SHA256 digest

References:

Helm Charts (.tgz)

Helm charts are gzipped tarballs with a specific directory structure for Kubernetes deployments.

mychart-1.0.0.tgz
└── mychart/
    ├── Chart.yaml          (chart metadata, required)
    ├── values.yaml         (default configuration)
    ├── values.schema.json  (optional JSON Schema)
    ├── charts/             (dependency charts)
    ├── crds/               (Custom Resource Definitions)
    └── templates/
        ├── deployment.yaml
        ├── service.yaml
        └── _helpers.tpl

Edge cases:

  • Chart.yaml is required; contains name, version, appVersion, dependencies
  • templates/ files are Go templates, not plain YAML; rendered with Helm
  • values.yaml provides defaults; users override via --set or -f values.yaml
  • Files named _*.tpl are template helpers, not rendered directly
  • charts/ contains dependencies as nested .tgz files or unpacked directories
  • Unknown fields in Chart.yaml are silently dropped (can't pass arbitrary data to templates)
  • Template files should use .yaml extension; .tpl for helpers that produce no output

References:

Terraform Providers (.zip)

Terraform providers are ZIP archives containing a single Go binary and metadata.

terraform-provider-aws_4.0.0_linux_amd64.zip
└── terraform-provider-aws_v4.0.0_x5

Registry release artifacts:
├── terraform-provider-NAME_VERSION_OS_ARCH.zip     (multiple)
├── terraform-provider-NAME_VERSION_manifest.json
├── terraform-provider-NAME_VERSION_SHA256SUMS
└── terraform-provider-NAME_VERSION_SHA256SUMS.sig

Edge cases:

  • Binary name format: terraform-provider-{NAME}_v{VERSION} (note the v prefix in binary, not in zip)
  • ZIP name format: terraform-provider-{NAME}_{VERSION}_{OS}_{ARCH}.zip
  • manifest.json specifies protocol versions: {"version": 1, "metadata": {"protocol_versions": ["6.0"]}}
  • Provider protocol uses MessagePack or JSON for wire format
  • SHA256SUMS must be GPG-signed for registry publication
  • Terraform caches providers in two layouts: packed (.zip) and unpacked (extracted)
  • Unpacked path: HOSTNAME/NAMESPACE/TYPE/VERSION/TARGET/

References:


Common Gotchas

Nested archives: gem (tar containing gzipped tars), deb (ar containing tars), Hex (tar containing tar.gz)

Concatenated archives: Alpine apk (multiple tar.gz segments concatenated)

Trailer metadata: Gentoo XPAK appends metadata after the tarball, not inside it

Not actually archives: Snap (SquashFS image), Flatpak (OSTree repo), Nix NAR (custom format)

ZIP-based but not plain ZIP: NuGet (OPC with relationship metadata), wheel (with required RECORD file), LuaRocks .rock

Order matters: deb members, FreeBSD +MANIFEST files, JAR MANIFEST.MF position, Pacman metadata files

Case sensitivity: Go modules reject case-only filename differences; npm/pip don't care

Hash algorithm transitions: npm SHA1 to SHA512, gem MD5 to SHA256/512

Filename encoding: tar variants handle UTF-8 differently; pax standardizes it

Compression format changes over time: Pacman (gz -> xz -> zstd), deb (gz -> xz -> zstd options)

Config files that are code: LuaRocks .rockspec (Lua), Conan conanfile.py (Python), Helm templates (Go templates)

Erlang-specific formats: Hex metadata.config uses Erlang term format, not JSON/YAML

Layered formats: OCI images layer tarballs with whiteout files for deletions

Determinism requirements: Nix NAR strips timestamps/ownership, Go modules forbid case collisions


Underlying Archive Formats

Most package managers build on tar or zip, but the variants matter.

Tar Variants

The tar format has several incompatible variants with different limitations:

v7 (Original)

  • 99-character filename limit
  • 8 GiB file size limit
  • No user/group names, only numeric IDs

UStar (POSIX.1-1988)

  • 255-character filename limit (split between prefix and name fields, so effective limit varies)
  • 8 GiB file size limit
  • Adds user/group names

GNU tar

  • Unlimited filename/path length (uses extension headers with typeflag L/K)
  • Files over 8 GiB use base-256 encoding in size field (sets high bit)
  • Incompatible with strict POSIX implementations

PAX (POSIX.1-2001)

  • Unlimited filename/path length via extended headers
  • Unlimited file size
  • UTF-8 metadata encoding
  • Sub-second timestamp precision
  • Backwards compatible with ustar readers (they see extended headers as regular files)

npm uses pax headers for long filenames. Most modern package managers default to or support pax.

References:


License

CC0 1.0 Universal

archives
package-management
package-manager