SSD Nodes Learn Hosting plans →
Przewodniki Matt ConnorAutor: Matt Connor · Zaktualizowano 2026-10-30

Docker Compose: bind mount a named volume - różnice

Analiza różnic między bind mount a named volume w Docker Compose. Wyjaśnienie kiedy stosować ścieżki hosta, a kiedy konteneryzowane wolumeny oraz jak unikać błędów uprawnień.

Bind mount czy named volume: krótka odpowiedź

Wolumeny w Docker Compose występują w dwóch rodzajach, a wybór zależy od tego, kto zarządza plikami. Bind mount należy stosować dla plików edytowanych i odczytywanych przez użytkownika, takich jak konfiguracje, szablony czy statyczne strony WWW. Named volume służy do przechowywania danych zarządzanych przez aplikację, na przykład plików baz danych, indeksów wyszukiwania czy przesłanych mediów. Bind mount wskazuje na ścieżkę w systemie hosta, którą można otworzyć w edytorze. Named volume to przestrzeń dyskowa tworzona i śledzona przez Docker, do której dostęp uzyskuje się za pośrednictwem narzędzi Docker.

Oba typy pojawiają się pod tym samym kluczem volumes: wewnątrz definicji usługi, co prowadzi do częstych pomyłek. Różnica polega na zawartości po lewej stronie dwukropka. Jeśli lewa strona zaczyna się od . lub /, jest to ścieżka w systemie hosta, czyli bind mount. Każda inna wartość jest nazwą, co oznacza named volume; taka nazwa musi zostać dodatkowo zadeklarowana w sekcji volumes: na najwyższym poziomie pliku.

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 to wolumen nazwany. ./nginx.conf:/etc/nginx/nginx.conf to montowanie typu bind mount, a :ro montuje je w trybie tylko do odczytu, co jest właściwym ustawieniem domyślnym dla konfiguracji, której kontener nie powinien modyfikować. Pomiń wpis najwyższego poziomu volumes:, a Compose zatrzyma się z błędem service "db" refers to undefined volume pgdata.

Uruchom usługę i wyświetl zasoby utworzone przez Docker:

docker compose up -d
docker volume ls

Wolumen nie nazywa się pgdata. Nazywa się <project>_pgdata, gdzie nazwa projektu domyślnie przyjmuje nazwę katalogu zawierającego plik compose. Katalog o nazwie myapp skutkuje nazwą myapp_pgdata. Jest to istotne, ponieważ zmiana nazwy katalogu spowoduje utworzenie nowego, pustego wolumenu, a aplikacja będzie wyglądać tak, jakby utraciła dane. W rzeczywistości tak się nie stało: stary wolumen jest nadal widoczny po wykonaniu docker volume ls. Przypisz nazwę za pomocą name: w pliku compose lub ustaw COMPOSE_PROJECT_NAME, jeśli katalog może zostać przeniesiony. Tego typu ustawienia powinny znajdować się wraz z innymi plikami środowiskowymi i sekretami Compose.

Dlaczego błędy uprawnień dotyczą głównie bind mountów

Jest to najważniejsza różnica praktyczna, wynikająca z jednej zasady: nazwany wolumen (named volume), który jest pusty przy pierwszym użyciu, jest inicjowany zawartością obrazu, podczas gdy bind mount nigdy nie jest.

Gdy Docker montuje pusty nazwany wolumen w miejscu katalogu, który posiada już zawartość w obrazie, kopiuje tę zawartość do wolumenu, zachowując właściciela i uprawnienia ustawione w obrazie. Oficjalny obraz Postgres zawiera /var/lib/postgresql/data, którego właścicielem jest jego własny użytkownik postgres, więc wolumen otrzymuje tego samego właściciela o tym samym identyfikatorze numerycznym, a baza danych uruchamia się poprawnie.

To inicjowanie jest jedyną przewagą nazwanego wolumenu. Docker zarządza lokalizacją wolumenu, ale kontenery odczytują i zapisują go za pomocą standardowych operacji systemu plików, więc uprawnienia i właściciel są egzekwowane tak samo, jak w przypadku bind mounta. Nazwany wolumen zamontowany w ścieżce, której obraz nie posiada, lub taki, który został najpierw wypełniony przez inny obraz lub użytkownika, nie przejmuje uprawnień z obrazu, przez co proces niebędący rootem może również napotkać odmowę dostępu. Sprawdzenie id opisane poniżej działa dla obu typów.

