Docker na VPS: jak uruchamiać aplikacje bez bałaganu na serwerze

Masz tani VPS, logujesz się po SSH, instalujesz Node.js, PostgreSQL, Nginx, kilka paczek z apta, potem jeszcze certbot, PM2, zmienne środowiskowe, pliki konfiguracyjne i nagle serwer przestaje być prosty. Po kilku tygodniach trudno powiedzieć, co jest potrzebne, co zostało po testach, a czego lepiej nie ruszać, bo „chyba działa”.

Docker rozwiązuje właśnie ten problem. Nie robi z VPS-a systemu bezobsługowego, ale porządkuje sposób uruchamiania aplikacji. Zamiast instalować każdą usługę bezpośrednio w systemie, uruchamiasz ją w osobnym kontenerze. Aplikacja, baza danych, reverse proxy, Redis, Umami, Uptime Kuma czy panel typu Coolify mogą działać obok siebie bez mieszania zależności w systemie hosta.

Największa zmiana mentalna jest prosta: VPS ma być miejscem uruchamiania kontenerów, a nie śmietnikiem po instalacjach. System powinien być nudny: Docker, firewall, SSH, katalogi projektów, backup, monitoring. Reszta ma być opisana w plikach konfiguracyjnych, które da się przenieść na drugi serwer i odtworzyć bez archeologii w historii terminala.

Czym Docker różni się od ręcznej instalacji aplikacji na VPS?

Przy ręcznej instalacji aplikacji na VPS zwykle robisz wszystko bezpośrednio w systemie. Instalujesz Node.js, globalne pakiety npm, PostgreSQL, reverse proxy, bibliotekę do kompresji obrazów, zależności projektu i kilka narzędzi pomocniczych. Na początku to wygląda niewinnie. Problem zaczyna się przy drugiej albo trzeciej aplikacji.

Jedna wymaga Node.js 18, druga Node.js 20. Jedna używa PostgreSQL, druga MySQL. Jedna chce port 3000, druga też. Aktualizacja systemu zmienia wersję biblioteki, aplikacja przestaje działać, a Ty próbujesz dojść, czy winny jest kod, baza, reverse proxy, czy pakiet zainstalowany trzy miesiące temu „tylko na chwilę”.

Kontener to odizolowane środowisko uruchomieniowe dla procesu. Aplikacja widzi własny system plików, własne zależności i własną konfigurację procesu, ale korzysta z kernela systemu hosta. To nie jest pełna maszyna wirtualna. Kontener jest lżejszy, szybciej startuje i łatwiej go wyrzucić.

Obraz Dockerowy to szablon, z którego powstaje kontener. Obraz może zawierać:

  • system bazowy, np. Alpine albo Debian,
  • runtime, np. Node.js,
  • kod aplikacji,
  • zależności z package.json,
  • domyślną komendę startową,
  • katalog roboczy,
  • informację o porcie, na którym aplikacja nasłuchuje.

Obraz jest zapakowaną instrukcją uruchomienia. Kontener to działająca instancja tego obrazu.

Ręczne uruchamianie aplikacji Node.js na VPS-ie często wygląda tak:

sudo apt update
sudo apt install nodejs npm postgresql nginx
npm install
npm run build
npm start

To działa, ale miesza projekt z systemem. Po kilku wdrożeniach pojawia się pytanie: które pakiety są od której aplikacji? Gdzie jest konfiguracja? Kto ustawił zmienną środowiskową? Dlaczego po restarcie serwera aplikacja nie wstała?

W Dockerze projekt opisujesz w plikach. Najczęściej będą to:

  • Dockerfile — przepis na obraz aplikacji,
  • compose.yaml albo docker-compose.yml — opis usług, portów, sieci i wolumenów,
  • .env — zmienne środowiskowe,
  • katalogi z konfiguracją, np. caddy/, nginx/, config/,
  • katalog backups/ albo osobny mechanizm backupu.

Prosty Dockerfile dla aplikacji Node.js może wyglądać tak:

