1. Overview

Occasionally, Docker Compose might not use images that are already present locally. Instead, it attempts to pull images from a remote repository (like Docker Hub). This could result in errors and unnecessary delays in development or deployment workflows.

In this tutorial, we’ll examine why this issue occurs and provide step-by-step instructions to troubleshoot and resolve it.

2. Understanding the Problem

When Docker Compose ignores local images, it’s usually because of a mismatch between how Docker Compose expects to find images and how our images are actually set up.

Let’s quickly look at some common scenarios that can cause this issue:

  • Incorrect image name or tag: Docker Compose is picky about details. If the image name or tag in our docker-compose.yml file doesn’t exactly match what we built, it won’t find the local version. For example, baeldung-server:latest is different from baeldung-server:staging.
  • DOCKER_HOST environment variable: if the DOCKER_HOST environment variable is set, Docker Compose will look for images on the remote host specified by the variable. This can lead to issues if we expect Docker Compose to use local images.
  • Docker Compose version: the behavior of Docker Compose varies slightly between different versions. If our docker-compose.yml file specifies a version that doesn’t align with our installed Docker Compose version, it could affect how it resolves images.

Now, let’s move on to making some checks to diagnose the exact cause in our case.

3. Making Preliminary Checks

Before we start troubleshooting the issue, let’s perform a few initial checks to eliminate simple causes.

First, let’s confirm the image we want to use is actually present on our local machine:

$ docker images
REPOSITORY                    TAG       IMAGE ID       CREATED        SIZE
baeldung-server               latest    8bba7bdc05d7   3 hours ago    8.83MB

This command displays a list of all Docker images stored locally, along with their tags and other details. We need to make sure the image specified in our docker-compose.yml file is in the list. If it’s not, we have to build or pull it first.

Next, let’s carefully review our docker-compose.yml file. Here’s a common way to reference a Docker image in the file:

$ cat docker-compose.yml
version: '3'
services:
  webserver:
    image: baeldung-server:latest
    ports:
      - "80:80"

We have to ensure the image name and tag exactly match the one listed in the docker images command output. Even minor inconsistencies, such as a missing latest tag or a capitalization error, can prevent Docker Compose from recognizing the image.

Furthermore, we can check if the DOCKER_HOST environment variable is set on our local machine:

$ echo $DOCKER_HOST

If we see an IP address or hostname, Docker Compose will look for images on the remote host specified there.

Let’s unset the variable so that Docker Compose can use images on our local machine:

$ unset DOCKER_HOST

In addition, we should delete any existing DOCKER_HOST variable definition from our .env file, if present.

Finally, we can check the Docker Compose version:

$ docker-compose version

We have to ensure this version is compatible with the version specified at the top of our docker-compose.yml file. If there’s a mismatch, let’s consider updating the Docker Compose installation or the version in the configuration file.

4. Troubleshooting Steps

Now that we’ve ruled out the most obvious cases, let’s dig deeper and explore other solutions to fix the issue.

4.1. Rebuilding Local Images

Sometimes, even after double-checking our image name and tag and confirming that the Docker image exists locally, Docker Compose can still insist on pulling from a remote repository. As a result, we have to rebuild our local image.

Notably, this step is essential if we’ve made any changes to our application’s source code or the Dockerfile itself. Docker Compose won’t automatically detect these changes and will continue to use the existing image unless we explicitly tell it to rebuild.

First, let’s clear Docker’s build cache to avoid potential conflicts from previous builds. This operation ensures a clean state for our new image:

$ docker builder prune -af

Next, to ensure we’re using the latest versions of any dependencies in our Dockerfile, we can rebuild the image using the –no-cache option:

$ docker build --no-cache -t baeldung-server:latest .

After that, we can run docker-compose up -d again. Docker Compose should now use the freshly built local image. Therefore, this new image contains any changes we made.

4.2. Using the pull_policy Option

Furthermore, if rebuilding the image hasn’t resolved the issue, there’s another option we can explore: the pull_policy setting. This configuration controls how Docker Compose decides whether to pull an image or not.

In essence, pull_policy acts as a decision-maker for Docker Compose when it comes to image management.

pull_policy has four possible values:

  • always (always pull the image)
  • never (never pull)
  • missing (pull only if the image isn’t in the local cache)
  • build (build or rebuild the image)

The default behavior, if not specified, is missing.

For our current scenario, the missing option or its alias, if_not_present (for backward compatibility), is the most relevant.

Now, let’s incorporate it in our docker-compose.yml file:

$ cat docker-compose.yml
version: '3'
services:
  webserver:
    image: baeldung-server:latest
    pull_policy: if_not_present
    ports:
      - "80:80"

With this configuration, Docker Compose will prioritize using the local baeldung-server:latest image, if it exists. Otherwise, it can resort to pulling from the registry.

4.3. Tagging and Using Local Registry

A local Docker registry could be the key if none of the previous steps work. This involves tagging our image with a reference to our local registry.

In other words, it’s like setting up a mini-Docker Hub on our machine. This approach comes in handy when Docker Compose seems determined to pull images from elsewhere.

Now, let’s find out how to go about it.

First, if we don’t already have a local registry up and running, we can easily launch one:

$ docker run -d -p 5000:5000 --restart=always --name registry registry:2

This command downloads the official Docker Registry image and starts a container listening on port 5000.

Next, let’s give our image a special tag that tells Docker Compose it lives in our local registry. By convention, local registries typically run on port 5000:

$ docker tag baeldung-server:latest localhost:5000/baeldung-server:latest

Now, let’s update our docker-compose.yml file to reference the new tag:

$ cat docker-compose.yml
version: '3'
services:
  webserver:
    image: localhost:5000/baeldung-server:latest
    ports:
      - "80:80"

After these steps, docker-compose up -d should now use the local image from our registry.

4.4. Using Docker Save and Load

Another approach is to save our local image to a tar archive:

$ docker save -o baeldung-server-latest.tar baeldung-server:latest

Then, we can transfer the baeldung-server-latest.tar file to the desired location (e.g., another machine or a remote Docker host).

Now, let’s load the image in the target environment:

$ docker load -i baeldung-server-latest.tar
Loaded image: baeldung-server:latest

Therefore, we can bypass any potential issues with Docker Compose directly accessing our local image store. After loading, Docker Compose should recognize the image and use it as expected.

5. Conclusion

In this article, we made our way through some solutions to the common problem of Docker Compose ignoring local images. Consequently, we covered solutions such as checking for typos, rebuilding images, using the pull_policy option, and more!

By understanding these potential causes and following troubleshooting steps, we can ensure Docker Compose effectively uses our local images.