Skip to main content

How to pass Docker images between Jobs

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_image

The 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:latest

CircleCI 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.

Additional Resources

Did this answer your question?