FROM node:20-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

EXPOSE 3000

CMD ["npm", "start"]

Ten plik mówi: użyj obrazu Node.js 20, ustaw katalog /app, zainstaluj zależności produkcyjne, skopiuj kod i uruchom aplikację. To jest znacznie czytelniejsze niż historia komend wpisywanych ręcznie na serwerze.

Różnica nie polega tylko na wygodzie. Docker zmienia sposób zarządzania ryzykiem.

Najważniejsze korzyści:

  • izolacja zależności — jedna aplikacja może używać innej wersji runtime’u niż druga;
  • odtwarzalność środowiska — przenosisz katalog projektu i uruchamiasz usługi z pliku Compose;
  • mniej bałaganu w systemie — nie instalujesz każdej usługi globalnie;
  • prostsze aktualizacje — podmieniasz obraz i restartujesz kontener;
  • szybsze testy — możesz uruchomić PostgreSQL, Redis albo aplikację testową bez trwałego instalowania ich w systemie;
  • łatwiejsze wycofanie zmian — jeśli nowy kontener nie działa, wracasz do poprzedniego obrazu albo konfiguracji.

Docker nie zwalnia z myślenia. Nadal trzeba pilnować firewalli, aktualizacji systemu, backupu, limitów RAM-u i bezpieczeństwa. Kontener nie jest magicznym sejfem. Jeśli wystawisz panel administracyjny bez hasła na publiczny port, Docker nie zrobi z tego bezpiecznej usługi.

Dobry podział obowiązków wygląda tak:

  • system hosta: Docker, firewall, użytkownicy, SSH, backup, monitoring;
  • kontenery: aplikacje, bazy, reverse proxy, narzędzia self-hostingowe;
  • pliki projektu: konfiguracja, zmienne środowiskowe, definicje usług;
  • wolumeny: dane, których nie wolno stracić.

Na małym VPS-ie dobrze zacząć od jednego katalogu:

mkdir -p /opt/apps
cd /opt/apps

Każda aplikacja powinna mieć własny podkatalog:

/opt/apps/
├── moja-aplikacja/
│   ├── compose.yaml
│   ├── Dockerfile
│   ├── .env
│   ├── caddy/
│   └── backups/
├── uptime-kuma/
│   └── compose.yaml
└── umami/
    └── compose.yaml

To drobiazg, ale po pół roku robi ogromną różnicę. Nie szukasz konfiguracji po /root, /home, /var/www, /etc/nginx/sites-enabled i losowych katalogach testowych. Masz jeden standard.

Docker Compose: jeden plik do uruchamiania aplikacji, bazy i usług

docker run jest dobre do szybkiego testu. docker compose jest dobre do normalnej pracy.

Komenda docker run uruchamia pojedynczy kontener z parametrami podanymi w terminalu. Przykład:

docker run -d \
  --name moja-aplikacja \
  -p 3000:3000 \
  --env NODE_ENV=production \
  node:20-alpine

Da się tak pracować, ale tylko przez chwilę. Przy prawdziwej aplikacji liczba parametrów szybko rośnie. Dochodzi baza danych, hasło, wolumen, sieć, restart po awarii, reverse proxy, certyfikaty, healthcheck. Jedna komenda zmienia się w długiego potwora, którego nikt rozsądny nie chce odtwarzać z historii terminala.

Docker Compose zapisuje te ustawienia w pliku YAML. Dzięki temu cały stos aplikacji — Node.js, PostgreSQL i reverse proxy — można uruchomić jedną komendą:

docker compose up -d

A zatrzymać tak:

docker compose down

Praktyczny scenariusz: aplikacja Node.js, baza PostgreSQL i reverse proxy na jednym VPS-ie.

Załóżmy, że aplikacja działa w kontenerze na porcie 3000, baza PostgreSQL ma być dostępna tylko wewnątrz sieci Dockera, a na zewnątrz wystawiamy wyłącznie reverse proxy. W 2026 roku pokazywanie produkcyjnego proxy wyłącznie na porcie 80 wygląda słabo, więc zamiast klasycznego Nginxa z ręcznym certbotem użyjmy Caddy. Jest prostszy dla małych wdrożeń i automatycznie obsługuje HTTPS dla domeny.

