Search code examples
dockerdocker-composedocker-volumedocker-build

Understanding host mounted volume changes in Docker


I've got a monorepo with a docker-compose.yml containing:

version: '3'

services:
  alpha:
    build:
      context: .
      dockerfile: projects/alpha/Dockerfile
      target: alpha
    restart: always
    ports:
      - "8080:80"
    volumes:
      - ./projects/alpha:/projects/alpha

The Dockerfile contains:

FROM python:3.11.3-slim as builder

# Make / set a working directory
WORKDIR /projects/alpha

# Install Poetry and system dependencies
RUN apt update && ... (install necessary apt packages)

# Copy pyproject.toml / poetry.lock
COPY projects/alpha/pyproject.toml projects/alpha/poetry.lock* ./

# Poetry install
RUN poetry install --no-interaction --no-ansi

# Copy source
COPY projects/alpha ./

# Add a random file to the directory
RUN touch ./RANDO.md

FROM builder as alpha
CMD gunicorn --reload -w 3 -b 0.0.0.0:80 -k uvicorn.workers.UvicornWorker alpha.asgi:application

In the above scenario, --reload does what I'd expect - it reloads the workers when I make changes to Python modules on the host machine. If I didn't mount the host directory and instead simply let Docker automatically create a volume, that wouldn't work, because editing files on the host wouldn't result in changes in the container's files. That's all good and well.

If I shell into the container, the RANDO.md file I touched doesn't exist, nor does it in my host directory.

However, if, while I'm shelled into the container, I touch RANDO.md, the file is created and visible both inside the container and in my host directory.

What is actually happening (both in the container and in my mounted host directory) when the WORKDIR /projects/alpha is run during the Docker build?


Solution

  • WORKDIR changes the working directory in the container of any build actions that follow it, and it makes the directory and its parents if needed. As specified in the documentation:

    The WORKDIR instruction sets the working directory for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile. If the WORKDIR doesn’t exist, it will be created even if it’s not used in any subsequent Dockerfile instruction.

    It is roughly equivalent to : RUN mkdir -p /path/to/work/dir && cd /path/to/work/dir.

    Docker strongly recommends using WORKDIR instead of manually running cd path commands to change the working directory as it is easier to read and debug issues.

    The problem is that you are copying to and touching files in a location that you are then remapping to a host directory as a mounted volume.

    The COPY command is executed at build time. Volumes are mounted when the container starts. When you mount a local host directory or a volume to a location on the container, you are changing any reference to that location on the container to that mounted location.

    In other words, what you are doing is:

    1. Your COPY steps and RUN touch ./RANDO.md are occuring in the local file location of /projects/alpha on the container
    2. The container starts and the bind mount ./projects/alpha:/projects/alpha is put place.
    3. Now that the container is running, any reference within the container to /projects/alpha is now accessing the bind mount and not the local directory.

    If you want to see what's happening, try changing your WORKDIR to /projects/beta. You should see the files you copied and the RANDO.md file in /projects/beta, and then you'll see the mounted directory in projects/alpha. Or just start the container once without mounting the volume.