Self-hosting Cusdis z Caddy na VPS
Spis treści
Wstęp#
Kiedy szukałem systemu komentarzy, szybko zdałem sobie sprawę, że większość dostępnych rozwiązań albo śledzi użytkowników, albo wymaga ciężkiego JavaScriptu, albo po prostu kosztuje. Cusdis wyróżnia się na tym tle. Jest lekki, open-source i od początku zaprojektowany z myślą o self-hostingu.
W tym wpisie pokazuję, jak wdrożyć Cusdis na dowolnym VPS, używając Dockera i Caddy jako reverse proxy, z poprawnie skonfigurowanym CORS-em.
Dlaczego Cusdis?#
Zanim przejdę do konfiguracji, kilka słów o tym, co skłoniło mnie do wyboru właśnie tego narzędzia:
- Prywatność — brak śledzenia przez strony trzecie
- Lekkość — widget waży około 5 KB po gzipie
- Kontrola — dane zostają na Twoim serwerze
- Zerowy koszt — żadnych miesięcznych subskrypcji
- Prostota — baza SQLite i minimalne wymagania sprzętowe
Po co w ogóle reverse proxy?#
Cusdis to aplikacja Node.js z wbudowanym serwerem HTTP i technicznie rzecz biorąc, mógłby sam odpowiadać na żądania z Internetu. Ale nie powinieneś tak robić, i zaraz wyjaśnię dlaczego.
Problem z CORS-em#
Jeśli osadzasz komentarze Cusdis na swojej domenie, a sama aplikacja Cusdis działa pod innym adresem, przeglądarka zablokuje połączenie bez odpowiednich nagłówków CORS. Żeby to naprawić bezpośrednio w Cusdis, musiałbyś edytować kod źródłowy i przebudowywać aplikację. W Caddy wystarczy jeden wpis w pliku konfiguracyjnym.
Problem z SSL-em#
Wbudowany serwer Node.js nie potrafi samodzielnie uzyskać certyfikatów SSL. Musiałbyś ręcznie konfigurować certbot, wstrzykiwać ścieżki do certyfikatów i pisać skrypty automatycznego odnawiania co 90 dni. Caddy robi to wszystko automatycznie — wystarczy podać nazwę domeny.
Problem z odpornością na ataki#
Serwery takie jak Caddy czy Nginx są napisane z myślą o walce z ruchem sieciowym. Caddy powstał w Go, który świetnie radzi sobie z atakami DDoS, wolnymi klientami jak Slowloris, czy tysiącami równoczesnych połączeń. Node.js jest jednowątkowy — jego zadaniem jest logika biznesowa, a nie ochrona przed złośliwym ruchem.
Problem z plikami statycznymi#
Reverse proxy potrafi kompresować odpowiedzi w formacie Gzip lub Brotli i cache’ować pliki statyczne. Serwowanie statyki przez Node.js niepotrzebnie obciąża główny wątek CPU, który powinien obsługiwać komentarze.
Podsumowując: Caddy działa jak pancerna tarcza przed Cusdisem. Zajmuje się HTTPS, nagłówkami, ochroną i kompresją. Do Cusdisa trafiają tylko czyste żądania biznesowe.
Wymagania#
- VPS z Dockerem i Docker Compose
- Domena lub subdomena wskazująca na ten VPS
- Podstawowa znajomość terminala
Struktura projektu#
Zaczynam od stworzenia dedykowanego katalogu:
mkdir -p /cusdis && cd /cusdis
W tym katalogu będą trzy pliki:
compose.yaml— definicja serwisów Docker.env— konfiguracja i sekretyCaddyfile— konfiguracja reverse proxy z CORS
Konfiguracja Docker Compose#
Tworzę plik compose.yaml:
services:
cusdis:
image: djyde/cusdis:latest
env_file: .env
volumes:
- cusdis_data:/data
restart: unless-stopped
caddy:
image: caddy:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
restart: unless-stopped
volumes:
cusdis_data:
caddy_data:
caddy_config:
Cusdis nasłuchuje wewnętrznie na porcie 3000. Caddy przejmuje ruch zewnętrzny i automatycznie obsługuje HTTPS.
Konfiguracja środowiska#
Tworzę plik .env z losowo wygenerowanymi sekretami:
JWT_SECRET=$(openssl rand -base64 32)
PASSWORD=$(openssl rand -base64 16)
cat > .env << EOF
PORT=8000
USERNAME=admin
PASSWORD=${PASSWORD}
JWT_SECRET=${JWT_SECRET}
DB_TYPE=sqlite
DB_URL=file:/data/db.sqlite
NEXTAUTH_URL=https://comments.twojadomena.pl
EOF
echo "Hasło admina: ${PASSWORD}"
Ważne: Zapisz wygenerowane hasło przed zamknięciem terminala — będzie potrzebne do pierwszego logowania.
Konfiguracja Caddy z CORS#
Tworzę Caddyfile:
:your_port {
reverse_proxy cusdis:3000
@widget path /js/iframe.umd.js
header @widget Access-Control-Allow-Origin https://yourdomain.com
header @widget Vary Origin
}
Dwa słowa wyjaśnienia. Matcher @widget dopasowuje wyłącznie ścieżkę do skryptu widgetu. Nagłówek Access-Control-Allow-Origin mówi przeglądarce, że tylko Twoja domena może odczytać ten zasób. Nagłówek Vary: Origin informuje cache, żeby rozróżniał odpowiedzi w zależności od nagłówka Origin — bez tego możesz trafić na nieprzewidywalne zachowanie przy cachowaniu.
Zamień comments.twojadomena.pl i twojadomena.pl na swoje własne domeny.
Wdrożenie#
docker compose up -d
docker compose logs -f
Caddy przy pierwszym uruchomieniu automatycznie pobiera certyfikat SSL z Let’s Encrypt. Nie musisz robić nic więcej.
Pierwsza konfiguracja w Cusdis#
- Otwórz
https://comments.twojadomena.pl - Zaloguj się danymi z pliku
.env - Kliknij “Add a site”
- Skopiuj wygenerowane App ID
Osadzenie widgetu na stronie#
Dodaj ten fragment HTML na każdej stronie, gdzie chcesz wyświetlać komentarze:
<div id="cusdis_thread"
data-host="https://comments.twojadomena.pl"
data-app-id="twoje-app-id"
data-page-id="unikalne-id-strony"
data-page-url="https://twojadomena.pl/strona"
data-page-title="Tytuł strony"
data-theme="dark"
></div>
<script async defer src="https://comments.twojadomena.pl/js/cusdis.es.js"></script>
Weryfikacja CORS#
Sprawdzam, czy nagłówki są poprawnie ustawione. Wysyłam preflight request:
curl -v -X OPTIONS \
-H "Origin: null" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: x-timezone-offset" \
"https://comments.twojadomena.pl/api/open/comments?page=1"
Poprawna odpowiedź powinna zawierać:
access-control-allow-origin: null
access-control-allow-credentials: true
access-control-allow-headers: *
Rozwiązywanie problemów#
“Provisional headers are shown”#
Przeglądarka zablokowała żądanie. Sprawdź dwie rzeczy: czy nagłówek Origin jest obsługiwany w Caddyfile i czy wszystkie wymagane nagłówki są wymienione w Access-Control-Allow-Headers.
Widget się nie ładuje#
Zaglądaj od razu do konsoli przeglądarki — błędy CORS są tam wyraźnie opisane. Najczęstsze przyczyny to brak obsługi null origin, brak x-timezone-offset w dozwolonych nagłówkach lub Cloudflare cachujący stare odpowiedzi. W tym ostatnim przypadku możesz przetestować z parametrem ?nocache=1.
Kontener crashuje#
Cusdis to aplikacja Next.js i potrzebuje około 200 MB RAM. Sprawdzam to w ten sposób:
docker stats --no-stream
free -h
Uwagi bezpieczeństwa#
Ta konfiguracja jest odpowiednia dla publicznego systemu komentarzy. Kilka rzeczy warto mieć na uwadze:
Access-Control-Allow-Origin: nulljest konieczne dla widgetu osadzonego wsrcdociframe- Komentarze domyślnie wymagają moderacji przed publikacją
- Warto rozważyć dodanie rate limitingu jako ochrony przed spamem
Utrzymanie#
Aktualizacja Cusdisa sprowadza się do dwóch komend:
docker compose pull
docker compose up -d
Backup bazy danych:
docker compose exec cusdis cat /data/db.sqlite > backup.sqlite
Podsumowanie#
Self-hosting Cusdis daje lekki, prywatny system komentarzy przy minimalnych zasobach — około 200 MB RAM, śladowe użycie CPU i kilka megabajtów dysku dla bazy SQLite.