Struktura katalogu:

/opt/apps/node-postgres-proxy/
├── compose.yaml
├── Dockerfile
├── .env
├── package.json
├── src/
└── caddy/
    └── Caddyfile

Plik .env:

POSTGRES_DB=appdb
POSTGRES_USER=appuser
POSTGRES_PASSWORD=zmien_to_na_dlugie_losowe_haslo
DATABASE_URL=postgres://appuser:zmien_to_na_dlugie_losowe_haslo@db:5432/appdb
NODE_ENV=production

Hasła nie powinny trafiać do repozytorium. Plik .env trzymaj na serwerze, dodaj do .gitignore i backupuj w bezpieczny sposób. Przy małych projektach to wystarczy. Przy większych warto przejść na menedżer sekretów, ale na start najważniejsze jest jedno: nie wklejaj haseł bezpośrednio w publiczny plik Compose.

Przykładowy compose.yaml:

services:
  app:
    build: .
    container_name: node_app
    restart: unless-stopped
    env_file:
      - .env
    depends_on:
      - db
    networks:
      - internal

  db:
    image: postgres:16-alpine
    container_name: node_postgres
    restart: unless-stopped
    env_file:
      - .env
    volumes:
      - postgres_data:/var/lib/postgresql/data
    networks:
      - internal

  proxy:
    image: caddy:2-alpine
    container_name: node_proxy
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - app
    networks:
      - internal

volumes:
  postgres_data:
  caddy_data:
  caddy_config:

networks:
  internal:
    driver: bridge

Minimalny Caddyfile:

example.com {
    reverse_proxy app:3000
}

To wszystko. Caddy przy poprawnie skierowanej domenie i otwartych portach 80 oraz 443 sam zajmie się certyfikatem HTTPS i jego odnawianiem. Nie trzeba pisać osobnej konfiguracji SSL w Nginxie ani pilnować certbota. Nginx nadal ma sens przy bardziej złożonych konfiguracjach, ale do małej aplikacji self-hosted Caddy często szybciej prowadzi do poprawnego, szyfrowanego wdrożenia.

W tym układzie reverse_proxy app:3000 działa dlatego, że kontenery są w tej samej sieci Dockera. Caddy nie musi znać adresu IP kontenera. Używa nazwy usługi app.

To jedna z najważniejszych zasad w Compose: kontenery komunikują się ze sobą po nazwach usług, a nie po localhost.

Dla aplikacji localhost oznacza jej własny kontener. Jeśli aplikacja Node.js ma łączyć się z PostgreSQL, nie wpisujesz:

DATABASE_URL=postgres://appuser:haslo@localhost:5432/appdb

Tylko:

DATABASE_URL=postgres://appuser:haslo@db:5432/appdb

Bo db to nazwa usługi PostgreSQL w pliku Compose.

Porty też trzeba rozumieć bez skrótów myślowych. Zapis:

ports:
  - "443:443"

oznacza:

port_na_serwerze:port_w_kontenerze

Czyli ruch przychodzący na port 443 VPS-a trafia do portu 443 w kontenerze reverse proxy.

Dla aplikacji Node.js celowo nie dodajemy:

ports:
  - "3000:3000"

Aplikacja nie musi być dostępna publicznie. Ma ją widzieć tylko reverse proxy. To ogranicza powierzchnię ataku i zmniejsza chaos z portami.

Priorytet przy wystawianiu usług na VPS-ie jest taki:

  1. Na publiczne porty wystawiaj tylko to, co musi być publiczne.
  2. Bazy danych trzymaj w sieci wewnętrznej Dockera.
  3. Panele administracyjne zabezpieczaj hasłem, VPN-em albo ograniczeniem IP.
  4. Nie publikuj portów “na chwilę”, jeśli istnieje ryzyko, że zapomnisz je zamknąć.

