Проброс каталогов и томов в Docker

🕒 5 мин.

В этой статье мы рассмотрим проброс каталогов и томов в контейнеры Docker. Первое используется для удобства редактирования файлов, второе для надёжного хранения данных.

Суть проблемы

К контейнерам DevOps инженеры относятся как к чему-то одноразовому. То есть их запросто могут удалить и пересоздать, не заботясь о данных хранимых в них. Часто контейнеры вообще разворачиваются и пересоздаются автоматически с помощью CI/CD инструментов. А настраивать каждый контейнер с помощью docker exec — это медленно и увеличивает риск ошибиться.

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

То-есть у нас есть 2 способа хранить полезные данные:

  • bind mount — способ привязать папку или файл с хоста внутрь контейнера.
  • named volumes — это создание специального тома на хосте и проброс его в контейнер.

Bind mount

Начнём наше изучение с bind mount. Это используют для того чтобы: изменения в контейнере были сразу видны на хосте, а изменения на хосте были сразу видны в контейнере. Удобно во время разработки, потому-что позволяет редактировать код на хосте, а изменения будут сразу видны в контейнере.

Подготовим рабочий каталог docker-app, все файлы будем размещать в нём.

Создадим приложение на python app.py (я редактирую приложение из предыдущей статьи):

from flask import Flask

# Для работы с переменными
import os

app = Flask(__name__)

# Читаем переменные окружения
greeting = os.getenv("GREETING", "Hello")
name = os.getenv("NAME", "World")

@app.route('/')
def hello():
   return f"""
   <h1>{greeting}, {name}!</h1>
   <a href="/file">Файл</a>
   """

@app.route('/file')
# Чтение из файла
def file():
   try:
      with open("data.txt", "r") as f:
         content = f.read()
   except:
      content = "Файл data.txt не найден"

   return f"<h1>{content}</h1>"

if __name__ == '__main__':
   app.run(host='0.0.0.0', port=5000, debug=True)
  • я добавил маршрут /file с функцией чтения из файла;
  • debug=True — необходим для автоматического перезапуска сервера при изменении кода.

А также, создадим requirements.txt — это зависимости для приложения:

Flask==2.3.3

Создадим файл, который будем читать — data.txt:

Text from a file located on the host!

Создадим Dockerfile:

# Базовый минимум (то-еть образ)
FROM python:3.11-slim

# Объявим порт
EXPOSE 5000

# Рабочий каталог
WORKDIR /app

# Копируем и устанавливаем зависимости
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Не копируем файлы в образ, чтобы показать что они на хосте

# Запускаем приложение при старте контейнера
CMD ["python", "app.py"]

Соберём образ:

$ docker build -t my_flask_app .

Запустим контейнер с bind mount (опция -v):

$ docker run -d -p 5000:5000 --name my_flask_app \
  -v $(pwd):/app my_flask_app
  • $(pwd) — текущий каталог на хосте;
  • /app — путь внутри контейнера (рабочий каталог из образа).

Проверим что контейнер работает:

$ docker ps
IMAGE        COMMAND         STATUS        PORTS                  NAMES
my_flask_app "python app.py" Up 23 seconds 0.0.0.0:5000->5000/tcp my_flask_app

Проверим работу:

$ curl http://localhost:5000
   <h1>Hello, World!</h1>
   <a href="/file">Файл</a>

$ curl http://localhost:5000/file
   <h1>Text from a file located on the host!</h1>
  • Я проверяю с помощью curl, но если есть возможность, просто откройте страничку в браузере.

Изменим файл на хосте — data.txt:

Text changed!

Проверим, без пересоздания или перезагрузки контейнера:

$ curl http://localhost:5000/file
   <h1>Text changed!</h1>

Мы только что использовали Live Reload или Hot Reload без пере-сборки образа!

Изменим сам код приложения app.py (строку с return, где мы читаем файл):

# это
return f"<h1>{content}</h1>"

# заменим на это
return f"""
<h1>Live reload!</h1>
<p>{content}</p>
"""

Проверим:

$ curl http://localhost:5000/file
   <h1>Live reload!</h1>
   <p>Text changed!</p>

Удалим контейнер, и проверим что данные не исчезли:

$ docker stop my_flask_app
$ docker rm my_flask_app

$ cat data.txt
Text changed!

Советы и предупреждения:

  • Используйте $(pwd) или абсолютные пути (/home/user/app);
  • В продакшене избегайте bind mounts — они привязаны к конкретной машине. Там лучше использовать named volumes, их мы рассмотрим далее.
  • Bind mounts идеальны для разработки, так как позволяют быстро редактировать и отлаживать код.

