Статьи Nazara
Как сохранить пользовательские файлы при обновлении приложения
Как отделить загрузки от релиза приложения, выбрать постоянное хранилище, проверить права и перенести файлы вместе с записями базы без потери связи.
Чтобы обновление приложения не удалило загруженные фотографии и документы, храните их отдельно от каталога релиза и записываемого слоя контейнера. Эта статья поможет владельцу небольшого проекта на VPS выбрать постоянное хранилище, связать его с базой данных и проверить сохранность файлов перед следующим деплоем. Результат — схема, в которой замена кода не означает замену пользовательских данных.
Какие файлы должны пережить обновление
Релиз — это версия программы: код, зависимости и собранные ресурсы интерфейса. Пользовательская загрузка — данные, появившиеся уже после запуска. Если держать оба вида файлов в одной папке и при публикации заменять её целиком, фотографии могут исчезнуть вместе со старой версией. Правильная граница проходит не по расширению файла, а по возможности восстановить его без обращения к пользователю.
Разделите содержимое проекта на четыре группы. Код восстанавливается из проверенного релиза. Оригиналы загрузок нужно сохранять и резервировать. Превью можно пересоздать, если остались оригиналы и известны параметры обработки. Временные файлы допустимо удалять только по установленному сроку и после завершения связанной операции. Документ клиента нельзя объявить временным лишь потому, что он лежит в каталоге temp.
Составьте реестр: назначение, фактический путь, кто записывает, кто читает, связь с записью базы и способ восстановления. Для небольшого сервиса достаточно таблицы. Например, заявка хранит идентификатор документа и его владельца, а сам документ находится в постоянном хранилище. Абсолютный путь текущего релиза не должен становиться вечным адресом файла.
Отдельно проверьте созданные самим приложением отчёты. Если они являются результатом услуги и пользователь ожидает получить их позднее, им тоже требуется постоянное хранение. Если отчёт можно сформировать заново, всё равно нужно понимать, сохранены ли исходные данные и не изменился ли алгоритм. «Сгенерируем повторно» — не план, пока результат не проверен.
Как выбрать постоянное хранилище
Для одного VPS постоянным хранилищем может быть отдельный каталог на диске или Docker volume. Для нескольких серверов часто удобнее объектное хранилище с доступом через API. Выбирайте не по модному названию, а по требованиям: кто управляет файлами, сколько процессов пишет, как делаются копии и что произойдёт при потере самого VPS.
| Вариант | Когда удобен | Что проверить |
|---|---|---|
| Отдельный каталог VPS | Один сервер, понятные пути и доступ администратора | Права процесса, свободное место, резервную копию вне сервера |
| Docker volume | Приложение живёт в контейнерах | Имя тома, правила подключения и отдельный способ восстановления |
| Объектное хранилище | Файлы нужны нескольким экземплярам приложения | API, права, ограничения, стоимость операций и план переноса |
Docker описывает volumes как постоянные данные вне жизненного цикла контейнера. Это полезная граница, но не резервная копия. Если том и база находятся на одном повреждённом сервере, разделение от кода само по себе не восстановит их.