Wolumeny są drugim miejscem, w którym początkujący tracą dane. Kontener można usunąć. Obraz można podmienić. Dane bazy muszą przeżyć.

W przykładzie PostgreSQL ma wolumen:

volumes:
  - postgres_data:/var/lib/postgresql/data

A na dole pliku:

volumes:
  postgres_data:

To named volume, czyli wolumen zarządzany przez Dockera. Dane PostgreSQL nie siedzą w warstwie kontenera, tylko w osobnym miejscu zarządzanym przez silnik Dockera. Usunięcie kontenera nie powinno usunąć takiego wolumenu.

Tu pojawia się ważna różnica:

docker compose down

zatrzyma i usunie kontenery oraz sieć projektu, ale zostawi named volumes.

Natomiast:

docker compose down -v

usunie również wolumeny zdefiniowane w projekcie. Przy bazie danych to może oznaczać utratę danych. Tej komendy nie wpisuje się mechanicznie.

Pliki konfiguracyjne, takie jak Caddyfile, wygodnie trzymać jako bind mount:

volumes:
  - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro

Lewy fragment to ścieżka na VPS-ie, prawy to ścieżka w kontenerze. Dopisek :ro oznacza tryb tylko do odczytu. To dobry nawyk przy konfiguracji: kontener ma czytać plik, nie musi go modyfikować.

W uproszczeniu:

  • named volume wybieraj dla danych aplikacji, baz danych i wewnętrznych danych usług;
  • bind mount wybieraj dla konfiguracji, kodu w środowisku developerskim i plików, które chcesz świadomie trzymać w katalogu projektu;
  • nie trzymaj ważnych danych tylko wewnątrz kontenera, bo kontener jest elementem wymiennym.

Aktualizacja kontenerów powinna być nudna i powtarzalna. Dla obrazów pobieranych z rejestru typowy schemat wygląda tak:

cd /opt/apps/nazwa-projektu
docker compose pull
docker compose up -d
docker image prune

Dla aplikacji budowanej lokalnie:

cd /opt/apps/node-postgres-proxy
docker compose build --pull
docker compose up -d
docker image prune

Po aktualizacji sprawdź:

docker compose ps
docker compose logs -f app
docker compose logs -f proxy

Nie aktualizuj kilku krytycznych usług naraz, jeśli nie masz backupu i czasu na reakcję. Najpierw aplikacja, potem baza. Aktualizacje PostgreSQL, MySQL czy MariaDB traktuj ostrożniej niż zwykły restart kontenera. Zmiana wersji głównej, np. PostgreSQL 15 → 16, może wymagać migracji danych, a nie tylko podmiany numeru obrazu w compose.yaml.

Przy małym VPS-ie trzeba też pilnować zasobów. Docker ułatwia uruchamianie usług, ale nie znosi limitów sprzętu. VPS z 1 GB RAM-u może udźwignąć prostą aplikację Node.js, PostgreSQL i lekki reverse proxy, ale dorzucenie panelu, monitoringu, kilku baz i narzędzi analitycznych szybko skończy się swapowaniem albo ubijaniem procesów przez system.

Przydatne komendy kontrolne:

docker stats

pokazuje zużycie CPU i RAM-u przez kontenery.

docker system df

pokazuje, ile miejsca zajmują obrazy, kontenery, wolumeny i cache buildów.

df -h

pokazuje wolne miejsce na dysku VPS-a.

Na produkcyjnym VPS-ie brak miejsca na dysku bywa bardziej zdradliwy niż brak RAM-u. Logi, stare obrazy i cache buildów potrafią zjeść partycję bez ostrzeżenia. Szczególnie groźne są logi kontenerów, jeśli aplikacja wyrzuca dużo błędów albo debugowania na standardowe wyjście.

Dlatego warto ustawić limit logów globalnie w Dockerze. Utwórz albo edytuj plik:

