← назад к разделу

Вы запустили Postgres в контейнере, добавили данные, потом удалили контейнер — и всё пропало. Это не баг, а нормальное поведение Docker. Разбираемся, почему так устроено и как правильно хранить данные, которые должны переживать перезапуски.

Почему данные в контейнере эфемерны

Контейнер — это изолированный процесс с собственной файловой системой. Эта файловая система создаётся из образа при старте контейнера, а при его удалении — исчезает вместе с ним.

Короткая формула: образ — неизменяемый шаблон, контейнер — временная рабочая копия.

Всё, что вы записали внутри работающего контейнера (строки в базе, загруженные файлы, логи), живёт на так называемом слое записи — тонком слое поверх образа. Docker удаляет этот слой вместе с контейнером командой docker rm.

docker run --name demo postgres:16
# ... создали таблицы, добавили строки ...
docker rm demo
# Данные исчезли

Для большинства случаев это даже удобно: запустил контейнер для теста, поиграл, удалил — чисто. Но базы данных, пользовательские файлы, состояние приложения должны жить дольше, чем один контейнер.

Три способа хранить данные вне контейнера

Docker предлагает три механизма: named volumes, bind mounts и tmpfs. У каждого своя область применения.

Named volumes — рекомендованный способ для данных

Том (named volume) — это каталог, которым управляет Docker. Он живёт на хосте в специальном месте (/var/lib/docker/volumes/) и не зависит от жизненного цикла контейнера.

# Создать том явно
docker volume create pgdata

# Подключить том к контейнеру
docker run -d \
  --name postgres \
  -v pgdata:/var/lib/postgresql/data \  # том:путь_внутри_контейнера
  -e POSTGRES_PASSWORD=secret \
  postgres:16

Теперь можно удалить контейнер, создать новый с тем же томом — данные никуда не денутся:

docker rm postgres
docker run -d \
  --name postgres \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret \
  postgres:16
# Все данные на месте

Управлять томами можно через docker volume:

docker volume ls                # список томов
docker volume inspect pgdata    # где лежит, когда создан
docker volume rm pgdata         # удалить (только если не подключён)
docker volume prune             # удалить все «ничейные» тома

Named volumes — правильный выбор для баз данных, файловых хранилищ, любых данных, которые нужны в продакшене.

Bind mounts — для разработки

Bind mount монтирует конкретный каталог или файл с хоста прямо в контейнер. Никакой «магии» Docker: вы сами говорите, какой путь на вашей машине куда попадёт внутри.

docker run -d \
  --name app \
  -v /Users/vva/projects/myapp:/app \  # хост:контейнер
  eclipse-temurin:21-jre \
  java -jar /app/app.jar

Главное применение — разработка: правите код в IDE, изменения сразу видны внутри контейнера без пересборки образа. Для Spring Boot это особенно удобно в паре с devtools.

# docker-compose.yml для разработки
services:
  app:
    image: eclipse-temurin:21-jre
    volumes:
      - ./build/libs:/app          # собранный jar с хоста
    command: java -jar /app/app.jar

Bind mounts не стоит использовать для хранения данных Postgres или другого состояния в продакшене: вы становитесь зависимым от структуры файловой системы конкретного хоста, а это ломает переносимость.

tmpfs — данные только в памяти

tmpfs монтирует каталог внутри контейнера в оперативную память хоста. Данные не уходят на диск и исчезают при остановке контейнера.

docker run --tmpfs /tmp myapp

Применяется для временных данных, которые не должны попасть на диск: чувствительные файлы (токены, ключи), кэши сессий, промежуточные вычисления. Недоступен на Windows-хостах.

Сравнение: когда что использовать

СитуацияМеханизм
База данных в продакшене (Postgres, Redis)Named volume
Файлы, загружаемые пользователямиNamed volume
Код при локальной разработкеBind mount
Конфигурационные файлы при разработкеBind mount
Временные файлы, чувствительные данныеtmpfs

Postgres в контейнере: полный пример

Запустим Postgres с именованным томом, чтобы данные сохранялись между перезапусками. Это типичная настройка для локальной разработки Spring Boot приложения.

# docker-compose.yml
services:
  postgres:
    image: postgres:16
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: myapp
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"
    volumes:
      - pgdata:/var/lib/postgresql/data   # именованный том

volumes:
  pgdata:   # Docker создаст том автоматически
docker compose up -d
# Создали таблицы, добавили данные...

docker compose down          # остановили и удалили контейнеры
docker compose up -d         # подняли снова — данные на месте

Обратите внимание: docker compose down не удаляет тома. Для удаления тома нужен явный флаг:

docker compose down --volumes   # удалить контейнеры И тома

Это защищает от случайной потери данных.

Куда смотреть, если данные пропали

Если после перезапуска контейнера данные исчезли — проверьте по шагам:

  1. Убедитесь, что использовался том, а не просто каталог внутри контейнера.
  2. Проверьте, что при остановке не применялся флаг --volumes.
  3. Убедитесь, что путь монтирования совпадает с тем, куда приложение реально пишет данные. У Postgres это /var/lib/postgresql/data, у Redis — /data, у других — свои пути, смотрите в документации образа.
docker inspect postgres | grep -A 10 Mounts   # проверить, что примонтировано

Коротко

  • Данные внутри контейнера эфемерны: удалил контейнер — потерял данные.
  • Named volume — правильный выбор для баз данных и любого состояния, которое должно жить дольше контейнера.
  • Bind mount — монтирует каталог с хоста; подходит для разработки, не для продакшена.
  • tmpfs — только в памяти, исчезает при остановке; для временных и чувствительных данных.
  • docker volume ls / inspect / rm / prune — основные команды управления томами.
  • docker compose down не удаляет тома — нужен явный --volumes.

Что почитать дальше

  • Запуск контейнеров — флаги docker run, режимы работы, управление контейнерами.
  • Docker Compose — как описывать многоконтейнерные приложения и задавать тома в docker-compose.yml.
  • Сетевое взаимодействие — как контейнеры общаются друг с другом и с хостом.