Bind mount działa odwrotnie. Kontener widzi dokładnie to, co znajduje się na hoście, włącznie z uprawnieniami, a zawartość obrazu w tej ścieżce jest ukryta. Jeśli katalog na hoście nie istnieje, daemon Docker tworzy go, działając jako root, co skutkuje katalogiem, którego właścicielem jest root:root. Proces kontenera działający jako użytkownik niebędący rootem nie może wtedy zapisać do niego danych:

PermissionError: [Errno 13] Permission denied: '/data/app.db'

Rozwiązaniem jest dopasowanie identyfikatorów numerycznych. Właściciel w bind mount jest porównywany według numerycznego identyfikatora użytkownika (uid), a nie nazwy, ponieważ kontener posiada własny plik /etc/passwd. Użytkownik o nazwie app wewnątrz kontenera nie ma znaczenia na hoście. Uid 1000 oznacza uid 1000 po obu stronach, chyba że daemon działa z mapowaniem przestrzeni nazw użytkowników (userns-remap, ustawiane w /etc/docker/daemon.json lub w linii poleceń dockerd). Mapowanie przesuwa każdy identyfikator kontenera do podrzędnego zakresu na hoście. W przykładowym zakresie Dockera zaczynającym się od 231072, uid 0 kontenera to uid 231072 hosta, więc uid 1000 kontenera to uid 232072 hosta.

id -u
mkdir -p ./data
docker compose exec web id
sudo chown -R 1000:1000 ./data

docker compose exec web id wyświetla uid, z którym faktycznie działa proces kontenera. Bez mapowania należy dopasować katalog na hoście do tego numeru lub przypisać kontener do własnego numeru za pomocą user: "1000:1000" w usłudze. Przy włączonym mapowaniu, user: nie może przypisać kontenera do numeru hosta, ponieważ ustawiony identyfikator również zostaje przesunięty: user: "1000:1000" w tym przykładzie nadal trafia na uid 232072 hosta. Należy zmienić właściciela (chown) katalogu na hoście na zmapowany identyfikator hosta (własny zakres znajduje się w /etc/subuid i /etc/subgid). Dokumentacja Dockera wymienia tylko --userns=host w docker container create, run lub exec jako sposób na wyłączenie mapowania dla pojedynczego kontenera. Przypisanie user: jest czystszym rozwiązaniem dla samodzielnie napisanej aplikacji. Zmiana właściciela katalogu na hoście jest bezpieczniejsza dla obrazu, którego nie jesteśmy autorami, ponieważ niektóre obrazy uruchamiają punkt wejścia (entrypoint) jako root, porzucają uprawnienia i oczekują konkretnego właściciela wewnątrz.

Warto znać jeszcze dwie pułapki. W systemach Fedora, RHEL i innych z włączonym SELinux (security-enhanced Linux), bind mount jest blokowany do czasu zmiany etykiety, dlatego należy dodać :z dla ścieżki współdzielonej między kontenerami lub :Z dla ścieżki używanej tylko przez jeden kontener, zapisywane jako - ./data:/data:Z. Ponadto bind mount pojedynczego pliku, zamiast katalogu, przestaje działać, gdy edytor zastępuje plik zamiast zapisu w miejscu, ponieważ montowanie podąża za oryginalnym inodem. Kontener widzi starą zawartość do momentu restartu. W przypadku częstej edycji pliku należy montować katalog nadrzędny.

Wydajność: gdzie różnica jest odczuwalna

Na serwerze z systemem Linux oba typy wolumenów korzystają z tej samej ścieżki w jądrze systemu, więc różnica w przepustowości jest na tyle mała, że nie powinna stanowić kryterium wyboru. Nazwane wolumeny korzystające z domyślnego sterownika local znajdują się w tym samym systemie plików co reszta Docker, w lokalizacji /var/lib/docker/volumes/, natomiast bind mount znajduje się w miejscu wskazanym przez użytkownika.

Różnica pojawia się w Docker Desktop dla systemów macOS oraz Windows, gdzie kontenery działają wewnątrz maszyny wirtualnej. W takim przypadku bind mount przechodzi z systemu plików hosta do maszyny wirtualnej przez warstwę współdzielenia plików, co powoduje zauważalne spowolnienie przy zadaniach wymagających wielu operacji na małych plikach, takich jak drzewo zależności Node.js czy pamięć podręczna frameworka PHP. Nazwane wolumeny pozostają wewnątrz maszyny wirtualnej i nie generują tego narzutu. Z tego powodu wiele plików Compose używanych w środowiskach programistycznych montuje katalog źródłowy jako bind mount, ale deklaruje nazwany wolumen dla node_modules.