sudo nano /etc/docker/daemon.json

I dodaj:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

Potem zrestartuj usługę Dockera:

sudo systemctl restart docker

Ten zapis oznacza, że pojedynczy plik logu kontenera może mieć maksymalnie 10 MB, a Docker będzie trzymał do 3 plików. To nie zastępuje monitoringu, ale chroni przed najgłupszym scenariuszem: aplikacja wpada w pętlę błędów, logi rosną przez weekend, a w poniedziałek baza nie może zapisać danych, bo dysk jest pełny.

Najczęstsze błędy początkujących: porty, wolumeny, sieci i backup

Publiczny port, którego miało nie być

Najdroższe błędy w Dockerze na VPS-ie zwykle nie wynikają z samego Dockera. Wynikają z błędnego założenia, że skoro firewall jest włączony, to wszystko za nim jest bezpieczne.

Jeśli w compose.yaml wpiszesz:

ports:
  - "5432:5432"

dla PostgreSQL, baza może zacząć nasłuchiwać na publicznym porcie VPS-a. To rzadko ma sens. Aplikacja może łączyć się z bazą po wewnętrznej sieci Dockera. Publiczny dostęp do PostgreSQL albo MySQL zostaw dla sytuacji, w których naprawdę go potrzebujesz, najlepiej przez VPN, tunel SSH albo ograniczenie IP.

Ważne: Docker bezpośrednio modyfikuje reguły iptables. Oznacza to, że jeśli zmapujesz port jako "5432:5432", Docker może otworzyć go na świat z pominięciem założeń, które masz w systemowym firewallu, np. UFW. Jeśli musisz wystawić bazę lub panel tylko na potrzeby tunelu SSH albo lokalnego narzędzia, binduj port do pętli zwrotnej: "127.0.0.1:5432:5432".

Bezpieczniejszy układ dla bazy:

services:
  app:
    networks:
      - internal

  db:
    networks:
      - internal

Bez sekcji ports przy bazie.

Jeżeli potrzebujesz lokalnego dostępu z serwera, użyj bindowania do 127.0.0.1:

ports:
  - "127.0.0.1:5432:5432"

Wtedy port nie jest wystawiany na publiczny interfejs sieciowy VPS-a. Nadal trzeba rozumieć, co się robi, ale ryzyko przypadkowego pokazania bazy całemu internetowi jest znacznie mniejsze.

Mylenie portu hosta z portem kontenera

Zapis:

ports:
  - "8080:80"

oznacza, że na VPS-ie używasz portu 8080, a w kontenerze usługa działa na 80. W przeglądarce wejdziesz więc na:

http://adres-serwera:8080

Nie na port 80, chyba że reverse proxy przekieruje ruch inaczej.

Ten błąd jest banalny, ale częsty. Ktoś zmienia port po lewej stronie, restartuje kontener, a potem szuka problemu w aplikacji. Problem nie jest w aplikacji. Ruch przychodzi na inny port, niż zakłada konfiguracja.

Pułapka localhosta

W kontenerze aplikacji localhost oznacza kontener aplikacji. Nie hosta. Nie bazę. Nie reverse proxy. Ten szczegół potrafi zjeść pół dnia.

Do innych usług w Compose odwołujesz się po nazwie usługi:

DATABASE_HOST=db
REDIS_HOST=redis

Nie tak:

DATABASE_HOST=localhost

Jeżeli aplikacja Node.js działa w kontenerze app, a PostgreSQL w kontenerze db, połączenie idzie na db:5432. Nazwa db działa, bo Compose tworzy wewnętrzny DNS dla usług w tej samej sieci.

Wolumeny albo utrata danych

Baza danych bez wolumenu to proszenie się o katastrofę. Jeśli PostgreSQL zapisuje dane tylko w warstwie kontenera, to usunięcie kontenera może oznaczać koniec danych.

Dla PostgreSQL standardowo montujesz wolumen do:

/var/lib/postgresql/data

Dla MySQL albo MariaDB zwykle będzie to:

/var/lib/mysql

Dla aplikacji plikowych trzeba sprawdzić dokumentację konkretnego obrazu. Nie zgaduj ścieżki. Jeśli aplikacja zapisuje uploady w /app/uploads, a Ty zamontujesz wolumen w /data, backup będzie pięknie wykonany i kompletnie bezużyteczny.

Backup widmo: idealny przepis na pustą bazę

Największy dramat, jaki widziałem na produkcji? Administrator regularnie backupował całe repozytorium z plikami docker-compose.yml, przekonany, że jest bezpieczny. Gdy dysk VPS-a padł, okazało się, że ma idealne przepisy na odtworzenie pustej bazy danych, ale właściwe dane produkcyjne — gigabajty wpisów, kont użytkowników i zamówień z named volumes — wyparowały bezpowrotnie.

Samo skopiowanie compose.yaml nie zapisuje danych PostgreSQL. Plik Compose pozwoli odtworzyć usługi, ale nie odtworzy rekordów w bazie.

Backup musi obejmować:

  • pliki konfiguracyjne projektu,
  • .env,
  • dane baz,
  • uploady użytkowników,
  • pliki generowane przez aplikację,
  • informację o wersjach obrazów albo cały plik Compose.

Najprostszy backup PostgreSQL z kontenera:

cd /opt/apps/node-postgres-proxy

docker compose exec -T db pg_dump -U appuser appdb > backups/appdb_$(date +%F_%H-%M).sql

Jeśli baza jest większa, kompresuj od razu:

docker compose exec -T db pg_dump -U appuser appdb | gzip > backups/appdb_$(date +%F_%H-%M).sql.gz

Do tego backup katalogu projektu:

tar -czf backups/project_files_$(date +%F_%H-%M).tar.gz \
  compose.yaml Dockerfile .env caddy/

To nadal backup lokalny. Chroni przed błędem aplikacji albo przypadkowym usunięciem kontenera, ale nie chroni przed awarią całego VPS-a. Minimum produkcyjne to kopia poza serwerem: drugi VPS, storage S3-compatible, zewnętrzny backup dostawcy albo regularne pobieranie archiwów na inny host.

Najważniejsza zasada backupu brzmi: backup, którego nigdy nie odtworzyłeś testowo, jest tylko nadzieją.

Raz na jakiś czas postaw usługę w osobnym katalogu albo na testowym VPS-ie i sprawdź:

  1. Czy masz compose.yaml.
  2. Czy masz .env.
  3. Czy masz dump bazy.
  4. Czy aplikacja startuje.
  5. Czy logowanie działa.
  6. Czy uploady i dane użytkowników są na miejscu.

Dopiero wtedy backup ma sens.

Myślenie, że latest oznacza zawsze stabilny

Tag latest brzmi dobrze, ale na produkcji jest pułapką. Nie oznacza „najbezpieczniejsza wersja”. Oznacza raczej: „to, co autor obrazu uznaje teraz za domyślne”. A to może się zmienić bez Twojej decyzji.

Zamiast:

image: postgres:latest

lepiej użyć:

image: postgres:16-alpine

Dla własnej aplikacji można tagować obrazy wersjami:

moja-aplikacja:1.4.2

albo identyfikatorem commita z repozytorium. Dzięki temu wiesz, co działa na produkcji.

Aktualizacja statelessowej aplikacji Node.js jest zwykle prosta: nowy obraz, restart, test logów. Aktualizacja bazy danych, narzędzia analitycznego albo panelu self-hostingowego może wymagać migracji. Przy usługach stanowych czytaj changelog przed zmianą wersji głównej. Nie po awarii.

Root w kontenerze i zbyt szerokie mounty

Wiele obrazów domyślnie działa jako root w kontenerze. To nie zawsze oznacza natychmiastową katastrofę, ale przy aplikacjach produkcyjnych warto wybierać obrazy, które wspierają uruchamianie jako nieuprzywilejowany użytkownik, albo samodzielnie ustawić użytkownika w Dockerfile.

