Jeden serwer, wiele domen — czy to w ogóle możliwe?
Kiedy klienci dowiadują się, że obsługujemy kilkanaście różnych stron internetowych i aplikacji na jednym serwerze fizycznym, często reagują niedowierzaniem. Czy to nie jest ryzykowne? Czy wydajność nie cierpi? Czy awaria jednej strony nie łoży pozostałych?
Odpowiedź na wszystkie te pytania brzmi: nie — pod warunkiem, że architektura jest dobrze zaprojektowana. W tym artykule pokażemy, jak to robimy.
Architektura: Nginx jako reverse proxy
Sercem naszej infrastruktury jest Nginx — lekki, wydajny serwer HTTP, który pełni rolę reverse proxy. Nginx nasłuchuje na portach 80 (HTTP) i 443 (HTTPS), przyjmuje wszystkie przychodzące żądania, a następnie kieruje je do odpowiednich kontenerów Docker na podstawie nazwy domeny.
Schemat wygląda następująco:
Internet → Nginx (:80/:443) → Kontener aplikacji (port wewnętrzny)
Każda domena ma swoją konfigurację w osobnym pliku. Nginx na podstawie nagłówka Host w żądaniu HTTP wie, do którego kontenera przekierować ruch. Ten mechanizm nazywa się virtual hosting i jest standardem w branży od lat.
Kluczowe elementy konfiguracji Nginx:
- Przekierowanie HTTP na HTTPS — każde żądanie na porcie 80 jest automatycznie przekierowane na 443
- Certyfikaty SSL — każda domena ma własny certyfikat Let's Encrypt
- Nagłówki bezpieczeństwa — HSTS, X-Frame-Options, Content-Security-Policy
- Rate limiting — ochrona przed atakami brute-force (10 żądań na sekundę)
- Kompresja gzip — automatyczna kompresja odpowiedzi, mniejszy transfer danych
Docker — izolacja bez kosztów wirtualizacji
Każda nasza aplikacja działa w osobnym kontenerze Docker. Kontener to lekka forma wirtualizacji, która zapewnia izolację procesów bez narzutu tradycyjnych maszyn wirtualnych.
Co daje nam Docker w praktyce?
Izolacja
Każda aplikacja ma własne środowisko uruchomieniowe, własne zależności i własną konfigurację. Jeśli jedna aplikacja wymaga Go 1.22, a inna Go 1.25 — nie ma żadnego konfliktu. Każdy kontener jest niezależny.
Powtarzalność
Kontener zbudowany na maszynie deweloperskiej będzie działał identycznie na serwerze produkcyjnym. Koniec z klasycznym problemem „u mnie działa". Dockerfile precyzyjnie opisuje środowisko, w którym aplikacja ma działać.
Wieloetapowe budowanie (multi-stage builds)
Nasze pliki Dockerfile korzystają z wieloetapowego budowania. W pierwszym etapie kompilujemy aplikację Go z pełnym zestawem narzędzi deweloperskich. W drugim etapie kopiujemy wynikowy plik binarny do minimalnego obrazu Alpine Linux. Efekt — finalny obraz waży kilkanaście megabajtów zamiast kilkuset.
Etap 1: golang:1.22-alpine → kompilacja → plik binarny
Etap 2: alpine:3.19 + plik binarny → obraz produkcyjny (~15 MB)
Łatwe zarządzanie
Docker Compose pozwala nam zdefiniować wszystkie serwisy w jednym pliku YAML. Uruchomienie całej infrastruktury to jedno polecenie: docker compose up -d. Restart konkretnego serwisu: docker compose restart nazwa_serwisu. Podgląd logów: docker compose logs -f nazwa_serwisu.
Sieć wewnętrzna — bridge network
Wszystkie nasze kontenery komunikują się przez wewnętrzną sieć Docker typu bridge. Ta sieć jest niewidoczna z zewnątrz — jedynym punktem wejścia jest Nginx.
Korzyści tego rozwiązania:
- Bezpieczeństwo — kontener aplikacji nie jest bezpośrednio dostępny z internetu
- Prosta konfiguracja — kontenery odnajdują się po nazwach, bez potrzeby zarządzania adresami IP
- Izolacja — nawet jeśli atakujący przejmie jeden kontener, nie ma automatycznego dostępu do pozostałych
Certyfikaty SSL — automatyzacja z Let's Encrypt
Zarządzanie certyfikatami SSL dla kilkunastu domen mogłoby być koszmarem administracyjnym. Zbudowaliśmy własnego klienta ACME w Go, który automatycznie:
- Generuje klucze prywatne dla każdej domeny
- Tworzy żądania podpisania certyfikatu (CSR)
- Komunikuje się z serwerami Let's Encrypt
- Przeprowadza weryfikację domeny
- Pobiera i instaluje certyfikaty
- Odnawia certyfikaty przed wygaśnięciem
Cały proces jest zdefiniowany w pliku konfiguracyjnym YAML — dodanie nowej domeny to dopisanie kilku linii.
Monitoring i logi
Każdy nasz serwis loguje żądania HTTP z timestampem, adresem IP, metodą, ścieżką i czasem odpowiedzi. Nginx również prowadzi własne logi dostępowe i błędów. Dzięki Docker Compose możemy w każdej chwili podejrzeć logi dowolnego serwisu w czasie rzeczywistym.
Na poziomie systemu operacyjnego monitorujemy:
- Zużycie CPU i RAM — per kontener i ogólne
- Zajętość dysku — z alertami przy 80% i 90%
- Czas odpowiedzi — każda anomalia jest widoczna w logach
- Uptime — zewnętrzny monitoring sprawdza dostępność każdej domeny co minutę
Wydajność — liczby mówią same za siebie
Nasze lekkie serwisy Go mają minimalne wymagania zasobowe. Typowy kontener zużywa:
- RAM: 10-20 MB (porównaj z 200+ MB dla typowej aplikacji Node.js)
- CPU: ułamek procenta w stanie idle
- Dysk: 10-15 MB na obraz Docker
Na jednym serwerze z 8 GB RAM i 4 rdzeniami CPU spokojnie obsługujemy kilkanaście serwisów z dużym zapasem wydajności. Średni czas odpowiedzi to poniżej 50 ms, a uptime przekracza 99.9%.
Ważna pułapka: restart Nginx po rebuild
Jedną z rzeczy, które nauczyliśmy się na własnych błędach, jest konieczność restartu Nginx po przebudowie dowolnego kontenera. Nginx cachuje adresy IP kontenerów przy starcie — gdy kontener zostaje przebudowany, otrzymuje nowy adres IP, a Nginx wciąż próbuje łączyć się ze starym. Efekt? Błąd 502 Bad Gateway.
Rozwiązanie jest proste: po każdym docker compose up -d --build serwis wykonujemy docker compose restart nginx. To jedna dodatkowa komenda, ale zapobiega frustrującym przestojom.
Dodawanie nowej strony — proces
Dodanie nowej strony do naszej infrastruktury to procedura, która zajmuje mniej niż godzinę:
- Tworzymy nowy projekt Go z plikiem
main.goiDockerfile - Dodajemy serwis do
docker-compose.yml - Tworzymy konfigurację Nginx dla nowej domeny
- Generujemy certyfikat SSL
- Uruchamiamy kontenery i restartujemy Nginx
Cały proces jest powtarzalny i przewidywalny — nie ma tu żadnej magii, tylko dobrze zorganizowana infrastruktura.
Podsumowanie
Nasza architektura oparta na Docker + Nginx + Go to nie jest rozwiązanie dla każdego. Wymaga znajomości Linuxa, Dockera i konfiguracji Nginx. Ale jeśli masz te umiejętności (lub chcesz, żebyśmy zrobili to za Ciebie), zyskujesz infrastrukturę, która jest szybka, bezpieczna i tania w utrzymaniu.
Potrzebujesz podobnego rozwiązania dla swoich projektów? Napisz do nas — pomożemy zaprojektować i wdrożyć infrastrukturę dopasowaną do Twoich potrzeb.