2025, Oct 16 04:16

Python в Docker не видит .env: как починить пути и WORKDIR

Почему при bind-монтах Docker приложение Python теряет .env из-за несоответствия WORKDIR и путей монтирования. Готовые правки Dockerfile и docker-compose.

Bind-монты Docker подключены, но Python всё равно не «видит» файлы. Приложение без проблем работает в Windows, однако внутри контейнера оно выбрасывает ошибку об отсутствии .env и tg_token.env. В Docker Desktop монтирования выглядят корректно, но код их не читает. Источник проблемы — не права доступа и не отсутствие самих файлов, а несоответствие между рабочей директорией и путями монтирования.

Демонстрация проблемы

Приложение ожидает .env‑файлы в относительной директории resources и завершает работу, если их нет.

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')

Контейнер собирается и запускается со следующей конфигурацией.

# 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 . .
# [Установка зависимостей]
CMD ["python", "bot.py"]

Кроме того, контекст сборки игнорирует каталоги с данными на хосте:

# .dockerignore
media/*
resources/*

Что именно не так

Программа запускается в /usr/src/app, потому что WORKDIR в Dockerfile установлен на этот путь. Значит, относительные пути вроде resources/.env разрешаются относительно /usr/src/app. Код ожидает увидеть файлы по адресам /usr/src/app/resources/.env и /usr/src/app/resources/tg_token.env.

Однако bind-монты в docker-compose.yml помещают хостовые директории под /app (конкретно /app/resources и /app/media). Получается «раздвоение»: приложение исполняется из /usr/src/app, а примонтированные данные лежат в /app. С точки зрения приложения, resources/.env не существует — поэтому и срабатывает исключение об отсутствии файла, даже если монтирования видны в Docker Desktop.

Чтобы убедиться, какую текущую директорию видит приложение, выведите её во время работы:

print(os.getcwd())

Как исправить

Есть два простых способа привести рабочую директорию и монтирования к единому знаменателю. Либо переместить монты под /usr/src/app, чтобы они соответствовали текущему WORKDIR, либо перенастроить WORKDIR на /app, подстроив запуск под существующие монты.

Вариант A. Оставляем WORKDIR равным /usr/src/app и обновляем bind-монты так, чтобы resources и media оказывались там, где реально запускается приложение.

# docker-compose.yml (исправленные монтирования)
services:
  bot:
    build: .
    volumes:
      - ./media:/usr/src/app/media:ro
      - ./resources:/usr/src/app/resources:rw

Вариант B. Сохраняем монты как есть и меняем рабочую директорию приложения на /app. Тогда текущая директория во время выполнения совпадёт с целями bind-монтов, указанными в docker-compose.yml.

# Dockerfile (исправленный 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 . .
# [Установка зависимостей]
CMD ["python", "bot.py"]

Оба подхода устраняют несоответствие путей, и файлы resources/.env и resources/tg_token.env становятся доступными по тем же относительным путям, которые уже использует ваш Python‑код.

Почему это важно

Относительные пути разрешаются относительно текущей рабочей директории процесса. В Docker эту точку задаёт WORKDIR как на этапе сборки, так и во время запуска. Если рантайм‑директория WORKDIR и цели bind-монтов не совпадают, код с относительными путями будет «терять» файлы, хотя они корректно примонтированы. На практике это чаще проявляется как срабатывание вашего собственного исключения об отсутствии файла, а не как ошибка монтирования или прав — что сбивает с толку, если смотреть только на файловый браузер контейнера.

Вывод

Синхронизируйте рабочую директорию и точки монтирования. Либо монтируйте resources и media в /usr/src/app, либо выставьте WORKDIR в /app, чтобы текущая директория совпадала с /app/resources. Если заподозрите похожую проблему в будущем, выведите текущую директорию и сравните её с местом монтирования томов. Держите их в одном контуре — и избавитесь от трудноуловимых ошибок «файл не найден», сохраняя при этом простой и предсказуемый конфиг Python и Docker.

Статья основана на вопросе на StackOverflow от Eji Sarmat и ответе от J Earls.