CircleCI Workspaces: Passing Build Artifacts Between Jobs

2 min readCloud Infrastructure

Workspaces let CircleCI jobs share files within a single workflow. A build job compiles binaries and persists them to the workspace; downstream test and deploy jobs attach the workspace and use the same artifacts. Workspaces are mutable within the workflow but don't persist across runs — use caching for that.

ci-cdcircleci

Workspaces vs caching vs artifacts

| | Workspace | Cache | Artifacts | |---|---|---|---| | Scope | Within one workflow | Across workflow runs | Stored after workflow | | Mutable | Yes — jobs can append | No (immutable per key) | No | | Use case | Pass compiled output between jobs | Reuse dependencies across runs | Download results, test reports | | Retention | Workflow lifetime + 15 days for rerun | 15 days per access | 30 days (configurable) |

Workspaces solve the job isolation problem: by default, each CircleCI job runs in a fresh container with no shared filesystem. If a build job compiles binaries that a test job needs, workspaces carry those files across.

persist_to_workspace and attach_workspace

version: 2.1

jobs:
  build:
    docker:
      - image: cimg/node:20.10
    steps:
      - checkout
      - run: npm ci
      - run: npm run build   # outputs to dist/
      - persist_to_workspace:
          root: .            # workspace root is current directory
          paths:
            - dist           # persist only the built output
            - node_modules   # persist deps if downstream jobs need them

  test:
    docker:
      - image: cimg/node:20.10
    steps:
      - checkout
      - attach_workspace:
          at: .              # restore to current directory
      - run: npm test        # dist/ and node_modules/ are now available

  deploy:
    docker:
      - image: cimg/node:20.10
    steps:
      - attach_workspace:
          at: .
      - run: npm run deploy  # uses dist/ from build job

workflows:
  build-test-deploy:
    jobs:
      - build
      - test:
          requires:
            - build          # runs after build completes
      - deploy:
          requires:
            - build          # can run in parallel with test
          filters:
            branches:
              only: main

test and deploy both depend on build, but they're independent of each other — CircleCI runs them in parallel after build completes.

Workspaces require internet connectivity — they upload to and download from CircleCI's storage

GotchaCircleCI

When a job calls persist_to_workspace, it uploads the specified paths to CircleCI's managed storage. When another job calls attach_workspace, it downloads those files. If a job runs in an environment with restricted outbound internet (custom runner, VPN-isolated executor), workspace operations fail.

Prerequisites

  • CircleCI jobs and workflows
  • Docker executor networking

Key Points

  • persist_to_workspace uploads to CircleCI's storage — requires outbound internet from the executor.
  • attach_workspace downloads from CircleCI's storage — same connectivity requirement.
  • For air-gapped or restricted environments, use volumes or shared storage instead of workspaces.
  • Workspace data is retained for 15 days after workflow completion, allowing workflow reruns.

Parallel test splitting with workspaces

Workspaces also enable fan-out patterns: a setup job prepares the test environment once, then multiple parallel test jobs pick up the same workspace:

jobs:
  setup:
    docker:
      - image: cimg/python:3.11
    steps:
      - checkout
      - run: pip install -r requirements.txt
      - persist_to_workspace:
          root: .
          paths:
            - .venv
            - tests

  test-unit:
    docker:
      - image: cimg/python:3.11
    steps:
      - attach_workspace:
          at: .
      - run: python -m pytest tests/unit/ --junitxml=test-results/unit.xml
      - store_test_results:
          path: test-results

  test-integration:
    docker:
      - image: cimg/python:3.11
    steps:
      - attach_workspace:
          at: .
      - run: python -m pytest tests/integration/ --junitxml=test-results/integration.xml
      - store_test_results:
          path: test-results

workflows:
  test-pipeline:
    jobs:
      - setup
      - test-unit:
          requires:
            - setup
      - test-integration:
          requires:
            - setup

Both test jobs run in parallel after setup, sharing the same installed .venv. This halves test time compared to sequential runs that each reinstall dependencies.

Workspace size limits and what not to persist

Large workspaces slow down every downstream job because each attach_workspace downloads all persisted files. Common mistakes:

Don't persist Docker images to workspaces. A compressed image is hundreds of MB — every downstream job downloads it. Use ECR or Docker Hub and pull in each job that needs it.

Don't persist the entire project directory. If you have a large git history or large binary assets, persisting . with no filter includes everything:

# Bad: persists everything including .git
- persist_to_workspace:
    root: .
    paths:
      - .

# Good: persist only what downstream jobs need
- persist_to_workspace:
    root: .
    paths:
      - dist
      - package.json
      - package-lock.json

Consider caching instead of workspace for dependencies. If multiple jobs need node_modules, caching it is more efficient than persisting it to the workspace — the cache uses content-addressed storage and is shared across workflow runs.

A CircleCI workflow has a build job that persists dist/ to the workspace. A deploy job attaches the workspace and deploys. The workflow reruns 20 days after the original run. The deploy job fails with 'workspace not found'. Why?

easy

The original workflow ran successfully. The rerun was triggered via the CircleCI UI. The workspace was not accessed in the 20 days between runs.

  • AThe deploy job's Docker image was updated and is incompatible with the workspace format
    Incorrect.Workspace format is agnostic to Docker image version. The image change doesn't affect workspace availability.
  • BCircleCI retains workspace data for 15 days — a rerun 20 days after the original workflow can't find the workspace because it was already evicted
    Correct!CircleCI workspace retention is 15 days from the workflow completion or last access. After 20 days with no access, the workspace data is deleted. The rerun fails because attach_workspace has nothing to download. For reruns beyond 15 days, the workflow needs to regenerate the artifacts — either by rebuilding from source or by storing critical artifacts as CircleCI Artifacts (which have configurable retention up to 30 days, or can be stored externally in S3).
  • CWorkspaces are branch-scoped — renames or branch changes after the original run invalidate them
    Incorrect.Workspaces are scoped to a workflow run, not a branch. They don't depend on branch state after creation.
  • DThe persist_to_workspace step needs to be re-run to refresh the expiry
    Incorrect.Workspaces are immutable after creation — you can't re-run just the persist step. The entire workflow needs to rerun to create a new workspace.

Hint:What is CircleCI's workspace retention period?