Szczególnie ważne jest to wtedy, gdy kontener ma dostęp do bind mountów z hosta. Jeśli kontener może zapisywać do katalogu projektu, a aplikacja zostanie przejęta, atakujący może modyfikować pliki w tym katalogu. Dlatego konfigurację montuj jako :ro, jeśli kontener nie musi jej zmieniać.

Przykład:

volumes:
  - ./caddy/Caddyfile:/etc/caddy/Caddyfile:ro

Pliki konfiguracyjne porozrzucane po serwerze

Dobry standard na VPS-ie:

/opt/apps/nazwa-aplikacji/compose.yaml
/opt/apps/nazwa-aplikacji/.env
/opt/apps/nazwa-aplikacji/caddy/Caddyfile
/opt/apps/nazwa-aplikacji/backups/

Zły standard:

/root/test/docker-compose.yml
/home/admin/app-final2/.env
/var/www/old-api/config.js
/tmp/Caddyfile

Ten drugi układ działa tylko do pierwszej awarii, migracji albo urlopu osoby, która to składała. Potem zaczyna się zgadywanie, który plik jest aktualny.

Debugowanie bez podstawowych komend

Nie trzeba znać całego Dockera, żeby sensownie używać go na VPS-ie. Trzeba znać kilka komend, które pokazują stan systemu.

docker compose ps

pokazuje stan usług.

docker compose logs -f

pokazuje logi całego stosu.

docker compose logs -f app

pokazuje logi konkretnej usługi.

docker compose restart app

restartuje jedną usługę.

docker compose exec app sh

wchodzi do kontenera, jeśli obraz ma powłokę sh.

docker inspect nazwa_kontenera

pokazuje szczegóły kontenera, sieci, mountów i konfiguracji.

docker volume ls
docker volume inspect nazwa_wolumenu

pomaga sprawdzić, gdzie są wolumeny i czy w ogóle istnieją.

Na start nie instaluj od razu Coolify, CasaOS i pięciu aplikacji. Postaw jeden mały projekt: aplikacja, baza, reverse proxy, backup. Gdy rozumiesz porty, wolumeny, sieci i restart kontenerów, panele self-hostingowe przestają być czarną skrzynką.

FAQ: najczęstsze problemy z Dockerem na VPS

Dlaczego po restarcie VPS-a moje kontenery nie wstają automatycznie?
Sprawdź dwie rzeczy: czy usługa Dockera startuje razem z systemem i czy kontenery mają ustawioną politykę restartu. Włącz Dockera komendą sudo systemctl enable docker, a w compose.yaml dodaj do usług restart: unless-stopped albo restart: always. W praktyce unless-stopped jest bezpieczniejszym domyślnym wyborem, bo kontener zatrzymany ręcznie nie wstanie sam po restarcie Dockera.

Jak bezpiecznie wyczyścić stary cache budowania obrazów, który zajmuje dysk?
Sama komenda docker image prune usuwa głównie nieużywane obrazy. Jeśli budujesz własną aplikację dziesiątki razy, dużo miejsca może siedzieć w cache buildera. Użyj docker builder prune. Przed agresywnym czyszczeniem sprawdź zajętość komendą docker system df.

Czy mogę użyć docker system prune -a, żeby odzyskać miejsce?
Możesz, ale nie wpisuj tej komendy odruchowo na produkcji. docker system prune -a usuwa wszystkie nieużywane obrazy, nie tylko te bez tagów. To może utrudnić szybki rollback, bo poprzedni obraz trzeba będzie pobrać albo zbudować ponownie. Najpierw sprawdź docker system df, potem czyść konkretnie: obrazy, cache buildera albo zatrzymane kontenery.

Dlaczego aplikacja w kontenerze nie łączy się z bazą przez localhost?
Bo localhost w kontenerze oznacza ten sam kontener. Jeśli PostgreSQL działa jako usługa db w Compose, aplikacja powinna łączyć się z db:5432, nie z localhost:5432. To jeden z najczęstszych błędów przy pierwszym wdrożeniu.

