Docker Compose: bind mount czy named volume?
Sprawdzenie, kiedy użyć bind mount, a kiedy named volume w Docker Compose, pomaga uniknąć błędów uprawnień oraz bezpiecznie inspektować, tworzyć kopie i migrować dane.
Montowanie bind czy nazwany wolumin: krótka odpowiedź
Woluminy Docker Compose występują w dwóch rodzajach, a wybór zależy od tego, kto zarządza plikami. Montowania bind należy używać w przypadku plików zapisywanych i odczytywanych bezpośrednio, takich jak konfiguracja, szablony i witryny statyczne. Nazwanego woluminu należy używać w przypadku danych, którymi zarządza aplikacja, takich jak pliki bazy danych, indeksy wyszukiwania i przesłane pliki multimedialne. Montowanie bind wskazuje ścieżkę na hoście, którą można otworzyć w edytorze. Nazwany wolumin jest magazynem tworzonym i śledzonym przez Docker. Dostęp do niego uzyskuje się za pośrednictwem Docker.
Oba rodzaje są określane za pomocą tego samego klucza volumes: w sekcji usługi, dlatego bywają mylone. Różnica znajduje się po lewej stronie dwukropka. Lewa strona zaczynająca się od . lub / oznacza ścieżkę na hoście, więc jest to montowanie bind. Każda inna wartość jest nazwą, więc jest to nazwany wolumin. Nazwę tę należy również zadeklarować w bloku najwyższego poziomu volumes:.
Dwie składnie w pliku compose
services:
db:
image: postgres:17
environment:
POSTGRES_PASSWORD: changeme
volumes:
- pgdata:/var/lib/postgresql/data
web:
image: nginx:1.27
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./site:/usr/share/nginx/html:ro
volumes:
pgdata:pgdata:/var/lib/postgresql/data jest wolumenem nazwanym. ./nginx.conf:/etc/nginx/nginx.conf jest montowaniem bind, a :ro montuje go tylko do odczytu. Jest to właściwe ustawienie domyślne dla konfiguracji, której kontener nie powinien nigdy modyfikować. Pominięcie wpisu najwyższego poziomu volumes: powoduje zatrzymanie Compose z błędem service "db" refers to undefined volume pgdata.
Uruchom usługi i wyświetl utworzone obiekty Docker:
docker compose up -d
docker volume lsWolumen nie nazywa się pgdata. Nazywa się <project>_pgdata, ponieważ nazwa projektu jest domyślnie ustalana na podstawie nazwy katalogu zawierającego plik compose. Katalog o nazwie myapp daje myapp_pgdata. Ma to znaczenie, ponieważ zmiana nazwy katalogu powoduje utworzenie nowego, pustego wolumenu. Aplikacja wygląda wtedy tak, jakby utraciła dane. Tak się nie dzieje: stary wolumen nadal jest wyświetlany przez docker volume ls. Ustal nazwę za pomocą name: w pliku compose albo ustaw COMPOSE_PROJECT_NAME, jeśli katalog może zostać przeniesiony. Takie ustawienia należy przechowywać razem z innymi plikami środowiskowymi Compose i sekretami.
Dlaczego błędy uprawnień dotyczą tylko bind mountów
Jest to najważniejsza różnica praktyczna. Wynika ona z jednej zasady: pusty przy pierwszym użyciu named volume jest inicjalizowany zawartością obrazu, a bind mount nigdy nie jest w ten sposób inicjalizowany.
Gdy Docker montuje pusty named volume w miejscu katalogu, który już zawiera dane w obrazie, kopiuje te dane do wolumenu wraz z właścicielem i trybami ustawionymi w obrazie. Oficjalny obraz Postgres dostarcza /var/lib/postgresql/data należący do własnego użytkownika postgres. Wolumen zostaje więc przypisany do tego samego identyfikatora numerycznego, a baza danych uruchamia się.
Bind mount działa odwrotnie. Kontener widzi zawartość hosta, w tym jej właściciela, a zawartość obrazu w tej ścieżce zostaje ukryta. Jeśli katalog na hoście nie istnieje, Docker daemon tworzy go jako root. Powstaje więc katalog należący do root:root. Proces kontenera uruchomiony jako użytkownik inny niż root nie może wtedy zapisywać w tym katalogu:
PermissionError: [Errno 13] Permission denied: '/data/app.db'Należy uzgodnić wartości liczbowe. Właściciel bind mountu jest porównywany na podstawie numerycznego identyfikatora użytkownika, a nie nazwy, ponieważ kontener ma własny /etc/passwd. Użytkownik o nazwie app w kontenerze nie ma żadnego znaczenia na hoście. Uid 1000 oznacza uid 1000 po obu stronach.
id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./datadocker compose exec web id wyświetla uid, z którym faktycznie działa proces kontenera. Należy przypisać katalog na hoście do tego numeru albo wymusić działanie kontenera z własnym numerem za pomocą user: "1000:1000" w usłudze. Wymuszenie user: jest lepszym rozwiązaniem w przypadku samodzielnie napisanego oprogramowania. Zmiana właściciela katalogu na hoście jest bezpieczniejsza w przypadku obrazu, którego nie napisano samodzielnie, ponieważ niektóre obrazy uruchamiają entrypoint jako root, następnie obniżają uprawnienia i wymagają określonego właściciela w podkatalogach.
Warto znać jeszcze dwie pułapki. W systemach Fedora, RHEL i innych systemach z wymuszaniem SELinux (security-enhanced Linux) bind mount jest odrzucany do czasu zmiany etykiety. Należy więc dodać :z w przypadku ścieżki współdzielonej między kontenerami albo :Z w przypadku ścieżki przeznaczonej tylko dla jednego kontenera, zapisując to jako - ./data:/data:Z. Bind mount pojedynczego pliku, a nie katalogu, przestaje działać, gdy edytor zastępuje plik zamiast zapisywać bezpośrednio w jego miejscu, ponieważ mount śledzi pierwotny inode. Kontener nadal widzi starą zawartość do czasu ponownego uruchomienia. Jeśli plik jest często edytowany, należy zamontować katalog nadrzędny.
Wydajność: gdzie różnica jest rzeczywista
Na serwerze Linux oba typy korzystają z tej samej ścieżki w jądrze, dlatego różnica przepustowości jest na tyle mała, że nie należy na tej podstawie dokonywać wyboru. Wolumeny nazwane, korzystające z domyślnego sterownika local, znajdują się w tym samym systemie plików co pozostała część Docker, w lokalizacji /var/lib/docker/volumes/, natomiast montowanie bind znajduje się w lokalizacji wskazanej podczas konfiguracji.
Różnica jest widoczna w Docker Desktop dla macOS i Windows, gdzie kontenery działają wewnątrz maszyny wirtualnej. Montowanie bind przechodzi tam z systemu plików hosta do tej maszyny wirtualnej przez warstwę udostępniania plików. Obciążenia wykonujące wiele operacji na małych plikach, takie jak drzewo zależności Node.js lub pamięć podręczna frameworka PHP, działają wtedy zauważalnie wolniej. Wolumney nazwane pozostają wewnątrz maszyny wirtualnej i nie ponoszą tego kosztu. Dlatego w tak wielu plikach compose używanych podczas programowania katalog źródłowy jest montowany za pomocą bind, a dla node_modules deklaruje się wolumen nazwany.
Druga istotna różnica dotyczy miejsca zapisu danych. Montowanie bind do /mnt/backup zapisuje dane na tym dysku. Wolumen nazwany jest zapisywany w systemie plików, w którym znajduje się /var/lib/docker, a na VPS jest to zwykle dysk główny. Baza danych rozrastająca się wewnątrz wolumenu nazwanego zapełnia ten sam dysk, na którym znajdują się dzienniki systemowe. Należy sprawdzić to przed wystąpieniem incydentu:
docker system df -v
df -h /var/lib/dockerdocker system df -v wyświetla wszystkie wolumeny wraz z ich rozmiarem i oznacza te, do których nie odwołuje się już żaden kontener.
Kontrola nazwanego woluminu
Nazwany wolumin nie jest czarną skrzynką. Można sprawdzić jego lokalizację w Dockerze:
docker volume inspect myapp_pgdataPole Mountpoint zawiera rzeczywistą ścieżkę na hoście, zwykle /var/lib/docker/volumes/myapp_pgdata/_data. Można ją odczytać za pomocą sudo ls, co jest przydatne podczas szybkiej kontroli. Nie należy traktować jej jako miejsca do edycji plików. Zapis w tej lokalizacji z uprawnieniami root odtwarza opisany wcześniej problem z właścicielem, a sama ścieżka jest szczegółem sterownika local, którego nie współdzielą inne sterowniki woluminów.
Bezpiecznym sposobem wyświetlenia zawartości jest użycie kontenera tymczasowego, który zamontuje wolumin:
docker run --rm -v myapp_pgdata:/vol alpine ls -la /volDziała to z każdym sterownikiem, zapewnia dostęp z tymi samymi uprawnieniami, które ma rzeczywisty kontener, i nie pozostawia żadnych danych dzięki --rm.
Tworzenie kopii zapasowych każdego typu
Bind mount jest zwykłym katalogiem, dlatego każde narzędzie do tworzenia kopii zapasowych na poziomie plików już go obsługuje. Należy wskazać ścieżkę na hoście. W przypadku named volume wymagany jest dodatkowy krok, ponieważ narzędzie musi uzyskać do niego dostęp. Należy zamontować volume i katalog na hoście w tym samym krótkotrwałym kontenerze, a następnie utworzyć archiwum:
docker run --rm \
-v myapp_pgdata:/data:ro \
-v "$PWD":/backup \
alpine tar czf /backup/pgdata.tar.gz -C /data .Przywracanie wykonuje się, odwracając tę operację do nowego volume:
docker volume create myapp_pgdata_restored
docker run --rm \
-v myapp_pgdata_restored:/data \
-v "$PWD":/backup \
alpine tar xzf /backup/pgdata.tar.gz -C /datatar zachowuje numeryczną własność plików, gdy polecenie jest wykonywane jako root wewnątrz kontenera. Dzięki temu przywrócony volume pozostaje dostępny dla aplikacji.
Dotyczy to obu typów. Kopiowanie plików bazy danych podczas jej działania tworzy archiwum danych, które mogą się zmieniać, i może spowodować przywrócenie uszkodzonego stanu. Najpierw należy zatrzymać usługę albo wykonać zrzut za pomocą narzędzia właściwego dla bazy danych, jak w docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Powstaje wtedy zwykły plik, który można dołączyć do standardowej szyfrowanej procedury tworzenia kopii zapasowych za pomocą restic razem z plikami compose.
Migracja bind mount do nazwanego wolumenu
Przeniesienie polega na kopiowaniu, a nie zmianie nazwy, i trwa około minuty.
docker compose down
docker volume create myapp_pgdata
docker run --rm \
-v "$PWD/data":/from \
-v myapp_pgdata:/to \
alpine sh -c 'cp -a /from/. /to/'cp -a zachowuje właściciela, uprawnienia i znaczniki czasu, dzięki czemu użytkownik kontenera, który mógł odczytywać stary katalog, nadal może odczytywać nowy wolumen. Następnie należy zmienić konfigurację usługi tak, aby używała pgdata:/var/lib/postgresql/data, dodać pgdata do głównego bloku volumes:, uruchomić docker compose up -d i odczytać logi aplikacji przed usunięciem starego katalogu. W przeciwnym kierunku należy użyć tego samego polecenia, zamieniając miejscami /from i /to.
Podczas testów należy pamiętać o jednej kwestii. docker compose down pozostawia nazwane wolumeny bez zmian, natomiast docker compose down -v usuwa każdy nazwany wolumen zadeklarowany przez projekt i nie można tego cofnąć. Bind mount przetrwa oba polecenia, ponieważ Docker nigdy nie zarządzał tym katalogiem. Jeśli polecenia zarządzające cyklem życia są nadal nowe, przewodnik po podstawach Docker Compose dla VPS omawia je krok po kroku.
Wybór dla każdej usługi
Należy ustalić, który proces zapisuje dany plik. Konfiguracja edytowana w edytorze tekstu i zatwierdzana w git powinna znajdować się w bind mount, montowanym za pomocą :ro, ponieważ musi być dostępna i objęta kontrolą wersji. Stan aplikacji, którego nie otwiera się ręcznie, powinien znajdować się w named volume, ponieważ Docker prawidłowo ustawia uprawnienia, a dane nie zależą od ścieżki na hoście.
Przypadkiem mieszanym są media. Biblioteka zdjęć jest zapisywana przez aplikację, ale zarządza nią również użytkownik. Często jest też na tyle duża, że wymaga użycia określonego dysku. Należy zamontować ją w ścieżce na tym dysku i jednorazowo jawnie ustawić właściciela. Taki układ jest najczęściej stosowany w samodzielnie hostowanych stosach usług: named volumes dla baz danych i pamięci podręcznych, bind mounts dla konfiguracji oraz dla dużego katalogu, który ma znaczenie dla użytkownika.
FAQ
Jaka jest różnica między bind mount a named volume?
Bind mount mapuje ścieżkę na hoście do kontenera, więc obie strony widzą ten sam katalog i można go edytować za pomocą standardowych narzędzi. Named volume to przestrzeń danych tworzona i zarządzana przez Docker, wskazywana nazwą i zadeklarowana w bloku najwyższego poziomu volumes:. W praktyce różnica dotyczy sposobu zarządzania: bind mount służy do konfiguracji utrzymywanej przez administratora, a named volume do danych zarządzanych przez aplikację.
Dlaczego bind mount zwraca „permission denied”, a named volume nie?
Pusty named volume jest inicjalizowany na podstawie obrazu, dlatego dziedziczy ustawionego w nim właściciela i użytkownik kontenera może w nim zapisywać. Bind mount pokazuje katalog hosta dokładnie w jego bieżącym stanie. Jeśli Docker musiał utworzyć ten katalog, ustawił jego właściciela jako root. Uruchom docker compose exec <service> id, aby sprawdzić identyfikator liczbowy używany przez kontener, a następnie sudo chown -R <uid>:<gid> katalog hosta albo ustaw user: "1000:1000" w usłudze.
Gdzie Docker przechowuje named volumes na dysku?
W przypadku domyślnego sterownika local są one przechowywane w /var/lib/docker/volumes/<volume>/_data, a polecenie docker volume inspect <volume> wyświetla dokładną wartość Mountpoint. Należy ją odczytać, jeśli trzeba coś sprawdzić, ale zapis powinien odbywać się wyłącznie za pośrednictwem kontenera, ponieważ edycja jako root na hoście zmienia właściciela w sposób niezgodny z oczekiwaniami kontenera.
Jak utworzyć kopię zapasową named volume?
Uruchom tymczasowy kontener z zamontowanym zarówno wolumenem, jak i katalogiem hosta, a następnie zarchiwizuj dane z jednego miejsca do drugiego za pomocą docker run --rm -v myvol:/data:ro -v "$PWD":/backup alpine tar czf /backup/myvol.tar.gz -C /data .. W przypadku bazy danych użyj narzędzia dostarczanego przez tę bazę zamiast kopiować aktywne pliki, ponieważ kopia wykonana podczas trwających zapisów może zostać odtworzona w uszkodzonym stanie.
Czy docker compose down usuwa moje wolumeny?
docker compose down usuwa kontenery i sieci, pozostawiając named volumes. docker compose down -v usuwa również wszystkie named volumes zadeklarowane przez projekt, bez możliwości ich odzyskania. Bind mounts nie są usuwane przez żadne z tych poleceń, ponieważ katalog należy do hosta, a nie do Docker.