Docker LinuxWindows (Docker Desktop)macOS (Docker Desktop)

Ошибка read-only file system в Docker после включения read_only

  • Обновлено
  • Чтение ≈ 5 минут

Краткий ответ

Параметр read_only ограничивает запись в корневую файловую систему, но не запрещает запись в отдельно созданные доступные для записи тома или tmpfs. Исправление зависит от того, являются ли данные временными или должны переживать пересоздание.

В этой статье
  1. Что означает ошибка и при каких условиях возникает
  2. Как различить причины
  3. Пошаговая диагностика и исправление
  4. Минимальный пример для отдельного тестового проекта
  5. Как проверить результат и что делать, если проблема осталась

#Что означает ошибка и при каких условиях возникает

При read_only: true контейнер не может записывать файлы в слой корневой файловой системы. Найдите путь записи и предоставьте отдельное временное или постоянное хранилище. Параметр read_only ограничивает запись в корневую файловую систему, но не запрещает запись в отдельно созданные доступные для записи тома или tmpfs. Исправление зависит от того, являются ли данные временными или должны переживать пересоздание.

#Как различить причины

Возможная причинаКак узнатьЧто делать
Приложение записывает в /tmp или каталог кэшаКонтейнер с read_only: true пытается создать временный файл в корневой файловой системе.В журнале OSError: [Errno 30] Read-only file system с путём /tmp либо похожим.Смонтировать tmpfs в подходящий временный каталог.
Приложение хранит состояние внутри слоя контейнераБаза или очередь пытается сохранять долговременные файлы в каталоге без volume.Ошибка записи относится к /app/data, /var/lib/... или каталогу состояния.Использовать именованный том/объектное хранилище после резервного копирования и миграции.
Каталог смонтирован только для чтенияВ Compose у bind mount или volume указан флаг :ro либо read_only: true.В inspect Mounts виден RW=false у требуемого каталога.Проверить намеренную политику доступа; изменить только при наличии оснований.
Запись идёт не в тот каталогПриложение сконфигурировано на путь внутри образа, а writable volume смонтирован в другое место.В логах указан путь, отличный от Destination в Mounts.Изменить путь в приложении или место монтирования после проверки.

