Три типа mount: bind, volume, tmpfs
Контейнер по умолчанию stateless: остановил — удалил — данные пропали. Чтобы данные жили дольше контейнера, или чтобы файлы с хоста были доступны внутри контейнера, Docker даёт три механизма монтирования: bind mount, named volume и tmpfs. Все три выглядят похоже — «папка появилась внутри контейнера», — но устроены и применяются по-разному. Разница важна для DE: ты будешь монтировать DAG-файлы в Airflow (bind), хранить data directory Postgres (volume) и держать секреты в памяти (tmpfs).
Зачем вообще нужны mount’ы
У контейнера своя файловая система — overlay из слоёв образа плюс writable layer сверху. Запись в overlay медленнее, чем в обычную FS, и при удалении контейнера всё, что было в writable layer, исчезает. Если контейнер — Postgres, и его data directory лежит в writable layer, то docker rm postgres уничтожает базу. Это плохой план.
Mount’ы решают две задачи:
- Persistence — данные переживают рестарт и удаление контейнера.
- Sharing — файл с хоста (или другого контейнера) доступен внутри.
Mount, /etc/fstab и mount namespaces
Bind mount: путь с хоста
Bind mount — самый прямолинейный механизм. Берёшь существующую папку на хосте, говоришь Docker «покажи её внутри контейнера в такой-то точке».
docker run --rm -it \
-v /Users/me/dags:/opt/airflow/dags \
apache/airflow:2.10.0 bash
Внутри контейнера /opt/airflow/dags — это та же самая папка, что /Users/me/dags на хосте. Изменил файл в VS Code — изменения тут же видны в контейнере. Airflow scheduler перечитает DAG, обновит расписание. Никакого rebuild.
Альтернативный синтаксис, более многословный, но явный — --mount:
docker run --rm -it \
--mount type=bind,source=/Users/me/dags,target=/opt/airflow/dags \
apache/airflow:2.10.0 bash
Bind mount удобен для разработки, но в production он становится проблемой: чтобы развернуть Postgres на новой машине, нужно сначала вручную создать /var/lib/postgres-data с правильными правами. Это нарушает «runs anywhere» — главное обещание контейнеров.
Named volume: Docker управляет
Named volume — это «папка», которой управляет сам Docker. Ты задаёшь только имя, путь Docker выбирает сам и прячет под капотом.
docker volume create pgdata
docker run -d --name pg \
-v pgdata:/var/lib/postgresql/data \
-e POSTGRES_PASSWORD=secret \
postgres:17
Если volume не существовал — Docker создаст его сам при первом -v pgdata:.... Команда docker volume create нужна, только если хочется заранее задать driver или labels.
Где физически живут данные? Обычно в /var/lib/docker/volumes/<name>/_data/. На macOS внутри Docker Desktop это спрятано в VM, на OrbStack — тоже виртуализировано. Доступа «прямо в Finder» нет, и это намеренно: Docker хочет, чтобы ты ходил в volume только через контейнер.
Преимущества named volume:
- Portable. Запустил compose-стенд на новой машине — volume создастся автоматически.
- Backup/restore — стандартный паттерн. Через одноразовый контейнер, который монтирует volume и tar’ит содержимое.
- Volume drivers. Local — default, но бывают NFS, GlusterFS, плагины облачных провайдеров.
- Контейнер контролирует владельца. Postgres-образ при первом запуске сам выставит UID/GID на data directory.
Tmpfs: всё в RAM
Tmpfs — это файловая система в памяти. Linux tmpfs существует независимо от Docker, Docker просто умеет монтировать tmpfs внутрь контейнера.
docker run --rm -it \
--tmpfs /tmp:size=64m \
--tmpfs /run/secrets:size=8m,mode=0700 \
alpine sh
Внутри /tmp и /run/secrets — это память, не диск. Размер ограничен флагом size=. Когда контейнер остановится, данные исчезнут — гарантированно, потому что они никогда и не были на диске.
Tmpfs полезен для:
- Секретов. JWT-токен, расшифрованный пароль, временный API-key. Не должны попасть на диск даже на секунду.
- Скрэтч-файлов. Промежуточные tmp-файлы pandas-pipeline, которые всё равно удалятся.
- Защиты от перезаписи.
/tmpв production-контейнерах часто делают tmpfs, чтобы атакующий не мог записать туда payload, переживший рестарт.
Tmpfs ест RAM. Если задал size=2g и забил его реальными данными — у хоста стало на 2 ГБ меньше памяти для других процессов. Это легко устроить OOM на дев-машине.
Таблица «когда что»
| Свойство | bind mount | named volume | tmpfs |
|---|---|---|---|
| Где живёт | host path | Docker-managed | RAM |
| Persistence | да | да | нет |
| Backup стандартный | rsync host | docker run+tar | бессмысленен |
| Portable между PC | нет | да | да |
| Скорость на macOS | медленный | быстрый | очень быстрый |
| Видит host-программа | да | теоретически да | нет |
| Типичный DE use | DAG live edit | Postgres data | scratch /tmp |
Реальный пример: Airflow-стенд
В compose-стенде Airflow для DE-разработки одновременно используются все три типа:
services:
postgres:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
airflow-scheduler:
image: apache/airflow:2.10.0
volumes:
- ./dags:/opt/airflow/dags
- airflow-logs:/opt/airflow/logs
tmpfs:
- /tmp:size=128m
volumes:
pgdata:
airflow-logs:
pgdata— named volume для Postgres, чтобы метаданные Airflow (DagRun, TaskInstance) пережили рестарт../dags— bind mount: DAG-файлы я редактирую в IDE, Airflow scheduler их подхватывает.airflow-logs— named volume для логов Airflow, чтобы они не теряли историю./tmp— tmpfs, чтобы временные файлы task-runner не засоряли overlay.
Каждый mount — на своём месте. Если бы я положил Postgres data в ./pgdata (bind mount), на проде это превратилось бы в кошмар с правами доступа и переносом между машинами. Если бы положил DAG-файлы в named volume — пришлось бы каждый раз заходить в контейнер и редактировать там через vi.
Попробуй сам
# 1. Bind mount: проверь, что изменение на хосте видно в контейнере.
mkdir -p /tmp/bind-demo
echo "hello from host" > /tmp/bind-demo/note.txt
docker run --rm -it \
-v /tmp/bind-demo:/data \
alpine cat /data/note.txt
# Из второго терминала отредактируй файл, не выходя из контейнера:
echo "edited on host" >> /tmp/bind-demo/note.txt
# В контейнере: cat /data/note.txt — увидишь обновление.
# 2. Named volume: данные переживут удаление контейнера.
docker volume create demo-vol
docker run --rm -v demo-vol:/data alpine sh -c 'echo "persistent" > /data/file.txt'
# Контейнер вышел и удалился. Создадим новый и прочитаем:
docker run --rm -v demo-vol:/data alpine cat /data/file.txt
# -> persistent
# 3. tmpfs: после рестарта пусто.
docker run --rm -it --tmpfs /scratch:size=16m alpine sh
# В контейнере:
# echo "in-memory" > /scratch/x
# ls /scratch -> x
# exit
# Новый запуск:
docker run --rm -it --tmpfs /scratch:size=16m alpine ls /scratch
# -> пусто
На macOS bind mount работает через виртуализацию: Docker Desktop использует VirtioFS/gRPC FUSE, OrbStack — VirtFS поверх Linux VM. Поэтому bind mount на маке заметно медленнее, чем на Linux. Если pipeline жалуется на медленный I/O в bind-папке, это не баг — это архитектура.
В следующем уроке погружаемся в bind mount: синтаксис, права, macOS-specific нюансы.