Archives#

archive demo

This tutorial walks through packaging a workspace into a portable archive and restoring it on another machine or in CI.

Prerequisites#

  • conda (>= 26.3) with the conda-workspaces plugin installed

  • A workspace with a manifest (conda.toml, pixi.toml, or pyproject.toml) and conda.lock

Create a workspace archive#

An archive bundles your manifest, lockfile, and source files into a single .tar.zst, .tar.gz, or .tar.bz2 file. In a git repo, only tracked files are included.

conda workspace archive -o my-project.tar.zst

The resulting file contains everything needed to reproduce the workspace elsewhere:

my-project.tar.zst
  conda.toml
  conda.lock
  src/
    app.py
    ...

If no -o is given, the archive is named after the workspace (<name>.tar.zst) and placed in the project root. The workspace name must be a single filename segment for this default; pass -o/--output when you want to write the archive somewhere else.

Use gzip compression#

For broader compatibility, use a .tar.gz extension:

conda workspace archive -o my-project.tar.gz

Exclude files#

Some files should not be included in archives. Configure permanent exclusions in your manifest:

[workspace.archive]
exclude = ["docs/**", "*.log", "data/raw/**"]

Or pass one-off exclusions on the command line:

conda workspace archive --exclude "benchmarks/**" --exclude "*.csv"

Both sources are combined. Built-in exclusions (.git, __pycache__, .conda/envs, .pixi, and common credential material such as .env, .ssh, .aws, and .npmrc) always apply regardless of configuration.

Extract an archive#

On the receiving end, extract the archive with:

conda workspace unarchive my-project.tar.zst

This creates a my-project/ directory (derived from the archive filename) containing the full workspace. To choose a different location:

conda workspace unarchive my-project.tar.zst --target /path/to/destination

The target directory must be empty or absent. unarchive refuses to extract over existing files.

After extraction, install the environments from the lockfile:

cd my-project
conda workspace install --locked

Extract and install in one step#

archive install demo

Pass --install to extract the archive and install all environments from the lockfile in a single command:

conda workspace unarchive my-project.tar.zst --target /path/to/destination --install

This is equivalent to extracting, changing into the directory, and running conda workspace install --locked, but without the extra steps.

Install an environment to an explicit prefix#

Use -e/--environment with --prefix to install one archived workspace environment to a final runtime prefix:

conda workspace unarchive my-project.tar.zst \
  --target /tmp/workspace \
  --install \
  -e runtime \
  --prefix /opt/runtime

This extracts the workspace into /tmp/workspace and installs the runtime environment from the lockfile directly at /opt/runtime.

For image builders or staged filesystem roots, add --dest:

conda workspace unarchive my-project.tar.zst \
  --target /tmp/workspace \
  --install \
  --dest /tmp/rootfs \
  -e runtime \
  --prefix /opt/runtime

With --dest, files are written under /tmp/rootfs/opt/runtime, but the environment’s final runtime prefix remains /opt/runtime. unarchive warns if any installed files still reference the physical staging prefix.

Lock before archiving#

If your lockfile is out of date or does not exist yet, pass --lock to solve and write conda.lock before creating the archive:

conda workspace archive --lock

This is equivalent to running conda workspace lock followed by conda workspace archive, but in a single command.

Write a receipt for verification#

archive receipt demo

Pass --receipt to write an external JSON receipt next to the archive:

conda workspace archive --lock --receipt -o my-project.tar.zst

This creates:

my-project.tar.zst
my-project.tar.zst.receipt.json

The receipt is an in-toto Statement that records SHA-256 digests for the archive, the manifest, and conda.lock, plus a per-environment package inventory from the lockfile. Use an explicit receipt path when your release process stores attestations separately:

conda workspace archive \
  --receipt attestations/my-project.receipt.json \
  -o dist/my-project.tar.zst

On the receiving side, pass --receipt to verify before the workspace is moved into place:

conda workspace unarchive my-project.tar.zst --receipt --target /tmp/verified

Or point to the explicit receipt path:

conda workspace unarchive dist/my-project.tar.zst \
  --receipt attestations/my-project.receipt.json \
  --target /tmp/verified

Verified extraction checks the archive digest before extraction, then extracts to a temporary staging directory, verifies the extracted manifest, lockfile, and package inventory, and only then moves the workspace to the target. The target must be empty or absent.

Receipts require both the manifest and conda.lock to be present in the archive. If include, exclude, or --exclude filters would remove either file, conda workspace archive --receipt fails before writing the archive or receipt.

For stricter package inventory checks, require every compared package record to include SHA-256:

conda workspace unarchive my-project.tar.zst \
  --receipt \
  --require-sha256 \
  --target /tmp/verified

Bundle packages for offline use#

archive bundle demo

When the target machine has no internet access, use --bundle to include all resolved conda package archives (.conda or .tar.bz2) inside the archive.

You need a lockfile first. Pass --lock to generate one automatically, or run conda workspace lock beforehand.

conda workspace archive --lock --bundle -o my-project-offline.tar.zst

This adds a packages/ directory inside the archive. Package hashes are verified against the lockfile before bundling.

On the receiving end, pair bundled archives with a receipt when you want conda workspace unarchive to verify the external integrity record and copy bundled packages into the local conda cache before installation:

conda workspace archive --lock --bundle --receipt -o my-project-offline.tar.zst
conda workspace unarchive my-project-offline.tar.zst --receipt
# packages are primed into the conda cache after receipt verification
cd my-project-offline
conda workspace install --locked

Without a verified receipt, unarchive extracts the files but skips package cache priming. Pass --no-install when you only want the files:

conda workspace unarchive my-project-offline.tar.zst --no-install

Security#

Archives are extracted with path traversal protection. Every member is validated before extraction: absolute paths, .. components, symlinks escaping the target directory, and special file types (device nodes, FIFOs) are all rejected. On Python 3.12+ the filter="data" parameter provides additional defense-in-depth.

When --bundle is used, package hashes are verified against the lockfile’s SHA256 entries at archive creation and before receipt-verified cache priming. Packages without a SHA256 entry in the lockfile are rejected instead of being copied into the conda package cache.

Trust model for bundled archives#

A bundled archive is self-consistent: the package hashes match the lockfile that ships inside the archive. However, the lockfile itself is not externally signed. If the archive came from an untrusted source, the lockfile inside it could have been tampered with to match altered packages.

To guard against this:

  • Obtain archives only from trusted sources.

  • Use --receipt and distribute the receipt through a separate trusted channel when bundled packages should prime a conda package cache.

  • Use conda workspace install --locked so the solver does not silently add packages beyond what the lockfile specifies.

Trust model for receipts#

A receipt is an integrity record, not a signature. It can detect that an archive, extracted manifest, extracted lockfile, or lockfile package inventory differs from the receipt. It does not prove who created the receipt.

For provenance-sensitive workflows, distribute the receipt through a separate trusted channel or sign it with your release signing system. Pair --receipt with --bundle when you want one archive to carry the package artifacts and one sidecar receipt to bind the archive to the lockfile inventory.

Next steps#