Overview
When building an ephemeral Docker image, you may not want to push it to a Docker registry (e.g., Docker Hub). However, you may still want a subsequent job to be able to use this image.
This article shows you how to pass a built Docker image from one job to a downstream job without needing a Docker registry, by saving the image as a tar file, passing it through a workspace, and loading it again.
Prerequisites
This is achieved by using workspaces, and thus requires that the 2 jobs are in the same workflow.
If you need to pass Docker images between workflows, you will need to make use of caches.
Solution
Job A: Save built image as tar file
We can use the docker image save command to save an image as a tar file.
jobs:
build_image:
docker:
- image: cimg/base:stable
steps:
- checkout
- setup_remote_docker:
version: default
- run:
name: Build image
command: |
docker image build --tag "acmeorg/foobar:${CIRCLE_SHA1}" .
- run:
name: Save image as tar
command: |
mkdir -p images
docker image save -o "images/acmeorg_foobar_${CIRCLE_SHA1}.tar" "acmeorg/foobar:${CIRCLE_SHA1}"
- persist_to_workspace:
root: .
paths:
- images
Job B: Load built image from tar file
We then use the docker image load command to load the image from the tar file attached from the workspace.
jobs:
load_image:
docker:
- image: cimg/base:stable
steps:
- attach_workspace:
at: .
- setup_remote_docker:
version: default
- run:
name: Load image
command: |
docker image load < "images/acmeorg_foobar_${CIRCLE_SHA1}.tar"
- run:
name: Inspect loaded image
command: |
# we should see acmeorg/foobar:${CIRCLE_SHA1} listed
docker image ls
- run:
name: Use image
command: |
# replace with the command your image needs
docker run --rm "acmeorg/foobar:${CIRCLE_SHA1}"
Combine the jobs in a workflow
We then combine the 2 jobs in the same workflow.
workflows:
sample:
jobs:
- build_image
- load_image:
requires:
- build_imageThe version: default tag uses the current default Docker version for remote Docker. See Run Docker commands for the list of supported versions.
Where the loaded image can and cannot be used
Both example jobs above use the Docker executor: cimg/base:stable is the job's primary container, where the steps run. The image you build and load is not the job's container. It lives in the remote Docker environment created by setup_remote_docker, and you work with it through docker CLI commands in your steps.
This works for a downstream job that loads the tar file and then uses the image through Docker commands, for example docker run, docker compose, or as a base image in another docker build. In a Docker executor job, run setup_remote_docker before loading the image. In a machine executor job, Docker is available directly and setup_remote_docker is not needed.
This does not work if you want the built image to be the container a downstream job runs in, meaning you reference it under that job's docker: key as the primary image or a service image:
jobs:
test_in_built_image:
docker:
# Not possible with this approach: this image is pulled from a registry
# before any steps run, so it cannot be loaded from the workspace first
- image: acmeorg/foobar:latestCircleCI pulls the images listed under docker: from a registry when the job starts, before steps such as attach_workspace run. To run a job inside an image you built, push the image to a registry (public or private) and reference it from there. See Using Docker Authenticated Pulls for private registries.
Outcome
You should be able to load the built Docker image and use it with Docker commands in subsequent jobs in the same workflow.
Important Considerations
The saved tar files may be large. Saving to and loading from workspaces can take time depending on the file sizes.
The loaded image can only be used through Docker commands in the downstream job. It cannot be used as the image for a downstream job's Docker executor (the
docker:image:key). For that, the image must be pulled from a Docker image registry.
