Dealownia
Menu
sprawdzono – jak sprawdzamy oferty

Docker Compose: strona, analityka i monitoring w jednym pliku

Gotowy plik compose: strona statyczna, Umami z Postgresem i Uptime Kuma za jednym nginx. Limity pamięci, aktualizacje obrazów i nocna kopia baz na małym VPS.

Aplikacje na własnym serwerzezaktualizowano 6 min czytania

Redakcja serwisu Dealownia · ceny i fakty ze stron dostawców, jak sprawdzamy

Strona statyczna, analityka Umami i monitoring Uptime Kuma na jednym VPS-ie, opisane w jednym pliku compose.yaml – z limitami pamięci, aktualizacją jedną komendą i nocną kopią danych. Całość jest darmowa poza samym serwerem. Układ: jeden nginx na hoście przyjmuje HTTPS, a narzędzia siedzą w kontenerach widocznych tylko lokalnie.

Czego potrzebujesz

  • VPS z Ubuntu 24.04 LTS, nginx, certbot i Dockerem z wtyczką Compose – instrukcja w poradniku od zera.
  • Minimum: 1 vCPU, 2 GB RAM (lub 1 GB z plikiem swap przy małym ruchu), 20 GB dysku. Po starcie, przy małym ruchu, kontenery zajmują orientacyjnie: Umami ok. 180 MiB, Uptime Kuma ok. 120 MiB, Postgres ok. 65 MiB – sprawdź u siebie docker stats. Jeśli dołożysz GlitchTip (orientacyjnie ok. 170 MiB) i Valkey, celuj w 2 vCPU i 4 GB RAM – wtedy jest zapas na budowanie stron i skoki ruchu.
  • Trzy rekordy DNS: example.pl, stats.example.pl, status.example.pl.

Na ten stack wybierz plan z 2 GB RAM (1 GB tylko z plikiem swap i przy małym ruchu).

Krok 1. Układ katalogów

bash
sudo mkdir -p /opt/apps/stack && sudo chown deploy:deploy /opt/apps/stack
cd /opt/apps/stack
mkdir -p site data/postgres data/uptime-kuma backup
cat > .env <<EOF
UMAMI_DB_PASSWORD=$(openssl rand -hex 24)
UMAMI_APP_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 .env

Do site/ wgrywasz zbudowaną stronę (np. katalog dist/ z Astro), w data/ żyją bazy, w backup/ lądują kopie.

Krok 2. Plik compose.yaml

yaml
name: stack

x-common: &common
  restart: unless-stopped
  logging:
    driver: json-file
    options: { max-size: "10m", max-file: "3" }

services:
  web:
    <<: *common
    image: nginx:stable-alpine
    volumes:
      - ./site:/usr/share/nginx/html:ro
    ports:
      - "127.0.0.1:8080:80"
    mem_limit: 64m
    cpus: 0.5

  db:
    <<: *common
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: ${UMAMI_DB_PASSWORD}
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U umami -d umami"]
      interval: 10s
      timeout: 5s
      retries: 10
    mem_limit: 512m
    cpus: 1.0

  umami:
    <<: *common
    image: ghcr.io/umami-software/umami:latest
    environment:
      DATABASE_URL: postgresql://umami:${UMAMI_DB_PASSWORD}@db:5432/umami
      APP_SECRET: ${UMAMI_APP_SECRET}
      DISABLE_TELEMETRY: "1"
    depends_on:
      db: { condition: service_healthy }
    ports:
      - "127.0.0.1:3000:3000"
    mem_limit: 512m
    cpus: 1.0

  uptime-kuma:
    <<: *common
    image: louislam/uptime-kuma:2
    environment:
      UPTIME_KUMA_DB_TYPE: sqlite
    volumes:
      - ./data/uptime-kuma:/app/data
    ports:
      - "127.0.0.1:3001:3001"
    mem_limit: 384m
    cpus: 0.75

Dlaczego tak:

  • Wszystkie porty na 127.0.0.1. Docker omija ufw, więc port opublikowany bez adresu byłby dostępny z internetu bez HTTPS.
  • mem_limit i cpus pilnują, żeby jedno narzędzie nie zagłodziło reszty serwera. Wartości wyżej to przykładowe limity dla Umami, Kumy i (z zapasem) Postgresa; przy mniejszym serwerze możesz je obniżyć, ale obserwuj docker stats przez kilka dni.
  • Kotwica x-common daje każdemu kontenerowi restart po awarii i rotację logów, nawet jeśli nie ustawiłeś jej w /etc/docker/daemon.json.
  • web jest opcjonalny. Strony prościej serwować nginx-em z hosta, bez kontenera – to o jedną warstwę mniej. Kontener ma sens, gdy chcesz, żeby cały stack dało się przenieść jednym katalogiem.

Sprawdź plik i uruchom:

bash
docker compose config -q
docker compose up -d
docker compose ps

config -q nic nie wypisuje, jeśli plik jest poprawny. Oczekujesz czterech kontenerów Up, z db w stanie healthy.

Krok 3. Jeden nginx na hoście dla wszystkich adresów

Uptime Kuma za proxy korzysta ze zmiennej $connection_upgrade z mapy WebSocket. Mapę WebSocket tworzysz raz na serwer (wyjaśnienie w poradniku o reverse proxy); polecenie niżej nic nie zmieni, jeśli plik już istnieje:

bash
[ -f /etc/nginx/conf.d/websocket-map.conf ] || sudo tee /etc/nginx/conf.d/websocket-map.conf >/dev/null <<'EOF'
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}
EOF

Plik /etc/nginx/sites-available/stack.conf (bez bloku map – ten jest już w conf.d):

nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.pl www.example.pl;
    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name stats.example.pl;
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name status.example.pl;
    location / {
        proxy_pass http://127.0.0.1:3001;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
    }
}
bash
sudo ln -s /etc/nginx/sites-available/stack.conf /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d example.pl -d www.example.pl -d stats.example.pl -d status.example.pl

Uptime Kuma potrzebuje nagłówków WebSocket, Umami – prawdziwego adresu odwiedzającego. Zaraz po starcie wejdź na oba panele: w Umami zmień domyślne hasło konta admin (umami), w Kumie załóż konto administratora. Szczegóły konfiguracji są w osobnych poradnikach o Umami i Uptime Kuma.

Krok 4. Kopia co noc

Plik /opt/apps/stack/backup.sh:

bash
#!/usr/bin/env bash
set -euo pipefail
umask 077
cd /opt/apps/stack
chmod 700 backup
ts=$(date +%F)
docker compose exec -T db pg_dump -U umami -Fc umami > "backup/umami-$ts.dump"
docker compose stop uptime-kuma
tar -czf "backup/uptime-kuma-$ts.tar.gz" -C data uptime-kuma
docker compose start uptime-kuma
find backup -type f -mtime +7 -delete
bash
chmod +x backup.sh && sudo ./backup.sh && sudo ls -lh backup
echo "30 3 * * * root /opt/apps/stack/backup.sh >>/var/log/stack-kopia.log 2>&1" | sudo tee /etc/cron.d/stack-kopia

Postgresa kopiujesz przez pg_dump, a nie kopiując katalog data/postgres – pliki działającej bazy skopiowane w locie bywają niespójne. Kumę zatrzymujemy na kilka sekund z tego samego powodu (SQLite). umask 077 i katalog backup z prawami 700 sprawiają, że zrzuty czyta tylko root. Kopie z siedmiu ostatnich dni wysyłaj dodatkowo zaszyfrowane poza serwer, jak w poradniku o kopiach. Strony w kopii nie są potrzebne, jeśli budujesz je z repozytorium.

Dopisz na końcu skryptu ping do Healthchecks.io albo monitora typu Push w Kumie – wtedy dowiesz się, że kopia się nie wykonała, zamiast odkryć to w dniu awarii.

Krok 5. Aktualizacje

bash
cd /opt/apps/stack
sudo ./backup.sh
docker compose pull
docker compose up -d
docker image prune -f

up -d odtwarza tylko kontenery, których obraz się zmienił. Zasady aktualizacji:

  • Tagi z główną wersją (postgres:17-alpine, uptime-kuma:2) zamiast latest tam, gdzie zmiana głównej wersji wymaga migracji. Wyjątek to Umami: jego dokumentacja instalacji podaje tylko tag latest, więc przed pull zawsze robisz kopię (sudo ./backup.sh wyżej).
  • Postgres 17 na 18 to nie zwykłe pull: potrzebny zrzut, nowy katalog danych i odtworzenie, a w wersji 18 zmienia się też ścieżka montowania (/var/lib/postgresql).
  • Przed aktualizacją zawsze świeża kopia.
  • Po aktualizacji sprawdź docker compose ps i logi: docker compose logs --since 10m.

Najczęstsze problemy

  • Serwer zwalnia, a dmesg pokazuje „Out of memory”. Kontener przekroczył mem_limit albo zabrakło pamięci na hoście. Sprawdź docker stats --no-stream, dodaj plik swap (sudo fallocate -l 2G /swapfile && sudo chmod 600 /swapfile && sudo mkswap /swapfile && sudo swapon /swapfile, plus wpis w /etc/fstab, żeby przetrwał restart) albo podnieś limit konkretnej usługi.
  • docker compose up zgłasza „port is already allocated”. Inny proces używa portu, np. stary kontener z wcześniejszych prób. sudo ss -tlnp | grep 3000 pokaże, kto go trzyma.
  • Umami nie startuje: „connection refused” do bazy. Postgres jeszcze się inicjuje. depends_on z condition: service_healthy rozwiązuje to przy starcie; po ręcznym restarcie samej bazy zrestartuj też umami.
  • Hasło w .env zmienione, a baza go nie przyjmuje. POSTGRES_PASSWORD działa tylko przy pierwszym tworzeniu bazy. Przy istniejących danych zmieniasz hasło w samej bazie (ALTER USER umami PASSWORD '...'), a dopiero potem w .env.
  • Panel Kumy wisi na ładowaniu. Brak Upgrade/Connection w bloku status.example.pl.

Co dalej

Dołóż zbieranie błędów JavaScriptu – GlitchTip może korzystać z tego samego Postgresa po utworzeniu osobnej bazy i użytkownika. Zanim zaczniesz kierować ruch na serwer, przejdź listę kontrolną zabezpieczeń. Jeśli szukasz maszyny z większą ilością RAM pod cały stack, porównaj plany w rankingu VPS.

Powiązane poradniki