#Пошаговая диагностика и исправление

  1. Сохраните ошибку и путь, по которому нужна записьВ терминале каталога Compose выполните docker compose logs --tail=100 app, заменив app именем своего сервиса. Ищите точный путь после Read-only file system и имя процесса. Не отключайте read_only целиком в качестве постоянного решения; этот параметр часто нужен для безопасности. Если контейнер хранит базу данных, перед любыми изменениями организуйте согласованную резервную копию средствами БД.
  2. Проверьте действующую конфигурацию и томаОткройте compose.yaml в редакторе; найдите read_only: true, tmpfs: и volumes: соответствующей службы. Выполните docker compose config -q — ошибок не должно быть. Далее docker compose ps -q app даст идентификатор, docker inspect --format "{{json .Mounts}}" ИДЕНТИФИКАТОР покажет Destination и RW. Если нужный путь не фигурирует среди Mounts, он остаётся частью корневой системы, защищённой read_only.
  3. Выясните, являются ли данные временнымиЕсли файл нужен только на время жизни контейнера — кэш, сокет, промежуточные данные, — подойдёт tmpfs. Если это база, пользовательские файлы или очередь заданий, tmpfs не подходит: его содержимое исчезнет после остановки контейнера. Обратитесь к документации приложения и уточните точный каталог записи. Не переносите данные с работающей БД простым копированием.
  4. Добавьте tmpfs для действительно временного путиДля учебного примера из статьи в compose.yaml у службы app добавьте tmpfs: - /tmp при сохранении read_only: true. Временный каталог /tmp станет доступен для записи, хотя остальная корневая файловая система останется только для чтения. Проверьте docker compose config -q; при синтаксической ошибке восстановите резервную копию YAML и исправьте отступы.
  5. Для постоянных данных используйте отдельный томЕсли приложение записывает, например, в /app/data, задайте именованный том для этого каталога. Убедитесь, что том не помечен :ro, а процесс внутри контейнера имеет права на запись; ошибки Permission denied и Read-only file system имеют разные причины. Для переноса данных сначала сделайте проверяемую резервную копию и согласуйте процедуру миграции. Не повышайте права контейнера до privileged и не назначайте chmod 777 без необходимости.
  6. Испытайте изменения в копии проектаСоздайте пустую учебную папку и сохраните предложенный compose.yaml. Откройте терминал в ней, выполните docker compose up -d и docker compose logs app. Команда Python создаёт /tmp/probe и печатает ok; контейнер может завершиться после успешной проверки (это нормально для одноразового теста). Если в журнале Errno 30, проверьте, правильно ли вставлены отступы tmpfs и дошла ли изменённая конфигурация до контейнера.
  7. Примените настройки рабочего сервиса только после согласованияЕсли исправление подтверждено на копии, запланируйте пересоздание конкретного сервиса в рабочем окружении. docker compose up -d --no-deps --force-recreate app изменяет работающий контейнер и вызывает простои, поэтому нельзя выполнять без уведомления и резерва важных данных. При откате верните исходный YAML и предыдущую подтверждённую версию приложения, затем повторно проверьте путь записи.
  8. Проверьте, что корневая система всё ещё защищенаПосмотрите docker compose config, где у app должен остаться read_only: true. Попробуйте штатную операцию приложения, требующую записи именно в разрешённый путь, и убедитесь в её успешности. Важно не проверять запись произвольными файлами в каталоге базы данных: это может нарушить порядок хранения. Если проблема сохранилась, сравните фактический путь ошибки с Mounts.
  9. Команды для отдельных ситуацийВыберите только подходящий вариант. Это не одна последовательность: часть команд диагностирует проблему, а часть меняет настройки или данные.
    Выберите подходящий вариант — это не команды для последовательного выполнения
    Вариант 1Действия чтения и проверки; имя app замените реальным.
    Linux · Терминал (Bash)Команда
    Действия чтения и проверки; имя app замените реальным. Последняя команда печатает ID только запущенного контейнера.
    docker compose logs --tail=100 app
    docker compose config -q
    docker compose ps -q app
    Вариант 2Замените ИДЕНТИФИКАТОР значением из docker compose ps -q app.
    Linux · Терминал (Bash)Команда
    Замените ИДЕНТИФИКАТОР значением из docker compose ps -q app. В JSON проверьте RW=true у требуемой точки монтирования.
    docker inspect --format "{{json .Mounts}}" ИДЕНТИФИКАТОР
    Вариант 3Только после проверки конфигурации, сохранности данных и согласования перерыва в работе.
    Linux · Терминал (Bash)С осторожностью
    Только после проверки конфигурации, сохранности данных и согласования перерыва в работе. Контейнер будет пересоздан; записанные только в его слое файлы могут пропасть.
    docker compose up -d --no-deps --force-recreate app

    Команда может изменить состояние системы или её настройки. Проверьте параметры и последствия перед запуском.

#Минимальный пример для отдельного тестового проекта

Текст
Минимальный тест только для новой пустой папки. Временная запись разрешена в /tmp, остальные каталоги образа остаются read-only. После однократного вывода ok контейнер штатно завершится.
services:
  app:
    image: python:3.12-alpine
    read_only: true
    tmpfs:
      - /tmp
    command: ["python", "-c", "from pathlib import Path; Path('/tmp/probe').write_text('ok'); print('ok')"]

#Как проверить результат и что делать, если проблема осталась

Проблема решена, если штатная запись в выделенный путь успешна, а корневая файловая система остаётся read-only. Если появляется Permission denied, проверяйте владельца и права самого тома; если Errno 30 остаётся, фактический каталог записи, вероятно, не совпадает с точкой tmpfs/volume.

Так эту ошибку тоже называют

Источники и обновления

Последнее обновление статьи: 2 ноября 2026. Версии программ и интерфейсы меняются — если шаги не совпадают с вашей версией, сверяйтесь с документацией производителя.