Czy baza danych powinna mieć publiczny port?
Zazwyczaj nie. Jeśli aplikacja działa w tej samej sieci Dockera, baza powinna być dostępna tylko wewnętrznie, np. jako db:5432. Jeśli musisz mieć dostęp lokalny z serwera albo przez tunel SSH, zbindowanie do 127.0.0.1, np. "127.0.0.1:5432:5432", jest rozsądniejsze niż otwarcie portu na publiczny interfejs.

Dlaczego UFW pokazuje, że port jest zablokowany, a usługa z Dockera i tak odpowiada z internetu?
Docker tworzy własne reguły w iptables do obsługi publikowanych portów. Dlatego samo ufw deny 5432 może nie wystarczyć, jeśli kontener ma mapowanie "5432:5432". Najprostsza decyzja: nie publikuj portu bazy wcale. Jeśli port ma być tylko lokalny, binduj go do 127.0.0.1.

Jak sprawdzić, czy dane bazy są w wolumenie, a nie tylko w kontenerze?
Sprawdź compose.yaml i szukaj montowania do ścieżki danych bazy, np. /var/lib/postgresql/data dla PostgreSQL. Potem użyj docker volume ls oraz docker volume inspect nazwa_wolumenu. Jeśli baza nie ma wolumenu ani poprawnego bind mounta, zatrzymaj się i napraw to przed kolejną aktualizacją.

Czy backup katalogu /opt/apps/nazwa-aplikacji wystarczy?
Nie, jeśli dane są w named volumes. Taki backup zapisze konfigurację, ale niekoniecznie zapisze bazę danych. Dla PostgreSQL rób dump przez pg_dump, a dla plików użytkowników sprawdź, gdzie aplikacja rzeczywiście zapisuje uploady. Backup konfiguracji i backup danych to dwie różne rzeczy.

Jak ograniczyć logi kontenerów, żeby nie zjadły dysku?
Ustaw globalny limit w /etc/docker/daemon.json, np. max-size: "10m" i max-file: "3" dla sterownika json-file. Po zmianie zrestartuj Dockera. To szczególnie ważne na małych VPS-ach, gdzie kilka gigabajtów logów potrafi zatrzymać bazę albo aktualizacje systemu.

Czy Caddy jest zawsze lepszy od Nginxa jako reverse proxy?
Nie. Caddy jest świetny do małych i średnich wdrożeń, bo ma prosty Caddyfile i automatyczne HTTPS. Nginx daje ogromną kontrolę i jest standardem w wielu środowiskach produkcyjnych. Jeśli chcesz szybko wystawić jedną aplikację z HTTPS, Caddy będzie prostszy. Jeśli masz skomplikowane reguły, nietypowe cache, dużo legacy albo firmowy standard oparty na Nginxie, zostań przy Nginxie.

Od czego zacząć, jeśli Docker na VPS-ie wydaje się zbyt duży?
Od jednego stosu: aplikacja Node.js, PostgreSQL, Caddy i backup. Nie dokładaj paneli administracyjnych, monitoringu i automatycznych aktualizacji, dopóki nie rozumiesz portów, wolumenów i sieci. Najpierw opanuj podstawowy układ, potem dopiero automatyzuj.

Categories: VPS, Docker i self-hosting
Redakcja

Written by:Redakcja All posts by the author

toNIEmarketing tworzy CMspace - marka prowadząca i rozwijająca własne portale poradnikowe, oferująca pozyskiwanie linków z artykułów sponsorowanych. Na toNIEmarketing zajmujemy się tematami marketingu, SEO, GEO, content marketingu i AI. Dzielimy się wiedzą, obserwacjami i praktycznym poradami.

Leave a reply

Your email address will not be published. Required fields are marked *

Ciasteczka

Kontynuując przeglądanie strony, wyrażasz zgodę na używanie plików Cookies. Więcej informacji znajdziesz w polityce prywatności.