Drugą istotną różnicą jest miejsce zapisu danych. Bind mount do /mnt/backup umieszcza dane na wskazanym dysku. Nazwany wolumen trafia do systemu plików, w którym znajduje się /var/lib/docker, co w przypadku serwera VPS zazwyczaj oznacza dysk główny. Baza danych rozrastająca się wewnątrz nazwanego wolumenu zapełnia ten sam dysk, na którym zapisywane są logi systemowe. Należy to sprawdzić, zanim dojdzie do awarii:

docker system df -v
df -h /var/lib/docker

docker system df -v wyświetla listę wszystkich wolumenów wraz z ich rozmiarem i oznacza te, do których nie odwołuje się już żaden kontener.

Inspekcja wolumenu nazwanego

Wolumen nazwany nie jest czarną skrzynką. Należy zapytać Docker o jego lokalizację:

docker volume inspect myapp_pgdata

Pole Mountpoint wskazuje rzeczywistą ścieżkę w systemie hosta, zazwyczaj /var/lib/docker/volumes/myapp_pgdata/_data. Można ją odczytać za pomocą sudo ls, co jest przydatne do szybkiej weryfikacji. Nie należy traktować tego miejsca jako lokalizacji do edycji plików. Zapisywanie tam plików jako root powoduje ponowne wystąpienie problemów z uprawnieniami opisanych powyżej, a sama ścieżka jest szczegółem implementacyjnym sterownika local, którego nie posiadają inne sterowniki wolumenów.

Bezpiecznym sposobem na zajrzenie do wnętrza wolumenu jest użycie tymczasowego kontenera, który go zamontuje:

docker run --rm -v myapp_pgdata:/vol alpine ls -la /vol

Metoda ta działa z każdym sterownikiem, uwzględnia te same uprawnienia, które widzi właściwy kontener, i nie pozostawia żadnych śladów dzięki fladze --rm.

Wykonywanie kopii zapasowych poszczególnych typów

Bind mount to zwykły katalog, więc każde narzędzie do tworzenia kopii zapasowych na poziomie plików obsłuży go bez problemu. Wystarczy wskazać ścieżkę na hoście i gotowe. Named volume wymaga dodatkowego kroku, ponieważ narzędzie musi uzyskać dostęp do jego zawartości. Należy zamontować wolumen oraz katalog hosta w tym samym tymczasowym 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 danych polega na odwróceniu tego procesu do nowego wolumenu:

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 /data

tar zachowuje numeryczne identyfikatory właścicieli plików, gdy działa jako root wewnątrz kontenera, co pozwala na poprawne korzystanie z przywróconego wolumenu przez aplikację.

Jedno ostrzeżenie dotyczy obu typów. Kopiowanie plików bazy danych w trakcie jej pracy tworzy archiwum "ruchomego celu", co może prowadzić do przywrócenia bazy w stanie uszkodzonym. Należy najpierw zatrzymać usługę lub wykonać zrzut za pomocą narzędzia dostarczonego z bazą danych, jak w docker compose exec -T db pg_dump -U postgres appdb > appdb.sql. Pozwala to uzyskać zwykły plik, który można następnie uwzględnić w standardowej szyfrowanej procedurze kopii zapasowych restic wraz z plikami compose.

Migracja bind mount do nazwanego wolumenu

Przeniesienie jest operacją kopiowania, a nie zmiany nazwy, i zajmuje 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 oraz znaczniki czasu, dzięki czemu użytkownik kontenera, który miał dostęp do starego katalogu, może odczytać nowy wolumen. Następnie należy zmodyfikować usługę, aby korzystała z pgdata:/var/lib/postgresql/data, dodać pgdata do sekcji volumes: najwyższego poziomu, wykonać docker compose up -d i sprawdzić logi aplikacji przed usunięciem starego katalogu. W przypadku operacji w drugą stronę stosuje się to samo polecenie z zamienionymi /from oraz /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 w projekcie i operacji tej nie można cofnąć. Bind mount przetrwa oba polecenia, ponieważ Docker nigdy nie był właścicielem tego katalogu. Jeśli polecenia cyklu życia są jeszcze nieznane, przewodnik po podstawach Docker Compose dla VPS zawiera ich szczegółowe omówienie.

