I’ve used CircleCi for personal project pipelines, and I think it’s a great tool. I like the declarative yaml templates and it’s a free option for personal use where high build concurrency is not a deal breaker.
Most software will share some similar basic requirements for a CICD pipeline - a build job bundling source code into deployable artifacts, and subsequent deploy job(s) that ship the bundled source artifacts to the appropriate destination where the code runs. Below are some notes around how I’ve approached artifact management when building CICD pipelines with CircleCi.
Single Workflow
The simplest approach to managing build files when deploying with CircleCi is to use a single single workflow + workspace pattern, though keep in mind this will be better for personal or smaller projects that don’t need many deployment environments or team level operations. Build artifacts can be persisted and retrieved between jobs within the same workflow using the workflow’s associated workspace. A workspace is automatically provisioned for each workflow and provides a filesystem available to jobs only within that workflow. Re-tried workflows will inherit the same workspace as the original workflow.
The following basic example defines a linear workflow with a build job and a deploy job. After a merge to the main branch, a successful build job that persists artifacts to the workspace will trigger a deployment job connected to the same workspace.
Complete Yaml Config
---
version: 2.1
executors:
my-executor:
docker:
- image: my-image/build-agent:latest
working_directory: ~
jobs:
build:
executor: my-executor
steps:
- checkout:
path: ~/my-project
- run:
name: build
command: ./build.sh
- persist_to_workspace:
root: workspace
paths:
- build_artifacts
deploy:
executor: my-executor
steps:
- attach_workspace:
at: ~/workspace
- run:
name: deploy
command: ./deploy.sh
workflows:
build-deploy:
jobs:
- build
- deploy:
requires:
- build
filters:
branches:
only: main
For a comprehensive reference on CircleCi workflows, jobs, steps, and other paradigms, checkout the config dogs and/or the concept docs.
Multi Workflow
When multiple worfklows that act on the same build artifacts are necessary, another option is the artifact + environment variable pattern. This approach uses the artifacts API to upload / download the build files, and an environment variable to provide the required job number parameter. Here artifacts refers to the CircleCi artifacts primitive, which is a standalone store where build files can be persisted and accessed regardless of workflow. A CircleCi provided config step can upload artifacts and the REST API also provides upload and download operations.
The REST API v1 requirs a job number parameter on artifact download operations, so the requester must know what historical job number produced the desired artifacts. Each new job increments the current job number regardless of the workflow, so the last artifact-producing job number can be unpredictable if other workflows and jobs are running simultaneously.
This is where project environment variables come into play. A build number environment variable representing the build with the latest artifacts can be continually updated and read from any job. Future deployment jobs, regardless of workflow, can leverage this variable during download.
Note: In version 1 of the REST API, there is an API path available that gets the artifacts produced by the latest job in the project. This gets us a step closer, but it really only works for deploy jobs that run immediately after the build job producing the artifacts on a given branch. Once that deploy job runs, then its build number becomes latest.
Here’s some recipes to help implement the artifacts + environment variable pattern. In practice, replace the generic name subsitutions :vcs-type, :username, and :project with your own values.
Set latest build number environment variable
$ curl -X POST -H "Content-Type: application/json" -d "{\"name\": \"LATEST_BUILD_NUM\", \"value\": \"$CIRCLE_BUILD_NUM\"}" \
https://circleci.com/api/v2/project/:vcs-type/:username/:project/envvar -H "Circle-Token: $ARTIFACT_TOKEN"
CIRCLE_BUILD_NUM is an environment variable already made available by CircleCi at job runtime, so we’ll use that to update our LATEST_BUILD_NUM environment variable.
Download artifacts
$ curl -L -X GET "https://circleci.com/api/v2/project/:vcs-type/:username/:project/$LATEST_BUILD_NUM/artifacts" \
-H "Accept: application/json" \
-u "$API_TOKEN:" \
| grep -o 'https://[^"]*' \
| wget --verbose --header "Circle-Token: $API_TOKEN" --input-file -
Use LATEST_BUILD_NUM as an API param. The API call returns metadata information about the artifacts including download urls, which are harvested out with grep to perform the downloads with wget.
Complete Yaml Config
---
version: 2.1
executors:
my-executor:
docker:
- image: my-image/build-agent:latest
working_directory: ~
commands:
get_latest_artifacts:
steps:
- run:
name: download artifacts
command: |
curl -L -X GET "https://circleci.com/api/v2/project/:vcs-type/:username/:project/$LATEST_BUILD_NUM/artifacts" \
-H "Accept: application/json" \
-u "$API_TOKEN:" \
| grep -o 'https://[^"]*' \
| wget --verbose --header "Circle-Token: $API_TOKEN" --input-file -
jobs:
build:
executor: my-executor
steps:
- checkout:
path: ~/my-project
- run:
name: build
command: ./build.sh
- store_artifacts:
path: build_artifacts
- run:
name: set latest build environment var
command: |
curl -X POST -H "Content-Type: application/json" -d "{\"name\": \"LATEST_BUILD_NUM\", \"value\": \"$CIRCLE_BUILD_NUM\"}" \
https://circleci.com/api/v2/project/:vcs-type/:username/:project/envvar -H "Circle-Token: $API_TOKEN"
deploy-dev:
executor: my-executor
steps:
- get_latest_artifacts
- run:
name: deploy to dev
command: deploy-dev.sh
deploy-prod:
executor: my-executor
steps:
- get_latest_artifacts
- run:
name: deploy to prod
command: deploy-prod.sh
workflows:
build-deploy-dev:
jobs:
- build
- deploy-dev:
requires:
- build
filters:
branches:
only: main
deploy-prod:
jobs:
- deploy-prod:
filters:
branches:
only: main
2026 Edit
While rebuilding my site in early 2026, I took another look at this article and a friendly LLM recommended the use of the CIRCLE_SHA1 variable that CircleCi injects with each job to help manage build artifacts.
The CIRCLE_SHA1 variable is the full git commit hash for the commit triggering the workflow, so it provides another way to label and identify build output that’s more source control based. It can also be helpful for rollback situations.
That being said, saving artifacts with commit hash naming conventions may mean it’s time to find an external artifact store like S3 where artifacts can be persisted long term and managed more tightly. CircleCi by default has an artifact retention of only 30 days.