Dependency Caching
When using Pulumi-managed workflow runners, you can speed up deployments with dependency caching.
Dependency caching covers more than your language packages. A cache entry bundles three things a deployment would otherwise download every run: your language dependencies (including the Pulumi SDKs, which are cached as ordinary package-manager packages), the Pulumi resource plugins your program uses, and any policy packs. In practice the plugins are usually the largest and slowest to fetch, so caching them is where most of the time savings come from.
The mechanism is straightforward. On the first deployment, the runner detects these items — using your lock files for the language dependencies — archives them, and stores the archive in blob storage. On later deployments, the runner pulls that archive down and unpacks it, saving the time it would otherwise spend downloading everything again. When your dependencies change, the runner invalidates the old cache and creates a new one.
Enabling dependency caching
Dependency caching is off by default. You can enable it per stack:
- Pulumi Cloud: on the stack’s Settings → Deploy page, turn on Enable dependency caching.
- Deployment settings: set
cacheOptions.enabletotrue. - REST API: set the same
cacheOptions.enablefield on the stack’s deployment settings.
Dependency caching is not available on stacks backed by Terraform state.
Because caching is a per-stack setting but the cache itself is shared across the project, stacks in the same project can have it enabled and disabled independently:
- A stack with caching disabled runs no cache steps at all. It neither reads from nor writes to the project’s cache, so it can’t benefit from it and can’t disturb it.
- A stack with caching enabled reads and writes the shared project cache, keyed by the contents of its lock files.
- Two caching-enabled stacks with identical lock files share the same cache entries and warm the cache for each other.
- Disabling caching on a stack does not delete anything. Clearing the cache is a separate, project-wide action.
What gets cached
In addition to the dependencies below, every cache entry includes the downloaded Pulumi plugins (~/.pulumi/plugins) and policy packs (~/.pulumi/policies). Language SDKs are not handled specially — they are cached as part of your normal package manager dependencies.
| Runtime | Detected using | Cached directories |
|---|---|---|
| Python (pip) | requirements.txt | The venv directory beside it, and ~/.cache/pip |
| Python (uv) | uv.lock | The uv cache directory |
| Python (Poetry) | poetry.lock | The in-project .venv directory, and Poetry’s cache directory |
| Node.js (npm) | package-lock.json | node_modules and the npm cache |
| Node.js (Yarn) | yarn.lock | node_modules and the Yarn cache |
| Node.js (pnpm) | pnpm-lock.yaml | The pnpm store (not node_modules) |
| .NET | packages.lock.json, or .csproj, .fsproj, and .vbproj files | ~/.nuget/packages |
| Go | go.sum, falling back to go.mod | The module cache and the build cache |
Lock files are located by searching the deployment’s working directory and then each of its parent directories, so a lock file at the root of a monorepo works even when Pulumi.yaml lives in a subdirectory. The .NET runtime is the exception: it searches only the working directory and below.
A few constraints are worth knowing before you turn caching on:
- Java and YAML are not supported. Only Python, Node.js, .NET, and Go have caching support. Enabling the setting on a Java or YAML stack is harmless but has no effect — the cache steps run, log
Skipping caching step: unsupported runtime, and nothing is cached, including plugins. - Python projects must have exactly one lock file. If more than one of
requirements.txt,uv.lock, andpoetry.lockis present, Pulumi can’t tell which package manager you intend to use and skips caching entirely — including plugins. Poetry is supported natively, so do not export arequirements.txtalongside yourpoetry.lock. - For pip, the virtual environment must be named
venv. A virtual environment named.venvorenvis not cached. - For Node.js, a
packageManagerfield inpackage.jsondetermines the package manager. Without that field, Pulumi infers it from the lock file, and multiple lock files cause caching to be skipped. - Custom executor images must include your package manager. Pulumi asks the package manager (
npm,yarn,pnpm,uv, orpoetry) where its cache lives, so the binary has to be on thePATH. If it isn’t, caching is skipped. See Deployment execution environment. - Overriding
PULUMI_HOMEdisables plugin caching. The plugin and policy paths are fixed at~/.pulumi, so if you pointPULUMI_HOMEelsewhere with a custom environment variable, those directories are not cached. Your language dependencies are still cached.
Verifying and troubleshooting
To confirm caching is working, open a deployment’s logs and read the Restore cache and Save cache steps. Neither step prints the words “hit” or “miss,” so look for these lines instead.
In the Restore cache step:
| Log line | What it means |
|---|---|
Detected project runtime: python | Pulumi identified the runtime. |
Calculated cache entry with key pip-<hash>... | Pulumi computed the cache key from your lock file. |
Decompressing and extracting cache... followed by Cache restored successfully | Cache hit. Dependencies were restored. |
No cache to restore, skipping... | Cache miss. Either this is the first deployment, or your lock file changed. |
Skipping caching step: <reason> | Nothing was cached. The reason explains why — usually an unsupported runtime or a missing or ambiguous lock file. |
In the Save cache step:
| Log line | What it means |
|---|---|
Got cache entry key <key>..., capturing paths: [...] | The directories about to be archived. |
Compressed file "<key>.tar.zst", size: ... followed by Uploaded ... successfully | A new cache entry was written. |
Key "<key>" already cached, skipping... | Your dependencies were unchanged, so nothing was re-uploaded. This is the normal steady state. |
Warning: cache entry ... exceeded size limit ..., skipping... | The archive was larger than the size limit and was not saved. |
Directory "<path>" not found, skipping... | An expected directory didn’t exist and was left out of the archive. |
If a deployment isn’t getting faster, the most common cause is a Skipping caching step line in the Restore cache step. Read the reason it gives.
Clearing the cache
Pulumi invalidates the cache automatically whenever your lock files change, so clearing it by hand is rarely necessary. If you need to force a cold rebuild, you can:
- Pulumi Cloud: use the Clear cache action on the stack’s Deployments page.
- REST API: send
DELETE /api/stacks/{organization}/{project}/{stack}/deployments/cache.
Both clear the cache for the entire project, not just the selected stack. Every stack in the project will rebuild its cache on its next deployment.
Limits and constraints
- Caches are shared at the project level, so all stacks within a project can share the same cache. Caches are fully isolated and never shared between organizations.
- Cache archives are limited to 5 GB compressed. A larger archive is skipped with a warning rather than failing the deployment.
- A cache failure never fails a deployment. Every problem with caching is a warning and a skip, which is why an unexpectedly slow deployment is worth checking against the log lines above.
- Cache keys are matched exactly. Unlike some CI systems, there is no partial or fallback key match, so any change to a lock file produces a completely cold cache.
- Caches that go unread for roughly a month are evicted. A cache in regular use stays warm.
- The Save cache step does not run when a
destroyoperation is configured to delete the stack afterward.