2025, Oct 16 04:00

Fixing Docker bind mount and WORKDIR mismatch so Python can read .env files (docker-compose volumes explained)

Python can't read .env inside Docker? The issue is a bind mount vs WORKDIR mismatch. Learn how to align docker-compose volumes or WORKDIR to fix missing files.

Docker bind mount is in place, Python still can’t see the files. The application runs fine on Windows, but inside the container it throws a “missing” error for .env and tg_token.env. The mounts look correct in Docker Desktop, yet the code can’t read them. The root cause here isn’t permissions or missing files; it’s a working directory vs. mount path mismatch.

Problem demonstration

The application expects .env files in a relative resources directory and exits if they are absent.

env_file_path = "resources/.env"
if os.path.exists(env_file_path):
    load_dotenv(env_file_path)
else:
    raise ResourcesMissing('.env missing')

token_env_path = "resources/tg_token.env"
if os.path.exists(token_env_path):
    load_dotenv(token_env_path)
else:
    raise ResourcesMissing('tg_token.env missing')

The container is built and started with the following configuration.

# docker-compose.yml
services:
  bot:
    build: .
    volumes:
      - ./media:/app/media:ro
      - ./resources:/app/resources:rw
# Dockerfile
FROM python:3.13.5-slim
WORKDIR /usr/src/app
ENV CONTAINER=docker

RUN mkdir -p /app/media
RUN mkdir -p /app/resources

RUN apt-get update
RUN pip install --upgrade pip
COPY . .

# [Installing dependencies]

CMD ["python", "bot.py"]

Additionally, the build context ignores host-side payload directories:

# .dockerignore
media/*
resources/*

What’s actually breaking

The program runs in /usr/src/app because WORKDIR is set to that path in the Dockerfile. Relative paths like resources/.env are therefore resolved against /usr/src/app, meaning the code expects to find its files at /usr/src/app/resources/.env and /usr/src/app/resources/tg_token.env.

However, the bind mounts in docker-compose.yml place host directories under /app (specifically /app/resources and /app/media). The result is a split-brain layout: the app executes from /usr/src/app while the mounted data lives in /app. From the application’s point of view, resources/.env does not exist, which explains why the missing-file exception is triggered even though the mounts appear in Docker Desktop.

If you want to double-check what the app believes is the current working directory, print it at runtime:

print(os.getcwd())

How to fix it

There are two straightforward ways to bring the working directory and the mounts back into alignment. Either move the mounts under /usr/src/app to match the current WORKDIR, or move the WORKDIR to /app to match the existing mounts.

Option A. Keep WORKDIR at /usr/src/app and update the bind mounts so the resources and media land where the app actually runs.

# docker-compose.yml (fixed mounts)
services:
  bot:
    build: .
    volumes:
      - ./media:/usr/src/app/media:ro
      - ./resources:/usr/src/app/resources:rw

Option B. Keep the mounts as they are and change the application’s working directory to /app. This aligns runtime CWD with the bind-mount targets already used in docker-compose.yml.

# Dockerfile (fixed WORKDIR)
FROM python:3.13.5-slim
WORKDIR /app
ENV CONTAINER=docker

RUN mkdir -p /app/media
RUN mkdir -p /app/resources

RUN apt-get update
RUN pip install --upgrade pip
COPY . .

# [Installing dependencies]

CMD ["python", "bot.py"]

Either approach resolves the path mismatch so that resources/.env and resources/tg_token.env are reachable via the same relative paths your Python code already uses.

Why this matters

Relative paths are evaluated against the process’s current working directory. In Docker, WORKDIR defines that context at build and runtime. If your runtime WORKDIR and your bind-mount targets don’t match, code that uses relative paths will appear to “lose” files that are actually mounted correctly. In practice this often surfaces as your own missing-file exception rather than a mount or permission error, which can be misleading when you only look at the container’s file browser.

Conclusion

Make the working directory and your bind mounts agree. Either mount resources and media into /usr/src/app, or set WORKDIR to /app so that the current directory lines up with /app/resources. If you suspect a similar issue in the future, print the current directory and compare it with where your volumes are mounted. Keeping these in sync prevents hard-to-trace “file not found” errors while keeping your Python code and Docker configuration simple and predictable.

The article is based on a question from StackOverflow by Eji Sarmat and an answer by J Earls.