Read-only file system в Docker: какие каталоги приложению нужны для записи
Контейнеру включили read_only, и при запуске он падает с ошибкой создания файла в /tmp или каталоге cache. Это полезная подсказка о поведении процесса: раньше запись незаметно попадала в writable layer контейнера. Теперь нужно определить, какие данные временны, какие должны переживать перезапуск и какие вообще не должны создаваться при старте.
Читаем путь и операцию из ошибки
docker compose logs --tail 100 api
docker compose ps api
docker inspect --format "{{.HostConfig.ReadonlyRootfs}} {{json .Mounts}}" CONTAINER_IDПодставьте идентификатор нужного контейнера. Сохраните имя файла, действие и пользователя процесса. Read-only file system отличается от Permission denied: первое относится к режиму файловой системы, второе часто связано с UID, владельцем и правами. Изменение chmod на каталоге не сделает read-only mount записываемым.
Проверьте также mount поверх нужного пути. read_only ограничивает корневую файловую систему контейнера, но отдельно подключённый writable volume может оставаться записываемым. И наоборот, volume с :ro останется read-only независимо от режима корня. Полная картина находится в конфигурации обоих уровней.
Разделяем временные и постоянные данные
Временные файлы, PID и пересоздаваемый cache обычно можно вынести в tmpfs. Состояние приложения, которое должно переживать остановку, требует volume или внешней системы хранения. Данные базы нельзя переводить в tmpfs только ради быстрого старта: после остановки содержимое исчезнет. Для логов приложения часто удобнее stdout/stderr, чтобы не создавать отдельный изменяемый каталог внутри образа.
Следующий учебный сервис работает от UID 10001, пишет временный файл в /tmp и остаётся запущенным. Он не содержит постоянных данных:
services:
probe:
image: alpine:3.22
user: "10001:10001"
read_only: true
mem_limit: 128m
tmpfs:
- /tmp:size=16777216,mode=1777
command:
- /bin/sh
- -c
- "printf ready > /tmp/probe; cat /tmp/probe; sleep 3600"docker compose -p readonlylab config --quiet
docker compose -p readonlylab up -d
docker compose -p readonlylab logs probeВ журнале ожидается ready, полученное чтением созданного файла. Путь /tmp отдельно записываемый, а корень остаётся read-only. mode=1777 разрешает создание временных файлов непривилегированному пользователю со стандартной семантикой sticky bit. Для приватного каталога приложения подберите более узкие владельца и права, не копируйте 1777 на постоянные данные.
Ограничиваем расход памяти
tmpfs использует память, а её расход учитывается в cgroup контейнера. Размер mount — верхняя граница файловой системы, а не дополнительная RAM поверх mem_limit. В примере tmpfs ограничен 16 MiB при лимите контейнера 128 MiB. Приложению всё равно нужен запас для heap, стеков и других буферов. На Linux содержимое tmpfs также может попадать в swap в зависимости от конфигурации хоста.
Если приложение создаёт большие промежуточные архивы, ограничение входных данных и потоковая обработка могут быть важнее размера tmpfs. Заполнение mount даст ошибку записи, а суммарный расход памяти способен вызвать OOM. Проверьте оба отказа отдельно на учебной нагрузке, чтобы приложение завершало операцию с понятным результатом.
Проверяем другие скрытые записи
После первого исправленного пути повторите полный сценарий: холодный старт, запрос, создание временного файла, обработку ошибки и корректную остановку. Программа может создавать cache только при первой компиляции шаблона или пытаться сохранить PID при завершении. Также проверьте настройки библиотек, которым нужен HOME или каталог cache. Лучше задать явный путь, чем открывать корень для записи из-за одного файла.
Установка пакетов, генерация приложения и изменение системной конфигурации должны по возможности происходить при сборке образа. Если entrypoint пытается редактировать /etc на каждом старте, выясните назначение этой операции и перенесите постоянный результат в образ либо подключённую конфигурацию. Writable mount поверх каталога скрывает его исходное содержимое, поэтому не подменяйте целый /etc ради одного файла.
После изменения проверьте и положительный сценарий записи в разрешённый путь, и отказ записи в корень. Сохраните список каталогов с назначением: временный, постоянный или только чтение. Тогда read_only становится проверяемым контрактом приложения, а следующая ошибка сообщает о новом поведении, которое можно разобрать по конкретному пути.
Ещё по теме
- Обновление приложения в Docker Compose: фиксируем версию и готовим откат
- Резервная копия PostgreSQL в Docker Compose: проверяем восстановление
Продолжить изучение
Хотите разобраться в контейнеризации целиком, от образа до работающего окружения? Сборка, сети, данные и запуск приложений входят в программу «Docker и Docker Compose: контейнеризация приложений».
Продолжить на практике
- Docker и Docker Compose: контейнеризация приложений ↗
Упакуйте приложение в Docker и запустите его с PostgreSQL через Compose. Образы, сети, тома, диагностика поломок, обновление версий и восстановление данных — с практикой и итоговым проектом.
