CircleCI Workspaces: Compile Once, Reuse Across Jobs
Workspaces let you compile once and reuse output across jobs in a single workflow. Here is when to use persist_to_workspace, how to wire a build-test-deploy pipeline, and why workspaces are not a cache.
30 Aug 2026, 14:27 UTC

Your frontend build takes three minutes. Your test job rebuilds it. Your deploy job rebuilds it again. By the time the pipeline finishes, you have spent nine minutes compiling the same bundle. CircleCI workspaces exist to eliminate that rebuild tax: one job compiles the output, persists it, and downstream jobs in the same workflow attach it without recompiling.
The distinction that trips up teams is this: workspaces pass artifacts between jobs in a single workflow run. Caches store dependencies across workflow runs. Using one for the other's job wastes time and storage.
How workspaces move files between jobs
A workspace is a temporary directory that a job can write to and later jobs can read. Two steps control the flow:
persist_to_workspacewrites files from a job under a root directory. Paths are relative to the job'sworking_directoryunless you provide an absolute root.attach_workspacerestores those files in a downstream job. The files appear at the same relative paths under the new job'sworking_directory.
Workspaces are scoped to a single workflow run. They are not shared across pipelines, branches, or projects. That makes them ideal for compiled output—bundled JavaScript, compiled binaries, or generated assets—that must exist in test and deploy jobs but changes on every commit.
Workspaces versus caches: choose by change frequency
The most common mistake is treating a workspace as a cache. If your dependency tree rarely changes, a cache is the correct tool. If your build output changes on every run, a workspace is.
| Behavior | Workspace | Cache |
|---|---|---|
| Lifetime | One workflow run | Across workflow runs |
| Best for | Build outputs, test reports, compiled assets | Dependencies: node_modules, Maven artifacts, pip packages |
| Keying | Not keyed; refreshed every run | Keyed by file checksums or template strings |
| Direction | Persist then attach within the same workflow | Save then restore across separate executions |
If you persist node_modules to a workspace, you pay storage and transfer costs on every run even when the dependencies have not changed. If you cache a compiled bundle, you may deploy stale output because the cache key does not capture source changes reliably.
A build-test-deploy example
The following configuration uses three jobs. The build job compiles a frontend and persists the dist/ directory. The test and deploy jobs attach that workspace instead of rebuilding. Run these steps inside their respective CircleCI jobs; no special host permissions are required beyond the job's default executor access.
version: 2.1
jobs:
build:
docker:
- image: cimg/node:20.0
working_directory: ~/repo
steps:
- checkout
- run: npm ci
- run: npm run build
- persist_to_workspace:
root: ~/repo
paths:
- dist
test:
docker:
- image: cimg/node:20.0
working_directory: ~/repo
steps:
- attach_workspace:
at: ~/repo
- run:
name: Check artifact presence
command: ls ~/repo/dist
- run: npm test
deploy:
docker:
- image: cimg/node:20.0
working_directory: ~/repo
steps:
- attach_workspace:
at: ~/repo
- run:
name: Upload artifacts
command: ./upload-script.sh ~/repo/dist
workflows:
build-test-deploy:
jobs:
- build
- test:
requires:
- build
- deploy:
requires:
- test
Replace ~/repo with your project's working directory and ./upload-script.sh with your actual upload command. The ls step in the test job is a diagnostic: if it fails, the workspace attachment did not restore the expected files, and you should compare the root value in persist_to_workspace with the at value in attach_workspace.
If your dependencies rarely change, add a restore_cache step to the build job so npm ci does not download packages on every run. Keep the workspace for dist/ only.
Trade-offs, verification, and limits
Workspaces are not free. They consume storage and add transfer time between jobs. Passing large binaries or thousands of small files can slow the critical path, especially when the test and deploy jobs run on separate executors.
They also introduce hidden coupling. If the build job fails, the test and deploy jobs are skipped because they require the workspace. That is usually desirable, but it changes failure semantics compared to independent jobs that could still provide partial signal.
To verify the setup works, the ls diagnostic above is the fastest check. You can also open the CircleCI job UI and inspect the workspace attach and persist status messages to confirm the steps executed. Run the pipeline twice, change a source file between runs, and confirm the new output appears in the test job. A workspace is refreshed per run; if you see stale files, you are likely looking at a cache instead.
Workspaces are ephemeral. They disappear when the workflow ends. Do not use them as long-term artifact storage. If you need an audit trail or want to let users download build output after the pipeline finishes, upload the artifacts to external object storage or a dedicated artifact repository during the deploy job.
The rule of thumb: if the artifact changes on every commit, persist it to a workspace. If it changes rarely, cache it. If it must outlive the workflow, store it externally.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.