Docker na VPS: różnice i najczęstsze problemy
Uruchomienie Dockera na VPS wymaga uwagi. Dowiedz się, dlaczego porty omijają UFW, jak zapobiec zapełnieniu dysku przez kontenery oraz jak uniknąć błędu OOM Killer przy braku RAM.
Co zmienia się podczas uruchamiania Docker na VPS
Docker na VPS korzysta z tego samego silnika i tych samych obrazów co Docker na laptopie, więc wszystkie znane polecenia działają w ten sam sposób. Zmienia się natomiast otoczenie. Laptop dysponuje zapasem pamięci, firewallem, którego nikt nie skanuje, oraz dyskiem o pojemności, którą rzadko trzeba monitorować. Wynajęty serwer ma sztywny limit pamięci, publiczny adres IP skanowany w ciągu kilku minut od uruchomienia oraz system plików root, który Docker zapełni bez pytania.
Cztery różnice powodują większość problemów na małych maszynach:
- Pamięć jest ograniczona, a jądro systemu rozwiązuje jej niedobór poprzez zabijanie procesów.
- Opublikowany port omija UFW (uncomplicated firewall), ponieważ Docker samodzielnie dopisuje własne reguły firewalla.
- Kontenery nie uruchamiają się ponownie po restarcie systemu, chyba że zostało to wcześniej skonfigurowane.
- Obrazy, kontenery, wolumeny i pamięć podręczna budowania (build cache) rosną, aż do zapełnienia dysku.
Każda z poniższych sekcji wskazuje przyczynę awarii, komunikat, który zobaczysz, oraz przewodnik, który szczegółowo rozwiązuje dany problem. Jeśli nie przygotowano jeszcze pliku compose, należy najpierw przeczytać Podstawy Docker Compose na VPS, a następnie wrócić do tej strony. Niniejszy dokument zakłada, że użytkownik potrafi już uruchomić stos usług.
Ile pamięci RAM zużywa kontener Docker?
Znacznie mniej, niż zakłada większość użytkowników. Kontener jest procesem wewnątrz cgroup (control group), a nie maszyną wirtualną, dlatego nie posiada własnego jądra systemu (guest kernel) ani sztywno przydzielonych zasobów. Koszt pamięci odpowiada zasobom faktycznie wykorzystywanym przez proces. Dzięki temu pełny stos technologiczny mieści się w 2 GB, podczas gdy ten sam stos uruchomiony na maszynach wirtualnych wymagałby znacznie więcej.
Poniższe wartości to typowe zużycie w stanie spoczynku dla standardowych obrazów na systemie Ubuntu 24.04 z domyślną konfiguracją, odczytane z docker stats kilka minut po uruchomieniu. Stanowią one punkt wyjścia do planowania, a nie benchmark obciążenia. Przed poleganiem na jakichkolwiek danych, w tym poniższych, należy uruchomić docker stats --no-stream na własnym serwerze.
The data behind this chart
[
{
"label": "nginx",
"idle_mb": 8,
"budget_mb": 64
},
{
"label": "Redis 7",
"idle_mb": 12,
"budget_mb": 128
},
{
"label": "Traefik v3",
"idle_mb": 40,
"budget_mb": 128
},
{
"label": "PostgreSQL 16",
"idle_mb": 45,
"budget_mb": 512
},
{
"label": "Uptime Kuma",
"idle_mb": 95,
"budget_mb": 256
},
{
"label": "MariaDB 11",
"idle_mb": 190,
"budget_mb": 512
},
{
"label": "Nextcloud (Apache)",
"idle_mb": 210,
"budget_mb": 768
}
]Obie kolumny pełnią różne funkcje. idle_mb to zużycie pamięci przez kontener w stanie bezczynności. budget_mb to wartość, którą należy zarezerwować podczas planowania, ponieważ rzeczywiste użycie nie ogranicza się do stanu spoczynku. PostgreSQL w stanie bezczynności zużywa około 45 MB, ale wymaga 512 MB, gdy aktywne są połączenia, operacje sortowania i pamięć podręczna. Planowanie należy opierać na kolumnie budżetowej, a diagnostykę na kolumnie stanu spoczynku.
Należy zwrócić uwagę na wartości w tych 7 wierszach. nginx w stanie spoczynku zużywa 8 MB, a Nextcloud 210 MB. Proxy działające przed aplikacjami jest niemal bezkosztowe. To baza danych oraz aplikacja PHP determinują wymagania sprzętowe serwera.
Ostrzeżenie dotyczące docker stats: wskaźnik zużycia pamięci obejmuje pamięć podręczną stron (page cache), która jest zapełniana podczas odczytu plików przez kontener, dlatego wartość ta rośnie przez pewien czas po starcie, a następnie stabilizuje się. Przed podjęciem decyzji o wystąpieniu wycieku pamięci należy monitorować proces przez godzinę.
Dobieranie rozmiaru VPS: co mieści się w 2 GB, 4 GB i 8 GB
Najpierw odejmij zasoby zajmowane przez system hosta. Kernel, systemd, journald, sshd oraz Docker daemon korzystają z tej samej pamięci RAM co kontenery, a dockerd wraz z containerd zajmuje około 100 MB. Potrzebujesz również wolnej pamięci na page cache oraz na skoki zapotrzebowania podczas budowania obrazów lub wykonywania zrzutów baz danych.
The data behind this chart
[
{
"plan": "2 GB VPS",
"total_mb": 2048,
"host_reserve_mb": 768,
"container_mb": 1280
},
{
"plan": "4 GB VPS",
"total_mb": 4096,
"host_reserve_mb": 1024,
"container_mb": 3072
},
{
"plan": "8 GB VPS",
"total_mb": 8192,
"host_reserve_mb": 1536,
"container_mb": 6656
}
]host_reserve_mb obejmuje system operacyjny, Docker daemon oraz zapas pamięci, który zapewnia responsywność serwera pod obciążeniem. To, co pozostaje, to container_mb i jest to jedyna wartość, którą możesz realnie wykorzystać. Rezerwa rośnie wraz z planem, od 768 MB w najmniejszej jednostce do 1536 MB w największej, ponieważ większy serwer uruchamia więcej kontenerów, generuje więcej logów i wymaga większego page cache.
Plan 2 GB pozostawia 1280 MB na kontenery. Przeznacz 512 MB z tej puli na PostgreSQL i 128 MB na Traefik, a połowa dostępnej pamięci zostanie już wykorzystana. Reszta wystarczy na dwie małe aplikacje po około 256 MB każda. To realny i użyteczny serwer. Nie ma tu jednak miejsca na Nextcloud oraz klaster wyszukiwarki.
Plan 4 GB pozostawia 3072 MB, co pozwala na jednoczesne uruchomienie bazy danych, reverse proxy, trzech aplikacji oraz kontenera monitorującego. Jest to najmniejszy rozmiar, który warto rozważać dla istotnych usług, ponieważ wolna pamięć pozwala zamortyzować skutki błędnego wdrożenia.
Plan 8 GB pozostawia 6656 MB z całkowitej puli 8192 MB. W tym przypadku ograniczeniem zazwyczaj staje się procesor lub przepustowość dysku, a nie pamięć RAM. Istnieje klasa kontenerów, które rezerwują pamięć na podstawie konfiguracji, a nie aktualnego obciążenia: lokalny serwer modeli rezerwuje KV cache proporcjonalnie do okna kontekstowego, więc zwiększenie num_ctx w Ollama może dodać gigabajty do zużycia pamięci, zanim nadejdzie pierwsze żądanie. Jeśli obliczenia wskazują, że stos usług się nie mieści, wybierz wyższy plan zamiast optymalizować konfigurację na siłę: rzeczywiste koszty VPS wyjaśnia, ile warte są dodatkowe gigabajty w skali miesiąca.
Dwie zasady pozwalają zachować rzetelność obliczeń. Nałóż limit pamięci na każdą usługę, aby jeden proces, który wymknął się spod kontroli, nie spowodował awarii całego serwera. Pozostaw również część budżetu niewykorzystaną, ponieważ docker compose build oraz pg_dump wymagają pamięci w najmniej odpowiednich momentach. Limity pamięci w Docker Compose zawiera składnię oraz opis typowych pułapek.
Dlaczego kontener kończy działanie z kodem 137?
Przyczyną jest przerwanie procesu przez jądro systemu. Kod 137 to suma 128 oraz 9, gdzie sygnał 9 oznacza SIGKILL. Kontener zażądał więcej pamięci, niż mu przydzielono, co spowodowało interwencję mechanizmu out of memory (OOM) killer.
CONTAINER ID IMAGE STATUS
9f2c1a4d7b3e postgres:16 Exited (137) 4 minutes agoPotwierdź przyczynę zamiast zgadywać:
docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory""OOMKilled": true oznacza, że kontener osiągnął limit własnej grupy cgroup, a dziennik jądra wskazuje proces, który został wybrany do zakończenia:
Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kBJest to sytuacja korzystna, ponieważ skutki awarii ograniczają się do jednego kontenera. Sytuacja niebezpieczna występuje, gdy kontener nie posiada żadnego limitu. Bez ograniczeń jego pułapem jest zasób całego serwera, więc wyciek pamięci w jednej usłudze doprowadzi do wyczerpania zasobów hosta, a jądro wybierze ofiarę na podstawie rozmiaru w całym systemie. Wpis w dzienniku traci przedrostek Memory cgroup i przyjmuje postać Out of memory: Killed process 2417 (postgres). Wybranym procesem jest często baza danych, podczas gdy kontener, w którym wystąpił wyciek, nadal działa. Dlatego limit dla każdej usługi jest ważniejszy niż precyzyjne określenie wartości pojedynczego ograniczenia.
Swap zmienia czas reakcji, a nie matematykę zużycia pamięci. Większość obrazów VPS jest dostarczana bez partycji swap. Sprawdź to za pomocą swapon --show, które nie wyświetli żadnych danych, jeśli swap nie istnieje. Plik wymiany daje jądru miejsce na przeniesienie nieaktywnych stron pamięci, co zapewnia czas na wykrycie problemu.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -hfree -h powinno teraz pokazywać niezerową wartość w wierszu Swap. Swap nie zwiększa ilości pamięci RAM. Serwer pod stałym obciążeniem pamięci staje się na tyle wolny, że połączenie SSH w celu naprawy może być niemożliwe, dlatego traktuj swap jako bufor alarmowy i zadbaj o odpowiednie limity.
Dlaczego UFW nie blokuje opublikowanego portu Docker?
Ruch sieciowy nigdy nie dociera do łańcucha chronionego przez UFW. Gdy port jest publikowany za pomocą -p 5432:5432 lub wpisu w pliku compose ports:, demon zapisuje regułę DNAT (destination network address translation) w tablicy nat oraz regułę akceptacji we własnym łańcuchu DOCKER. Pakiet skierowany do kontenera jest przekazywany bezpośrednio do niego, z pominięciem dostarczenia do hosta. W rezultacie ruch jest obsługiwany w ścieżce FORWARD i nigdy nie przechodzi przez reguły INPUT zdefiniowane w UFW.
Można to zaobserwować na serwerze:
sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432UFW może wyświetlać 5432 DENY IN Anywhere, podczas gdy tablica nat zawiera regułę DNAT tcp ... to:172.18.0.2:5432 dla tego samego portu. Z innej maszyny nc -vz your.server.ip 5432 nadal nawiązuje połączenie. Baza danych jest dostępna z publicznego Internetu, mimo że zapora sieciowa wskazuje na brak takiej możliwości.
Rozwiązaniem jest ograniczenie publikacji portów. Kontenery w ramach jednego projektu compose współdzielą sieć i komunikują się za pomocą nazw usług. Baza danych obsługująca wyłącznie aplikację obok nie wymaga wpisu ports:. W przypadku konieczności dostępu lokalnego, należy powiązać publikację z interfejsem loopback:
services:
db:
image: postgres:16
ports:
- "127.0.0.1:5432:5432"Po wykonaniu docker compose up -d, nc -vz your.server.ip 5432 z zewnątrz kończy się niepowodzeniem, a psql -h 127.0.0.1 -p 5432 na serwerze nadal działa poprawnie. W poprawnie skonfigurowanym stosie usług tylko reverse proxy publikuje porty, zazwyczaj 80 oraz 443. Artykuł Dlaczego opublikowane porty Docker omijają UFW opisuje łańcuch DOCKER-USER dla przypadków, w których publikacja portu jest niezbędna przy jednoczesnym zachowaniu filtrowania, natomiast Podstawy zapory sieciowej UFW omawia reguły hosta działające na niższym poziomie.
Dlaczego moje kontenery znikają po restarcie?
Ponieważ nie otrzymały polecenia powrotu. Kontener jest tworzony z polityką restartu no, jeśli nie ustawiono inaczej, więc po restarcie pozostaje zatrzymany, a daemon nie podejmuje żadnych działań. Restart na VPS nie jest rzadkością: aktualizacje jądra z unattended-upgrades, prace konserwacyjne dostawcy oraz wspomniana wyżej sekwencja OOM kończą się restartem.
Muszą zostać spełnione dwa warunki. Daemon musi uruchamiać się podczas startu systemu:
systemctl is-enabled dockerPolecenie to zwraca enabled w standardowej instalacji Ubuntu. Następnie każda usługa wymaga odpowiedniej polityki:
services:
app:
image: ghcr.io/example/app:1.4
restart: unless-stoppedOpcja unless-stopped przywraca kontener po restarcie i uwzględnia kontenery zatrzymane celowo. Opcja always restartuje również te kontenery, które zostały zatrzymane ręcznie, gdy tylko daemon zostanie zrestartowany, co bywa zaskoczeniem podczas debugowania. Sama edycja pliku nie wystarcza, ponieważ polityka restartu jest ustalana w momencie tworzenia kontenera. Należy wykonać docker compose up -d, aby kontener został utworzony ponownie, a następnie sprawdzić bieżącą wartość:
docker inspect my-app | grep -A3 RestartPolicyNastępnie należy celowo zrestartować serwer i wykonać docker compose ps w katalogu projektu. Stos, który przetrwa planowany restart, przetrwa również ten nieplanowany. Jeśli stos wymaga gwarancji kolejności uruchamiania lub wykonania jednorazowego zadania podczas startu, lepszym narzędziem jest jednostka systemd: uruchamianie Docker Compose podczas startu zawiera odpowiedni plik jednostki. Aby sprawdzić, czy kontener, który powrócił, rzeczywiście działa poprawnie, należy dodać kontrole stanu (healthchecks) w Compose.
Dlaczego dysk na moim VPS jest pełny?
Docker przechowuje wszystkie dane, dopóki nie otrzyma polecenia ich usunięcia. Każdy pobrany tag obrazu, każdy zatrzymany kontener, każdy anonimowy wolumen pozostawiony po odtworzeniu usługi oraz każda warstwa pamięci podręcznej budowania (build cache) pozostają na dysku. W przypadku systemu plików root o rozmiarze 40 GB lub 80 GB, co jest standardem w tych planach, prowadzi to do awarii w ciągu miesięcy, a nie lat.
Pełny dysk nie objawia się jako nagłe przerwanie pracy usługi. W ciągu tej samej godziny można otrzymać błędy no space left on device z kontenera, z apt, z journald oraz z docker pull. PostgreSQL przestaje przyjmować zapisy. Serwer nadal działa, co utrudnia wykrycie problemu w porównaniu do pętli restartów.
Przed usunięciem danych należy sprawdzić ich zajętość:
docker system df
df -h /docker system df dzieli całkowite zużycie na obrazy, kontenery, wolumeny lokalne oraz pamięć podręczną budowania, wyświetlając kolumnę RECLAIMABLE przy każdej pozycji. Na serwerze, na którym budowane są własne obrazy, pamięć podręczna budowania zazwyczaj zajmuje najwięcej miejsca.
docker image prune -a
docker builder prune
docker system dfdocker image prune -a usuwa wszystkie obrazy, które nie są używane przez żaden kontener. docker builder prune czyści pamięć podręczną budowania. Obie operacje są bezpieczne podczas działania usług, ponieważ elementy aktualnie używane są pomijane. Niebezpieczne jest natomiast użycie docker system prune --volumes, które usuwa każdy wolumen, do którego w danej chwili nie odwołuje się żaden kontener. Stos usług zatrzymany na weekend ma właśnie taką postać, więc jego wolumen z bazą danych zostanie usunięty. Przed użyciem tej flagi należy przeczytać bind mounts versus named volumes i wykonać kopię zapasową.
Logi kontenerów to mniej oczywiste źródło przyrostu danych. Domyślny sterownik json-file nie posiada limitu rozmiaru, więc jeden generujący dużo komunikatów kontener może zapisać gigabajty danych w /var/lib/docker/containers. Należy nałożyć limit dla każdego kontenera w /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}Zastosuj zmiany za pomocą sudo systemctl restart docker, co spowoduje restart kontenerów, więc wybierz odpowiedni moment. Limit dotyczy kontenerów utworzonych po wprowadzeniu zmiany, dlatego należy odtworzyć działające kontenery za pomocą docker compose up -d --force-recreate i zweryfikować stan:
docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containersWynik polecenia inspect powinien pokazywać ustawione max-size. Jeśli pole jest puste, oznacza to, że kontener został utworzony przed wprowadzeniem zmiany i nadal zapisuje logi bez limitu.
Nawyki utrzymujące mały serwer Docker w dobrej kondycji
Żaden z poniższych punktów nie wymaga panelu sterowania ani nauki obsługi dodatkowych narzędzi.
- Uruchamiaj
docker system dforazdf -h /pierwszego dnia każdego miesiąca. Dwa polecenia, trzydzieści sekund pracy i widać trendy na długo przed wystąpieniem awarii. - Przypisz każdej usłudze limit pamięci, nawet tym, które wydają się niewielkie. Limit sprawia, że awaria ogranicza się do restartu jednego kontenera, zamiast wpływać na cały host.
- Monitoruj serwer z zewnątrz, aby otrzymywać powiadomienia o wyczerpywaniu się pamięci lub miejsca na dysku, zanim zareaguje kernel. Uptime Kuma działa w kontenerze i w stanie spoczynku zużywa około 95 MB.
- Twórz kopie zapasowe wolumenów, a nie kontenerów. Kontener jest elementem tymczasowym, wolumen nie. Kopie zapasowe restic na VPS obejmują harmonogram oraz test przywracania danych.
- Przypinaj tagi obrazów w pliku compose i aktualizuj je w wybranym przez siebie dniu. Przy użyciu
latest, wersja pobrana podczas kolejnegodocker compose pullbędzie tą, która została wydana danego ranka.
Mały serwer VPS z Dockerem pozostaje stabilny przez lata, jeśli cztery parametry mieszczą się w normie: budżet pamięci, lista opublikowanych portów, polityka restartu dla każdej usługi oraz ilość wolnego miejsca na dysku. Wszystko inne to ten sam Docker, którego używasz lokalnie.
FAQ
Ile pamięci RAM potrzeba do uruchomienia Docker na VPS?
Sam Docker jest mało wymagający. Demon oraz containerd zajmują łącznie około 100 MB, a reszta zapotrzebowania zależy od uruchomionych kontenerów. Najpierw należy zarezerwować zasoby dla hosta: 768 MB na maszynie o pojemności 2048 MB na system operacyjny, demona oraz zapas, co pozostawia 1280 MB na kontenery. Baza danych przy 512 MB, reverse proxy przy 128 MB oraz dwie małe aplikacje mieszczą się w tym limicie. Własny stos należy zmierzyć za pomocą docker stats --no-stream, zamiast polegać na publikowanych szacunkach.
Czy można uruchomić Docker na VPS z 1 GB RAM?
Tak, dla jednego lub dwóch lekkich kontenerów, pod warunkiem dodania pliku wymiany (swap) przed rozpoczęciem pracy. Mniej więcej połowa zasobów maszyny 1 GB jest zajęta przez system operacyjny i demona Docker, co pozostawia miejsce na małą aplikację i reverse proxy, ale nie na bazę danych pod realnym obciążeniem. Budowanie obrazów na maszynie o tej wielkości zakończy się niepowodzeniem lub spowoduje zamknięcie innych procesów, dlatego obrazy należy budować gdzie indziej i pobierać gotowe.
Czy UFW chroni kontener Docker?
Nie w przypadku portów wystawionych na zewnątrz. Docker tworzy własne reguły DNAT i forward, więc pakiet skierowany na wystawiony port kontenera jest przekazywany bezpośrednio do niego, zamiast trafiać do hosta, przez co reguły INPUT zarządzane przez UFW go nie widzą. ufw deny 5432 może być aktywny, podczas gdy port odpowiada na zapytania z Internetu. Należy wystawiać porty na interfejs loopback za pomocą 127.0.0.1:5432:5432, pozostawiać usługi wewnętrzne niewystawione lub filtrować ruch w łańcuchu DOCKER-USER.
Czy kontenery zrestartują się po restarcie VPS?
Tylko jeśli zostały utworzone z odpowiednią polityką restartu. Należy ustawić restart: unless-stopped dla każdej usługi, wykonać docker compose up -d, aby kontenery zostały zaktualizowane, a następnie potwierdzić, że systemctl is-enabled docker zwraca enabled. Następnie należy celowo zrestartować serwer i sprawdzić docker compose ps. Polityka restartu, która nie została przetestowana, nie jest wiarygodna.
Jak często należy czyścić obrazy Docker?
Dla większości małych serwerów wystarcza czyszczenie raz w miesiącu lub gdy docker system df wskazuje ilość miejsca, którą warto odzyskać. docker image prune -a oraz docker builder prune są bezpieczne podczas działania usług, ponieważ używane obrazy i pamięć podręczna są pomijane. Należy unikać docker system prune --volumes, chyba że wiadomo dokładnie, które wolumeny są nieużywane, ponieważ polecenie to usuwa dane każdego stosu, który jest aktualnie zatrzymany.