Skip to main content

Dependency Cache

Save and restore dependency caches between pipeline runs.

Reference: loha/cache@v2

Category: build

Runtime: curlimages/curl:8.5.0 (non-root, digest-pinned)

License: Apache-2.0

When to use​

Use this step bracketing your install command to skip re-downloading dependencies on every run. Place a restore action before the install and a save action after. Cache hits collapse a 2-minute install to a 5-second extract.

Parameters​

ParameterTypeDefaultRequiredDescription
ACTIONenum: restore, save—YesCache action: restore downloads cached files, save uploads current files.
PATHSstring—YesComma-separated directories to cache (e.g., node_modules,.npm or venv,.pip-cache).
KEY_FILESstringpackage-lock.jsonNoComma-separated file paths for cache key hash (e.g., package-lock.json,Cargo.lock).
CACHE_PREFIXstringdefaultNoPrefix for the cache key. Use to namespace caches by project or environment.
FAIL_ON_MISSenum: true, falsefalseNoFail the step if no cache is found during restore.
WORKING_DIRECTORYstring.NoDirectory to operate in, relative to workspace root.

Workspaces​

  • source (required): bound from pipeline workspace source

Results​

ResultDescription
CACHE_HITtrue if cache was restored from storage, false on cache miss or save action
CACHE_KEYComputed cache key derived from the hash of KEY_FILES

How it works​

Cache key is {CACHE_PREFIX}-{sha256(KEY_FILES)[:16]}. When any file in KEY_FILES changes, the SHA changes, the restore misses, and a fresh install runs. Save then writes a new entry under the new key. Saves are idempotent per key, so concurrent runs cannot corrupt an entry.

Storage is provided by Loha. There is no bucket to create, no endpoint, and no credentials — enable caching in your repository settings and the step works. The build never holds a storage credential: it asks Loha for a URL naming one object and transfers it directly.

Which cache a build can see​

Caches are scoped per repository and per branch, and the rules are worth knowing because they decide when you get a hit:

  • A build reads its own branch's cache first, then falls back to your default branch's. So a new branch or a pull request starts warm rather than cold.
  • A build writes only its own branch's cache. A branch can never overwrite what your default branch later restores.

That second rule is a security boundary, not a convenience. Without it, anyone able to push a branch could plant content that every later build on main would download and execute. It does mean a long-lived branch keeps its own copy, so use CACHE_PREFIX to separate genuinely different caches rather than to separate branches — that already happens.

Examples​

npm install with cache​

steps:
- name: restore-cache
uses: loha/cache@v2
with:
ACTION: 'restore'
PATHS: 'node_modules,.npm'
KEY_FILES: 'package-lock.json'
CACHE_PREFIX: 'npm'

- name: install
uses: loha/run-command@v1
with:
COMMAND: 'npm ci'

- name: save-cache
uses: loha/cache@v2
with:
ACTION: 'save'
PATHS: 'node_modules,.npm'
KEY_FILES: 'package-lock.json'
CACHE_PREFIX: 'npm'

pip install with cache​

steps:
- name: restore-cache
uses: loha/cache@v2
with:
ACTION: 'restore'
PATHS: '.venv,.pip-cache'
KEY_FILES: 'requirements.txt,requirements-dev.txt'
CACHE_PREFIX: 'pip'

- name: install
uses: loha/run-command@v1
with:
COMMAND: 'pip install -r requirements.txt --cache-dir .pip-cache'
IMAGE: 'python:3.12-slim'

- name: save-cache
uses: loha/cache@v2
with:
ACTION: 'save'
PATHS: '.venv,.pip-cache'
KEY_FILES: 'requirements.txt,requirements-dev.txt'
CACHE_PREFIX: 'pip'

cargo build with cache​

steps:
- name: restore-cache
uses: loha/cache@v2
with:
ACTION: 'restore'
PATHS: 'target,.cargo/registry'
KEY_FILES: 'Cargo.lock'
CACHE_PREFIX: 'cargo'

- name: build
uses: loha/run-command@v1
with:
COMMAND: 'cargo build --release'
IMAGE: 'rust:1.76-alpine'

- name: save-cache
uses: loha/cache@v2
with:
ACTION: 'save'
PATHS: 'target,.cargo/registry'
KEY_FILES: 'Cargo.lock'
CACHE_PREFIX: 'cargo'

Troubleshooting​

Problem: Cache miss on every run.

Solution: Verify KEY_FILES paths exist relative to WORKING_DIRECTORY at pipeline runtime; a typo silently produces a different hash every run.


Problem: Cache restored but install still re-downloads everything.

Solution: Make sure PATHS covers every directory the package manager populates: for npm that is both node_modules and .npm; for pip it is .venv and .pip-cache.


Problem: "build caching is not available for this run" — the step was skipped.

Solution: Build cache is off for this repository, the plan does not include caching, the run had no repository to scope a cache to (a manual run without one), or this deployment cannot cache. The step does nothing and the build continues uncached; the build warnings say which it was. A workspace admin turns it on under Repository settings → Build cache. Set FAIL_ON_MISS: true on the restore if a build must not run without its cache.


Problem: "cache save failed" in the save step's log, but the build is green.

Solution: The save could not reach cache storage, so nothing was stored and the next build starts cold. The build itself is unaffected. If every save says this, the deployment's cache storage is misconfigured — tell your Loha administrator. If the message shows CACHE_BUDGET_EXCEEDED, the workspace has reached its build cache size limit; older entries are removed hourly, or a Loha administrator can raise the limit.


Problem: A pull request gets a hit but a push to a long-lived branch does not.

Solution: Expected. A branch reads its own cache first and the default branch second, and writes only its own. The first build on a new branch warms it from the default branch; the second is a hit.