Why Your Docker Compose Changes Aren't Applying

Why Your Docker Compose Changes Aren't Applying

A tale of commands issued, expectations unmet, and the eventual "aha!" moment that illuminates a core principle of Docker Compose's lifecycle management.

Mike Chumba Mike Chumba
4 min read
845 words

The Ghost in the Machine.

In the world of container orchestration, few tools have become as indispensable as Docker Compose. It has matured to the point where it became part of the default installation process in October 2022. It offers a streamlined, declarative approach to managing multi-container applications, defining complex services, networks, and volumes in a single YAML file. Yet, even seasoned, ahem!, developers can be ensnared by a common and frustrating pitfall like modifying the docker-compose.yml file, only to find the changes mysteriously absent from their running containers.

This is the story of my journey through that very labyrinth, a tale of commands issued, expectations unmet, and the eventual “aha!” moment that illuminates a core principle of Docker Compose’s lifecycle management.

An Unchanged Volume Mount

The Scene of the Crime.

Our journey begins with a simple, common task of modifying a service in a docker-compose.yml file to alter a volume mount. Let’s imagine our initial configuration for a web service looks something like this:

version: '3.8'
services:
  webapp:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./initial-content:/usr/share/nginx/html

The goal is to change the source directory for the NGINX content. I dutifully update the file:

version: '3.8'
services:
  webapp:
    image: nginx:latest
    ports:
      - "8080:80"
    volumes:
      - ./new-content:/usr/share/nginx/html # The change is here

With the change saved, the I turn to the command line. The natural inclination is to restart the service to apply the new configuration.

docker compose restart

The terminal indicates that the webapp service is restarting. Success? Not quite. Upon inspecting the web server’s content, the old files from ./initial-content are still being served. Puzzled, I try a more deliberate approach:

docker compose stop
docker compose start

Again, the containers stop and start as expected, but the result is the same. The configuration change has not been picked up. The frustration mounts.

docker inspect Reveals the Truth

The Investigation.

At this point, it’s clear that the running container is not reflecting the state of the docker-compose.yml file. To get to the bottom of this, we turn to a powerful diagnostic docker inspect tool. This command provides a detailed JSON output of a container’s configuration.

By running docker inspect on the webapp container and filtering for the “Mounts” section, we find the smoking gun:

"Mounts": [
    {
        "Type": "bind",
        "Source": "/path/to/project/initial-content",
        "Destination": "/usr/share/nginx/html",
        ...
    }
]

The output confirms that the container is still bound to the original ./initial-content directory. The restart and start/stop commands never applied the updated configuration.

Understanding the Docker Compose Lifecycle

The Revelation.

The official Docker Compose documentation holds the key to this mystery. The restart command, and by extension stop and start, are designed to manage the lifecycle of existing containers. They do not re-read the docker-compose.yml file to check for configuration drift. They simply stop, start, or restart the container with the configuration it was originally created with.

The command that does reconcile the configuration file with the running services is docker compose up . When you run docker compose up, it performs a critical check. It compares the configuration in your docker-compose.yml against the currently running services. If it detects a mismatch (like our changed volume mount) it will stop and recreate the container to match the new specification. This is the fundamental difference.

To apply the change, the correct command was:

docker compose up -d

The -d flag runs the containers in detached mode, returning control of the terminal. After running this, a subsequent docker inspect would reveal the updated mount point, and the web server would finally serve the content from ./new-content. For situations where you want to guarantee a fresh start, you can use the --force-recreate flag .

The Unquestionable Power of down and up

The “Nuke and Pave” Approach.

After the struggle and the eventual discovery, a desire for absolute certainty can take hold. How can one be positive that the environment is a perfect reflection of the configuration file, with no lingering artifacts? This is where I landed, and it’s a robust and reliable workflow, because I had suffered enough and could not take chances.

The solution is a two-step process:

  1. docker compose down: This command doesn’t just stop the containers; it removes them entirely, along with the networks created by up. It offers a clean slate. Critically, to prevent accidental data loss, it does not remove named volumes by default.
    docker compose down
  1. docker compose up: With the old containers and networks gone, running up now builds the environment from scratch, directly and exclusively from the docker-compose.yml file.
    docker compose up -d

This down and up combination is the most definitive way to ensure your running environment is in perfect sync with your configuration. It eliminates any possibility of an old container with a stale configuration being inadvertently started.

This journey, born from a simple misinterpretation of command behavior, culminates in a deeper and more practical understanding of Docker Compose. The intuitive restart is not for configuration changes. The true power lies in up’s ability to reconcile and recreate, and for the ultimate clean deployment, the down and up workflow reigns supreme.