Имена постоянных хранилищ должны быть стабильными. Если каждую публикацию запускать как новый Compose-проект, нужно проверить, не создаётся ли другой том с новым префиксом. Старые данные могут оставаться на диске, а приложение выглядеть пустым из-за подключения нового хранилища. Такой случай требует проверки подключения, а не немедленного восстановления поверх неизвестного тома.
Для начала запишите ожидаемый путь внутри приложения и соответствующее ему хранилище снаружи. Не выбирайте два независимых места записи «на всякий случай»: это усложняет поиск актуального оригинала. Если нужна репликация, у неё должны быть правила согласования, наблюдение за ошибками и проверка результата.
Как связать файл с владельцем и закрыть чужой доступ
Постоянный файл должен иметь устойчивый внутренний идентификатор. В базе храните его связь с пользователем или заявкой, статус обработки, размер и необходимые сведения для выдачи. Пользовательское имя документа удобно показывать в интерфейсе, но не следует использовать его напрямую как путь на сервере.
Разделите публичные материалы и частные документы. Обложка открытой статьи может быть доступна всем. Договор, файл заявки или выгрузка кабинета обычно требуют проверки прав при каждом обращении. Непредсказуемое имя затрудняет угадывание, но не заменяет авторизацию: человек, получивший чужую ссылку, не должен автоматически получить чужой документ.
Один из вариантов — пользователь обращается к обработчику приложения по идентификатору, сервер проверяет владельца и только затем разрешает выдачу. Если после проверки используется внутренний маршрут Nginx, его назначение и ограничения нужно сверить в документации директивы internal. Сама директива не узнаёт владельца документа; это обязанность приложения.
Зафиксируйте срок доступа отдельно от срока хранения. Закрытие заявки может запрещать скачивание в кабинете, но ещё не разрешать физическое удаление всех данных. И наоборот, доступная ссылка не означает, что файл должен храниться бессрочно. Правила определяются задачей проекта и применимыми требованиями, а не выбранной структурой каталогов.
Для проверки используйте два тестовых аккаунта. Первый загружает документ, второй пытается открыть его известный идентификатор. Затем проверьте обращение без входа и после выхода. Для частного файла во всех неразрешённых случаях должны отсутствовать и содержимое, и случайный обход через прямой статический URL.
Что проверить при приёме загрузки
Сохранность и безопасность — разные задачи. Постоянное хранилище надёжно сохранит и вредоносный файл, если приложение принимает всё подряд. Сначала определите разрешённые форматы и размеры для конкретной функции. Поле для фотографии не должно незаметно превращаться в универсальную загрузку исполняемого содержимого.
Рекомендации OWASP по загрузке файлов предлагают сочетать проверки, а не доверять одному признаку. Расширение и присланный Content-Type недостаточны; нужны проверка допустимого содержимого, ограничения размера, безопасное внутреннее имя и контроль прав. При необходимости добавляют антивирусную проверку и обработку формата, но они не отменяют остальные ограничения.
В приложении предусмотрите состояния: принят, проверяется, доступен, отклонён. Документ, который ещё проходит проверку, не должен сразу оказаться в публичном каталоге. Сообщение пользователю должно объяснять результат без раскрытия внутренних путей и команд. При неуспешной операции временный объект нужно учесть для последующей безопасной очистки.
HTML и SVG могут содержать активное содержимое. Не разрешайте их публикацию в общем контексте сайта только потому, что это «текст» или «картинка». Для необходимых форматов отдельно проектируют безопасную обработку и выдачу. Аналогично архив нельзя автоматически распаковывать в рабочую папку приложения без проверки структуры и ограничений.
Добавьте сценарии, связанные с ресурсами: прерванная загрузка, повторная отправка, большой файл, несколько одновременных запросов. Установите ограничения на стороне приложения и инфраструктуры согласованно. Иначе пользователь видит ошибку в одном месте, а на диске уже остаётся часть данных. Запись о принятом документе должна появляться только по предусмотренному сценарию, а не просто после создания пустого файла.
Как подключить каталог, не скрыв старые данные
Сначала выясните, куда действующая версия реально записывает файлы. Не ориентируйтесь только на имя uploads в исходниках: конфигурация и рабочий каталог процесса могут менять итоговый путь. Загрузите безопасный тестовый объект и найдите его через предусмотренную диагностику приложения. Сверьте путь, права и запись базы.
При bind mount Docker подключает каталог хоста внутрь контейнера. Если подключение сделано поверх уже непустой папки контейнера, прежнее содержимое становится скрытым этим монтированием. Такое поведение описано в документации bind mounts. Поэтому новое подключение не является командой переноса старых загрузок: копирование и проверка выполняются отдельно.
Ниже — условная форма подключения, а не готовая команда для чужого проекта. Путь, образ и имя сервиса необходимо заменить только после проверки фактической конфигурации. Если приложение ожидает другой каталог, такой пример не обеспечит сохранность.
services:
app:
image: example/myapp:approved-version
volumes:
- type: bind
source: /srv/myapp-data/uploads
target: /app/uploads
Убедитесь, что source уже существует и содержит именно проверенные данные, а не пустую папку с похожим названием. Проверьте, какой UID/GID использует процесс, может ли он создавать и читать файл и какие права имеет сервер выдачи. Не решайте ошибку записи выдачей всем прав 777: это расширяет доступ вместо исправления владельца и необходимых полномочий.
Сервису обработки может требоваться запись, а отдельному процессу выдачи — только чтение. Разделяйте эти потребности, если архитектура позволяет. Перед переключением проверьте новую схему на копии тестовых данных. На сервере с несколькими проектами не меняйте права родительского каталога рекурсивно без точного списка затрагиваемых объектов.
Как перенести загрузки и проверить обновление
Перенос начинайте с плана остановки записи. Если во время копирования пользователи продолжают добавлять и удалять документы, база и каталог могут разойтись. Для небольшого проекта проще назначить короткое окно обслуживания, приостановить изменение данных, сделать согласованную копию и только затем переключить путь. Для непрерывного сервиса нужен отдельный механизм синхронизации и подтверждения завершения.
- Зафиксируйте исходное место файлов, настройки и версию приложения
- Создайте резервную копию базы и загрузок с понятной точкой согласования
- Остановите запись предусмотренным способом, не удаляя действующие данные
- Скопируйте файлы в отдельное проверенное хранилище
- Сверьте количество, размеры, контрольные суммы и ссылки базы
- Переключите приложение и выполните тестовый сценарий
- Возобновите запись и наблюдайте за ошибками чтения и загрузки
Количество файлов полезно, но не доказывает совпадение содержимого. Проверяйте контрольные суммы оригиналов или другой подходящий способ сравнения. При этом несвязанные временные файлы и пересоздаваемые превью нужно учитывать отдельно: иначе список расхождений будет шумным и важный пропуск потеряется. Найденный «лишний» файл сначала исследуют, а не удаляют автоматически.