Named volume

Именованные тома (named volumes) используется для:

  • баз данных (MySQL, PostgreSQL);
  • кеша (Redis);
  • очередей (RabbitMQ, Kafka).

Такими томами управляет сам Docker.

Создадим рабочий каталог docker-volumes, все файлы будем создавать в нём.

Named volume необходимо предварительно создать:

$ docker volume create db_data

Посмотрим на список volumes:

$ docker volume ls
DRIVER    VOLUME NAME
local     db_data

С помощью команды docker volume inspect можем исследователь том:

$ docker volume inspect db_data
[
    {
        "CreatedAt": "2025-10-19T18:05:07+03:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/db_data/_data",
        "Name": "db_data",
        "Options": null,
        "Scope": "local"
    }
]
  • /var/lib/docker/volumes/db_data/_data — это путь на хосте, где физически находится volume.

Контейнер с Postgres

Для тестирования создадим контейнер с Postgres. Но для начала создадим файл .env — с помощью которого будем передавать необходимые переменные контейнеру:

POSTGRES_DB=myapp
POSTGRES_USER=admin
POSTGRES_PASSWORD=secret

Возьмём официальный образ из DockerHub. Он подготовлен специальным образом чтобы считывать эти переменные.

Для запуска контейнера воспользуемся такой командой:

$ docker run -d --name postgres-db --env-file .env \
 -v db_data:/var/lib/postgresql/data postgres:15

Подключимся к контейнеру (сразу к psql):

$ docker exec -it postgres-db psql -U admin -d myapp

Создадим таблицу, вставим в неё одну строчку и выйдем:

myapp=# CREATE TABLE users (id SERIAL, name TEXT);
myapp=# INSERT INTO users (name) VALUES ('Alice');
myapp=# \q

Пересоздадим контейнер с тем же volume:

$ docker stop postgres-db
$ docker rm postgres-db

$ docker run -d --name postgres-db --env-file .env \
 -v db_data:/var/lib/postgresql/data postgres:15

Подключимся и проверим что данные на месте:

$ docker exec -it postgres-db psql -U admin -d myapp

myapp=# SELECT * FROM users;
 id | name
----+-------
  1 | Alice
(1 row)

myapp=# \q

Чтобы не засорять систему удалим контейнер:

$ docker stop postgres-db
$ docker rm postgres-db

В отличии от bind mount, named volume:

  • Лучше переносимы: работают одинокого на любом хосте, не зависят от путей в ОС.
  • Управление Docker: резервное копирование, инспектирование, очистка.

Контейнер с Redis

Для закрепления создадим ещё один контейнер с Redis.

Создадим volume для Redis:

$ docker volume create app_cache

Создадим контейнер redis:

$ docker run -d --name my_redis -v app_cache:/data redis

Подключимся к контейнеру:

$ docker exec -it my_redis redis-cli

Добавим пару ключей:

127.0.0.1:6379> SET test "hello"
127.0.0.1:6379> SET test2 "hello2"
127.0.0.1:6379> exit

Пересоздадим контейнер:

$ docker stop my_redis
$ docker rm my_redis
$ docker run -d --name my_redis -v app_cache:/data redis

Проверим что данные остались:

$ docker exec -it my_redis redis-cli

127.0.0.1:6379> get test
"hello"

127.0.0.1:6379> get test2
"hello2"

127.0.0.1:6379> exit

Посмотрим где физически лежат данные:

$ docker volume inspect app_cache
[
    {
        "CreatedAt": "2025-09-19T15:55:49+03:00",
        "Driver": "local",
        "Labels": null,
        "Mountpoint": "/var/lib/docker/volumes/app_cache/_data",
        "Name": "app_cache",
        "Options": null,
        "Scope": "local"
    }
]

$ sudo ls /var/lib/docker/volumes/app_cache/_data
dump.rdb

Итог

Мы научились пробрасывать каталог с хоста в контейнер — это называется Bind mount.

-v $(pwd):/app

А также научились создавать именованные тома (named volume) для контейнеров:

docker volume create app_cache

И использовать named volume при создании контейнеров:

-v app_cache:/data redis

Запомните что опция для проброса одна -v. Мы просто можем указать каталог (или даже файл), либо можем указать заранее подготовленный том.

Мы используем cookie-файлы для наилучшего представления нашего сайта. Продолжая использовать этот сайт, вы соглашаетесь с использованием cookie-файлов.
Принять
Отказаться
Политика конфиденциальности