Wykorzystanie miejsca na dysku w pracy z Docker
Grudzień 20, 2023 | #docker
001101
110000
001100
Sprawdzanie zajętości dysku przez pliki Docker-a
W trakcie pracy z Docker-em pobieramy na dysk obrazy z repozytoriów, w oparciu o które budujemy nowe obrazy albo powołujemy do życia ich instancje, czyli kontenery. Zamknięte wewnątrz kontenerów aplikacje muszą gdzieś przechowywać dane zatem definiujemy dla nich wolumeny, a wszystko to zajmuje miejsce na dysku. Docker ma wbudowane narzędzie pozwalające monitorować zajęcie przestrzeni dyskowej w kilku kategoriach:
docker system df
Przykładowy wynik tego polecenia może być następujący
TYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 20 9 6.858GB 3.967GB (57%)
Containers 14 0 3.762MB 3.762MB (100%)
Local Volumes 22 12 4.059GB 344.3MB (8%)
Build Cache 567 0 19.81GB 19.81GB
Poszczególne kategorie to:
Obrazy (Images): Łączna ilość miejsca zajmowanego przez obrazy Docker-a na dysku, zarówno te używane, jak i nieużywane (tj. obrazy, które nie są bazą dla żadnego uruchomionego kontenera).
Kontenery (Containers): Całkowita ilość miejsca zajmowana przez kontenery, w tym aktywne (uruchomione) i nieaktywne (zatrzymane).
Woluminy (Volumes): Łączne wykorzystanie miejsca przez woluminy Docker, które są używane do trwałego przechowywania danych.
Cache Budowania (Build Cache): Miejsce zajmowane przez cache budowania Docker, które zawiera pośrednie etapy obrazów utworzone podczas budowania obrazów.
Użycie pamięci dyskowej przez obrazy Docker-a
Pojedynczy obraz Dockera potrafi sporo ważyć. Jeszcze kilka lat temu pracując z kontenerami trzeba było na ich potrzeby zabezpieczyć od kilku do nawet kilkudziesięciu gigabajtów przestrzeni dyskowej. Na przestrzeni lat obrazy były dostosowywane do specyficznych zastosowań, chudły okrajane ze zbędnych funkcjonalności i teraz jest z tym dużo lepiej.
Przede wszystkim musimy sobie zdać sprawę z tego, że ...
Kontener to nie maszyna wirtualna (VM) więc obrazy Dockera mogą być mniejsze
Kontener Dockera nie jest maszyną wirtualną.
UWAGA!
Z małym zastrzeżeniem. Dotyczy to czystego Docker Engine w systemie Linux. Docker Desktop używa maszyn wirtualnych (VM) pod spodem. W przypadku systemów Mac i Windows, Docker Desktop instaluje lekką maszynę wirtualną LinuxKit, którą Docker zarządza dla użytkownika. Na Windows, VM ta działa pod WSL2 (Windows Subsystem for Linux 2), umożliwiając wszystkim dystrybucjom WSL2 dostęp do Docker'a. Na Mac, obecnie przechodzą od wcześniejszej implementacji z HyperKit do nowego frameworku wirtualizacji Apple, aby uruchomić tę VM. Także na Linux Docker Desktop działa w maszynie wirtualnej dlatego używając Dockera w tej wersji nie znajdziesz plików, o których wspominam w tym artykule w ich standardowych lokalizacjach, ale wciąż możesz używać podanych tu komend.
Chociaż kontenery i maszyny wirtualne służą do izolacji środowisk aplikacji, działają one w zasadniczo różny sposób. Dlatego też obrazy Dockera np. Ubuntu, zawierają jedynie podstawowe elementy systemu operacyjnego a nie są one pełnymi wersjami, takimi jak te instalowane na fizycznych lub wirtualnych maszynach. W konsekwencji, ważą mniej, ale wciąż sporo.
Główne typy obrazów Docker, biorąc pod uwagę ich wielkość
Obrazy możemy podzielić według różnych kryteriów ale najprostszym jest to czy obraz został ściągnięty z rejestrów i dostajemy go z dobrodziejstwem inwentarza, czy też został przez nas zbudowany bazując na innym obrazie lub od podstaw.
Pobierając oficjalne obrazy z rejestru możemy zakładać, że zostały one przygotowane z pełną pieczołowitością (co nie oznacza, że nie ma w nich żadnych podatności) i starannością. Nie mamy wpływu na ich rozmiar ale możemy wybierać jaką chcemy wersję.
Obrazy Bazowe (Base Images)
Obrazy Pełne (Full Images):
Zawierają pełny system operacyjny, często z dodatkowymi narzędziami i bibliotekami np.
ubuntu
,debian
,centos
.Obrazy Minimalistyczne (Minimal Images):
Zawierają minimalny zestaw komponentów niezbędnych do uruchomienia systemu operacyjnego np.
alpine
,busybox
. Są znacznie mniejsze od pełnych obrazów, co czyni je popularnym wyborem w środowiskach produkcyjnych, zwłaszcza dla mikroserwisów i aplikacji rozproszonych.
Obrazy Języków Programowania
Obrazy SDK/Development:
Zawierają kompilatory, narzędzia budowania, debuggery i inne narzędzia deweloperskie dla określonego języka programowania np.
node:x-sdk
,openjdk:x-jdk
. (pod x podstaw nr wersji)Obrazy Runtime:
Zawierają tylko środowisko uruchomieniowe i niezbędne biblioteki dla określonego języka lub frameworka np.
node:x-alpine
,openjdk:x-jre
. Są mniejsze, ponieważ nie zawierają narzędzi deweloperskich, tylko to, co jest potrzebne do uruchomienia aplikacji.
Obrazy Aplikacji
Obrazy "Fat":
Zawierają aplikację wraz z pełnym systemem operacyjnym i wszystkimi jej zależnościami. Są większe i mogą być mniej bezpieczne ze względu na większą liczbę składowych.
Obrazy "Slim" lub "Lightweight":
Zawierają aplikację i jej bezpośrednie zależności, ale z mniejszą liczbą składowych systemu operacyjnego. Są chudsze i często bardziej bezpieczne, ponieważ zawierają mniej komponentów, co zmniejsza powierzchnię ataku.
Obrazy Distroless
Specjalne obrazy stworzone przez Google, które zawierają aplikacje i ich bezpośrednie zależności, ale bez pełnego systemu operacyjnego. Są bardzo małe i zabezpieczone, ponieważ nie zawierają zbędnych narzędzi czy składowych systemu w tym także shell-a co minimalizuje potencjalne wektory ataków.
Wybór odpowiedniego obrazu zależy od wielu czynników, takich jak wymagania aplikacji, potrzeby bezpieczeństwa, zasoby systemowe oraz procesy CI/CD. Mniejsze obrazy są szybsze w pobieraniu i uruchamianiu, co jest korzystne w środowiskach produkcyjnych i ciągłej integracji/dostawy, ale czasami konieczne jest użycie większych obrazów ze względu na konkretne wymagania aplikacji lub środowiska uruchomieniowego dlatego też ilość miejsca na dysku nie może być jedynym kryterium.
Wyświetlając listę obrazów ...
docker images ls
Mamy możliwość podejrzenia tagu obrazu, który zdradza nam też jego rodzaj, jak również rozmiaru.
REPOSITORY TAG IMAGE ID CREATED SIZE
xxx/blog/django latest 8cd00a60f287 6 days ago 299MB
<none> <none> bc95922ac24b 6 days ago 47.8MB
xxx/blog/postgres latest 292262f248ae 6 days ago 211MB
redis 7-alpine d2d4688fcebe 11 days ago 41MB
traefik v2.10 64586c703ab1 12 days ago 153MB
nginx 1.25.3-alpine-slim 5473339cf3c6 2 weeks ago 11.9MB
python 3.10.4-alpine3.15 abd9fc366a36 18 months ago 47.8MB
traefik v2.6 22c6901de2be 19 months ago 102MB
postgres 14.2-alpine ebf01b748a56 20 months ago 211MB
wzgapi_api latest 49d82317a617 3 years ago 444MB
certbot/certbot latest bbf174584317 4 years ago 102MB
postgres 9.6-alpine 5152558331dc 4 years ago 38.6MB
nginx 1.15.8-alpine 315798907716 4 years ago 17.8MB
python 3.6-alpine 1837080c5e87 4 years ago 74.4MB
Obrazy <none>
Docker-a
Przyglądając się uważnie powyższej liście obrazów możemy dostrzec pozycję, która od razu budzi nasz niepokój
<none> <none> bc95922ac24b 6 days ago 47.8MB
Co więcej wywołując komendę
docker image ls -a
Przekonujemy się że takich "anonimowych" obrazów jest dużo więcej.
Czym są obrazy Docker <none>
i czy powinniśmy je usuwać?
Te obrazy to albo:
Obrazy pośrednie (intermediate), do których są odniesienia z innych obrazów przez co nie można ich usunąć bez usuwania obrazów, które do nich referują. Są tworzone podczas procesu budowania obrazu. To właśnie one reprezentują poszczególne warstwy obrazu, które powstają w wyniku wykonania instrukcji zdefiniowanych w Dockerfile. Obrazy pośrednie są ukryte (od wersji Docker 1.13) przed oczami użytkownika i dopiero flaga
--all
lub-a
pozwala je zobaczyć.Obrazy wiszące (dangling), czyli takie, do których nie ma już referencji. Można je usunąć i są widoczne domyślnie.
docker image rm $(docker image ls -f dangling=true -q)
# lub
docker image prune
WARNING! This will remove all dangling images.
Are you sure you want to continue? [y/N]
Skoro omówiliśmy czym są obrazy pośrednie to możemy w płynny sposób przejść do budowania obrazów?
Budowanie małych obrazów Docker
Przy okazji omawiania obrazów pośrednich zostało wspomniane, że to właśnie one reprezentują warstwy obrazu. Otóż Docker przechowuje obrazy jako serię warstw. Każda instrukcja w Dockerfile, takie jak RUN
, COPY
, ADD
, tworzy nową warstwę. Chociaż Docker został zaprojektowany w taki sposób aby ograniczyć zajmowanie przestrzeni dyskowej, a warstwy obrazów są nakładane na siebie w sposób, który pozwala na współdzielenie plików między nimi bez ich dublowania, każda nowa warstwa zwiększa rozmiar końcowego obrazu ponieważ zawiera różnice (zmiany) w stosunku do warstw poprzednich. Dlatego ograniczenie liczby instrukcji w Dockerfile, zwłaszcza poprzez grupowanie podobnych instrukcji, może pozytywnie wpływać na wielkość końcowych obrazów Docker.
I tak zamiast używać wielu instrukcji RUN
do instalacji pakietów, lepiej jest połączyć te instrukcje w jedną, używając operatora &&
. To pozwala zredukować liczbę warstw i zapewnia, że wszystkie tymczasowe pliki użyte w procesie instalacji mogą być usunięte w ramach tej samej warstwy.
Grupowanie instrukcji COPY
lub ADD
w Dockerfile jest ograniczone, ponieważ te instrukcje są zaprojektowane do kopiowania plików i folderów z kontekstu budowy do obrazu Docker. Można jednak:
- Skopiować wiele plików lub folderów, podając wiele źródeł i jeden cel.
COPY file1.txt file2.txt /destination/
- Korzystanie z Wildcards (Symboli Wieloznacznych)
COPY *.txt /destination/
- Zorganizować pliki w folderach źródłowych w taki sposób, aby kopiować całe foldery `COPY ./source/ /destination/
Warto jest też wspomnieć w tym miejscu o pliku .dockerignore
do wykluczenia niepotrzebnych plików i folderów z kontekstu budowy, co zmniejsza czas budowania i rozmiar obrazu.
Odpowiednie grupowanie i ograniczenie liczby instrukcji w Dockerfile może znacząco zmniejszyć rozmiar końcowego obrazu Docker, poprawić wydajność procesu budowania oraz ułatwić zarządzanie obrazami. To ważna praktyka w optymalizacji obrazów Docker, szczególnie w środowiskach produkcyjnych, gdzie zarówno rozmiar obrazów, jak i czas potrzebny na ich budowanie i wdrażanie, są krytycznymi czynnikami.
O warstwach będzie jeszcze mowa przy okazji omawiania Build Cache tymczasem przejdźmy do kontenerów.
Konsumpcja miejsca na dysku przez kontenery Docker-a
Powołanie nowej instancji obrazu, jaką jest kontener też nie jest obojętne z punktu widzenia zarządzania miejscem na dysku.
Kontener Docker-a tworzy własną warstwę zapisu
Każdy kontener gdy jest uruchomiony, tworzy warstwę zapisu na wierzchu warstw tylko do odczytu obrazu bazowego. Wszystkie zmiany dokonywane w kontenerze, takie jak instalacja nowych pakietów, zapisywanie nowych plików czy modyfikacja konfiguracji, są przechowywane w tej warstwie.
W zależności od używanego sterownika lokalizacja fizyczna warstw na hoście może być różna ale dla najpopularniejszego sterownika Overlay2 używanego w wielu dystrybucjach linuxa, zarówno warstwy obrazów jak i warstwy zapisu kontenerów trzymane są w folderze /var/lib/docker/overlay2
. Jeśli kontener będzie przechowywał dane we własnym systemie plików, to będą one magazynowane właśnie w tym katalogu.
Warstwa zapisu nie znika po zatrzymaniu kontenera, co oznacza, że wszelkie dane w niej zawarte nadal zajmują miejsce na dysku.
Logi kontenera Docker potrafią szybko rosnąć
Zatrzymane kontenery mogą również przechowywać logi i inne dane tymczasowe, które były generowane podczas ich działania. Logi kontenera - przy założeniu, że kontener używa domyślnego sterownika logowania json-file
lub local
- trzymane są w folderze /var/lib/docker/container/[CONTAINER_ID]
gdzie [CONTAINER_ID]
to unikalny identyfikator kontenera.
Dobór sterownika logowania i jego konfiguracja ma ogromny wpływ na wyczerpywanie się miejsca na dysku. Nie będę w tym miejscu omawiał wszystkich sterowników logowania Docker, gdyż wiele z nich umożliwia przesyłanie logów do systemów zewnętrznych albo jest dostępna jedynie w wersji Docker Enterprise Edition. Wspomnę jedynie o tym, że domyślny sterownik json-file
warto zastąpić nowszym local
gdyż oferuje lepszą kontrolę nad zarządzaniem logami, w tym:
Rotację logów - możliwość ustawienia limitów dotyczących liczby i rozmiaru plików logów, dzięki czemu unika się zapełnienia dysku przez nieograniczone logi.
Wydajność - użycie bardziej wydajnego formatu zapisu, co jest korzystne, zwłaszcza w środowiskach z dużą ilością logów.
Można skonfigurować sterownik local
dla poszczególnych kontenerów podczas ich uruchamiania lub globalnie dla całego środowiska Docker. Oto przykład konfiguracji sterownika local
dla kontenera:
docker run --log-driver=local --log-opt max-size=10m --log-opt max-file=3 my-image
W tym przykładzie, logi będą rotowane, gdy osiągną rozmiar 10MB, a zachowane zostaną tylko trzy najnowsze pliki logów.
Pliki stanu kontenera
Docker przechowuje również pewne metadane i pliki stanu dla każdego kontenera, które zajmują dodatkowe, choć zazwyczaj niewielkie, miejsce na dysku.
Usuwanie niepotrzebnych kontenerów Docker-a
Niepotrzebne kontenery usuwamy komendą:
docker rm [CONTAINER_ID]
albo hurtowo
docker container prune
WARNING! This will remove all stopped containers.
Are you sure you want to continue? [y/N]
UWAGA!
Jest bardzo ważne aby nie spieszyć się z usuwaniem nieaktywnych kontenerów, zwłaszcza jeśli ich zatrzymanie nastąpiło w nieplanowany sposób, gdyż dopóki kontener istnieje, jest możliwość sprawdzenia w logach przebiegu jego pracy.
Jeśli chcemy usunąć wszystkie kontenery możemy skorzystać z polecenia
docker container rm -f $(docker container ls -aq)
Dobrze jest też wyrobić sobie nawyk natychmiastowego usuwania kontenerów "jednorazowego" użycia tworzonych tylko po to aby wykonać pojedynczą operację, czy zdebugować coś wewnątrz kontenera. Sprawę ułatwia flaga --rm
ponieważ w przypadku jej użycia zatrzymanie kontenera oznacza natychmiastowe, automatyczne usunięcie kontenera.
docker run --rm -it python:3 python
Woluminy Docker i zapotrzebowanie na przestrzeń dyskową
Woluminy Docker-a są przechowywane poza strukturą plików samego kontenera po to aby zachować ich trwałość nawet po usunięciu kontenera.
Fizycznie zapisywane są na hoście w specjalnie wyznaczonym katalogu (pomijając woluminy typu bind mounts - czyli podmontowane katalogi lub pliki hosta do katalogów lub plików wewnątrz kontenera). Domyślna lokalizacja tego katalogu zależy od systemu operacyjnego i konfiguracji Docker, ale w większości przypadków znajduje się ona w:
/var/lib/docker/volumes/
Możesz uzyskać dostęp do tych danych bezpośrednio z poziomu systemu hosta, co jest szczególnie przydatne dla celów backupu, monitoringu lub gdy potrzebujesz bezpośrednio zarządzać danymi, ale stanowi też potencjalne zagrożenie. Bezpośrednia modyfikacja danych w katalogu wolumenów Docker może prowadzić do nieoczekiwanych problemów więc lepiej wystrzegać się takich praktyk.
Trwałość podstawowy atrybut woluminów Docker-a
Trwałość danych jest szczególnie kluczowa bo o ile kontenery mają charakter efemeryczny czyli łatwo jest je uruchomić, zatrzymać i usunąć, skalować itd. o tyle utrata danych często wiąże się z brakiem możliwości ich odtworzenia chyba że wcześniej zadbaliśmy o kopię zapasową. Woluminy mogą być współdzielone przez wiele kontenerów i nawet jeśli nie są aktualnie używane można uruchomić kontener, który zacznie z nich korzystać.
Anonimowe czy nazwane woluminy Dockera?
W Dockerze istnieją dwa główne typy woluminów: nazwane (named volumes) i nienazwane (anonymous volumes). Obie te formy służą do przechowywania danych poza systemem plików kontenera i nie ma między nimi różnic jeśli chodzi o obciążenie dysku, ale wspominam o tym bo różnią się sposobem zarządzania i możliwościami wykorzystania.
Woluminy anonimowe są automatycznie tworzone bez konieczności wcześniejszego definiowania i zapewniają wyższy poziom izolacji danych kontenera od hosta. Z drugiej jednak strony trudniej jest je śledzić, powiązać z jakimś konkretnym kontenerem i nimi zarządzać. Dlatego w przypadku ich użycia jest dużo większe ryzyko pozostawienia na dysku "osieroconych" woluminów.
Dlatego w przypadku usuwania kontenerów, o których wiemy że używają woluminów anonimowych dobrze jest to zrobić w jednym kroku korzystając z flagi -v
.
docker rm -v [nazwa_lub_id_kontenera]
UWAGA !
Zwróć proszę uwagę, że opcja -v
zapewnia, że wszystkie anonimowe woluminy powiązane z kontenerem zostaną usunięte wraz z nim, ale woluminy nazwane, które zostały utworzone i zamontowane w kontenerze, nie zostaną usunięte.
W Docker Compose działa to inaczej
docker-compose down -v
Usunięte zostaną zarówno woluminy anonimowe jak i nazwane.
Poza specyficznymi sytuacjami powinno się właściwie zawsze korzystać z woluminów nazwanych. Są łatwe w identyfikacji i zarządzaniu, trwałe, przenośne i można je podmontować do wielu kontenerów.
Te powody zadecydowały o tym, że komenda ...
docker system prune
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- all dangling build cache
Are you sure you want to continue? [y/N]
... nie usuwa woluminów nawet jak w bieżącym momencie nie ma podpiętego do nich kontenera.
Użycie opcji --volumes
umożliwia usunięcie nieużywanych woluminów ale tylko anonimowych podobnie jak polecenie.
docker volume prune
WARNING! This will remove anonymous local volumes not used by at least one container.
Are you sure you want to continue? [y/N]
W przypadku konieczności usunięcia wszystkich woluminów bez względu na to czy są używane i czy są nazwane zawsze można użyć polecenia
docker volume rm -f $(docker volume ls -q)
Docker build cache
Docker build cache to mechanizm używany przez Docker podczas budowania obrazów, który ma na celu przyspieszenie tego procesu. Cache ten przechowuje wyniki poszczególnych instrukcji z Dockerfile, aby w przypadku ponownego budowania obrazu uniknąć powtarzania tych samych kroków, jeśli nie nastąpiły żadne zmiany.
Podobnie jak warstwy obrazów tak samo cache zależy od użycia instrukcji RUN
, COPY
i ADD
. Szczególnie dwie ostatnie są istotne ponieważ mechanizm wykrywania zmian sprawdza sumy kontrolne plików, które są dodawane do obrazu. Jeśli pliki te się zmienią, cache dla tych instrukcji i wszystkich kolejnych nie będzie używany. Odpowiednia kolejność instrukcji w Dockerfile może pomóc w lepszym wykorzystaniu cache. Na przykład, zmiany w instrukcjach dotyczących kodu aplikacji powinny występować po instrukcjach instalacji zależności, aby zmiany w kodzie nie unieważniały cache zależności.
Mogłoby się wydawać, że mechanizm buforowania jest tożsamy z mechanizmem warstw ale są między nimi istotne różnice.
- Build cache jest specyficzny dla procesu budowania obrazów i służy do przyspieszenia tego procesu. Dane w nim są tymczasowe i mogą być usunięte bez wpływu na działające kontenery.
- Warstwy obrazów i kontenerów Docker są używane do przechowywania i zarządzania danymi obrazów i kontenerów w czasie ich działania i są trwałe, dopóki obrazy lub kontenery są przechowywane.
Można wymusić budowanie obrazu bez użycia cache za pomocą opcji --no-cache
.
docker image build --no-cache .
Jest to przydatne, gdy chcesz mieć pewność, że wszystkie kroki są wykonywane od nowa.
Można też wyczyścić cache poleceniem
docker builder prune
Polecam zapoznać się z opcjami tego polecenia w szczególności z flagami --filter
i --keep-storage
bo dają nam większą elastyczność i kontrolę na przechowywanym buforem.
Reasumując
Polecenie docker system df
jest przydatne do monitorowania i zarządzania wykorzystaniem przestrzeni dyskowej przez Docker, umożliwiając identyfikację i usuwanie nieużywanych obrazów, kontenerów czy woluminów, co pozwala na optymalizację wykorzystania zasobów.
Poprawnie przygotowane obrazy Docker-a zawierają tylko niezbędne do działania pliki, a nowoczesne sterowniki magazynowania, jak Overlay2, efektywniej zarządzają warstwami obrazów Docker-a dzięki czemu zajmują one mniej miejsca na dysku.
Dobór obrazu bazowego jest kluczowy z punktu widzenia szybkości budowania ale też rozmiaru powstałego na jego podstawie obrazu. Odpowiednie grupowanie i ograniczenie liczby instrukcji w Dockerfile może znacząco zmniejszyć rozmiar końcowego obrazu Docker-a, poprawić wydajność procesu budowania oraz ułatwić zarządzanie obrazami. To ważna praktyka w optymalizacji obrazów Docker, szczególnie w środowiskach produkcyjnych, gdzie zarówno rozmiar obrazów, jak i czas potrzebny na ich budowanie i wdrażanie, są krytycznymi czynnikami.
Kontenery - także te zatrzymane - również zajmują miejsce na dysku z uwagi na warstwę zapisu i inne skojarzone z nimi dane w tym m.in. logi aplikacji. Dobór odpowiedniego sterownika może ułatwić narzucenie ograniczeń w tej kwestii. Jest to o tyle istotne, że przyrost logów następuje w trakcie działania kontenerów i jeśli nie jest monitorowane może nas zaskoczyć nagłym brakiem miejsca na serwerze.
Najważniejsza jest trwałość danych ale też łatwość zarządzania nimi dlatego zazwyczaj najlepszym pomysłem jest używanie woluminów nazwanych, które zmniejszają ryzyko pozostawienia na dysku osieroconych zbiorów danych.
Docker build cache jest ważnym narzędziem w ekosystemie Docker, pozwalającym na szybsze iteracje podczas rozwoju i budowania obrazów, a także efektywne zarządzanie zasobami systemowymi. Usunięcie cache jest bezpieczną operacją ale może wpłynąć negatywnie na szybkość budowania obrazów
Warto monitorować obłożenie dysku i raz na jakiś czas czyścić go ze zbędnych plików. Dobrym pomysłem wydaje się cykliczne uruchamianie np. w cronie polecenia
docker system prune -f
Usuwanie cykliczne volumenów - nawet anonimowych - wydaje się ryzykownym pomysłem dlatego nie dodałem flagi --volumes