После переключения откройте два прежних тестовых документа, создайте новый и перезапустите или замените только экземпляр приложения по согласованному плану. Повторно проверьте старые и новый объекты, права доступа и отображение в кабинете. Тест с новой загрузкой до обновления и чтением после него проверяет именно независимость данных от релиза.
Не удаляйте исходную копию сразу после первого успешного запроса. Определите период наблюдения и условия возврата. Если новая версия меняет структуру записей базы, возврат старого образа может быть несовместим с новыми данными. План отката должен учитывать и код, и схему базы, и каталог; простой запуск прежнего контейнера не гарантирует безопасный возврат.
Сохранность после обновления не заменяет восстановление после аварии. Проверьте согласованную пару «база + файлы» на отдельной среде по материалу о резервном копировании VPS. Перед открытием обновлённого проекта полезна и проверка веб-приложения перед запуском.
Чек-лист сохранности пользовательских файлов
Отметки сохраняются в этом браузере. Это памятка для самостоятельной проверки: она не сканирует сервер и не подтверждает резервные копии автоматически.
Если нужна помощь со схемой хранения, опишите приложение и типы документов через Умную форму. Для обсуждения достаточно структуры и тестовых примеров; не отправляйте документы клиентов и доступы к серверу.
Ответы на частые вопросы
Достаточно ли папки uploads рядом с кодом
Только если деплой гарантированно не заменяет её и это проверено. Надёжнее явно отделить данные от релиза и включить их в план резервирования.
Почему после обновления папка выглядит пустой
Возможны другой путь, новый том, смена проекта Compose или монтирование поверх прежнего каталога. Сначала проверьте подключение; не записывайте восстановление поверх неизвестных данных.
Можно ли хранить файлы прямо в базе
Это отдельное архитектурное решение. Оцените объём, нагрузку, резервирование и возможности выбранной базы. Оно не устраняет необходимость проверять владельца и восстановление.
Нужны ли копии, если используется объектное хранилище
Нужен собственный план восстановления с учётом возможностей провайдера. Само наличие API не защищает от ошибочного удаления и не восстанавливает записи приложения.
Можно ли удалять превью после смены версии
Только если оригиналы сохранены, повторная обработка проверена и приложение переживает отсутствие превью. Не смешивайте их с единственными экземплярами документов.
Проверка нескольких файлов доказывает сохранность всего каталога
Нет. Пользовательский сценарий дополняет сверку реестра и содержимого. Для полного переноса нужны проверка всех ожидаемых объектов и разбор расхождений.