Wybór strategii dla poszczególnych usług

Należy ustalić, kto zapisuje pliki. Konfigurację edytowaną w edytorze tekstu i zatwierdzaną w git należy umieścić w bind mount, zamontowanym w :ro, ponieważ powinna być widoczna i wersjonowana. Stan aplikacji, którego nigdy nie modyfikuje się ręcznie, powinien znajdować się w named volume, ponieważ Docker nadaje uprawnienia na podstawie obrazu, a dane nie zależą od ścieżki na hoście.

Przypadkiem mieszanym są media. Biblioteka zdjęć jest zapisywana przez aplikację, ale zarządzana przez użytkownika i często jest na tyle duża, że wymaga dedykowanego dysku. Należy użyć bind mount do ścieżki na tym dysku i jednorazowo ustawić odpowiednie uprawnienia. Jest to wzorzec stosowany w większości rozwiązań self-hosted: named volumes dla baz danych i pamięci podręcznych, a bind mounts dla konfiguracji oraz dużych katalogów wymagających nadzoru. System obsługi zgłoszeń, taki jak Chatwoot działający na VPS, wykorzystuje dokładnie ten model: Postgres w named volume, a załączniki w ścieżce, którą można objąć kopią zapasową.

FAQ

Jaka jest różnica między bind mount a nazwanym wolumenem?

Bind mount mapuje ścieżkę na hoście do kontenera, dzięki czemu obie strony widzą ten sam katalog i można go edytować standardowymi narzędziami. Nazwany wolumen to przestrzeń dyskowa tworzona i zarządzana przez Dockera, określana nazwą i deklarowana w bloku najwyższego poziomu volumes:. Praktyczny podział dotyczy własności: bind mounty służą do konfiguracji utrzymywanej przez użytkownika, nazwane wolumeny do danych utrzymywanych przez aplikację.

Dlaczego otrzymuję "permission denied" przy bind mount, a nie przy nazwanym wolumenie?

Pusty nazwany wolumen jest inicjowany zawartością obrazu, więc zazwyczaj dziedziczy uprawnienia ustawione w obrazie i użytkownik kontenera może w nim zapisywać. Nadal egzekwuje on uprawnienia, więc może zawieść w ten sam sposób, jeśli obraz nie posiada nic w danej ścieżce. Bind mount pokazuje katalog hosta dokładnie w takim stanie, w jakim jest, a jeśli Docker musiał go utworzyć, właścicielem został root. Uruchom docker compose exec <service> id, aby sprawdzić identyfikator numeryczny używany przez kontener, a następnie wykonaj sudo chown -R <uid>:<gid> na katalogu hosta lub, gdy daemon nie mapuje przestrzeni nazw użytkowników, ustaw user: "1000:1000" w usłudze. Jeśli daemon używa mapowania przestrzeni nazw użytkowników, zmień właściciela na zmapowany identyfikator hosta, ponieważ identyfikatory kontenera są przesunięte na hoście, a user: tego nie zmienia.

Gdzie Docker przechowuje nazwane wolumeny na dysku?

Przy użyciu domyślnego sterownika local znajdują się one w /var/lib/docker/volumes/<volume>/_data, a docker volume inspect <volume> wyświetla dokładną ścieżkę Mountpoint. Można ją odczytać w celu weryfikacji, ale zapisywać należy wyłącznie za pośrednictwem kontenera, ponieważ edycja jako root na hoście zmienia uprawnienia w sposób, którego kontener może nie oczekiwać.

Jak wykonać kopię zapasową nazwanego wolumenu?

Uruchom krótkotrwały kontener z zamontowanym wolumenem oraz katalogiem hosta, a następnie zarchiwizuj dane z jednego 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 należy użyć własnego narzędzia do zrzutu (dump) zamiast kopiowania plików w czasie pracy, ponieważ kopia plików wykonana podczas trwających zapisów może doprowadzić do stanu uszkodzenia danych przy przywracaniu.

Czy docker compose down usuwa moje wolumeny?

docker compose down usuwa kontenery i sieci, pozostawiając nazwane wolumeny na miejscu. docker compose down -v usuwa również każdy nazwany wolumen zadeklarowany w projekcie, trwale. Bind mounty nigdy nie są usuwane przez żadne z tych poleceń, ponieważ ten katalog należy do hosta, a nie do Dockera.

Czy ten artykuł był przydatny?