AZZałuczkowski realizacja 04 z 04
Wszystkie realizacje

Migracja produkcji na własny stack

realizacja 04 z 04, 2026

Aplikacja i kilkanaście zadań cyklicznych stały na płatnej platformie chmurowej, gdzie każdy cron był osobną usługą z osobną ceną. Przeniosłem całość na własny serwer: niższy koszt, krótszy czas wdrożenia i pełna kontrola nad tym, co się dzieje przy deployu.

Punkt wyjścia

Aplikacja Next.js z bazą PostgreSQL plus kilkanaście zadań cyklicznych — każde jako osobna, płatna usługa na platformie PaaS. Wygodne na starcie, kosztowne przy skali i nieprzyjemne w diagnostyce: żeby zobaczyć, dlaczego zadanie nie wystartowało, trzeba było przeklikać się przez trzy ekrany panelu.

Co zrobiłem

  • Jeden stack Compose na serwerze: aplikacja plus baza, bez własnego reverse proxy — ruch wchodzi przez wspólną bramę, która obsługuje wszystkie aplikacje na tym hoście i sama wystawia certyfikaty.
  • Zadania cykliczne w aplikacji. Zamiast kilkunastu płatnych usług — scheduler wewnątrz procesu, który woła własne endpointy po pętli lokalnej, z tokenem, zapisem każdego przebiegu do tabeli i wyłącznikiem per zadanie w panelu. Historia przebiegów jest widoczna tam, gdzie się jej szuka, a nie w logach dostawcy.
  • Potok CI/CD: push do gałęzi głównej buduje obraz, wypycha go do rejestru floty, a krok wdrożeniowy po SSH uruchamia migracje bazy jako osobne, jednorazowe zadanie i dopiero potem podmienia kontener aplikacji. Kolejność jest tu istotna: migracje przed podmianą, nigdy w trakcie startu aplikacji.
  • Telemetria: logi i metryki idą do wspólnego stosu obserwowalności floty, z alertem, gdy zadanie cykliczne przestanie się zgłaszać.

Pułapki, które po drodze złapałem

Zmienne budowania kontra zmienne uruchomienia. Część konfiguracji Next.js wkompilowuje się w artefakt podczas budowania. Przekazanie jej dopiero przy starcie kontenera daje aplikację, która wygląda poprawnie i ma puste adresy w kodzie klienta. To jest dokładnie ten błąd, który przechodzi przez testy i wychodzi u użytkownika.

Kontener nie może być rootem. Na współdzielonym hoście obraz działający jako root to gotowy wektor ucieczki z kontenera. Każdy obraz dostaje własnego użytkownika, a prawa zapisu tylko do katalogów, które faktycznie są zapisywane w czasie działania.

Routing po unikalnej nazwie, nie po nazwie usługi. Każdy projekt Compose ma usługę o nazwie app. Na wspólnej sieci takie nazwy kolidują, a brama potrafi skierować ruch jednej aplikacji do zupełnie innej. Każda aplikacja wchodzi do sieci pod własnym, unikalnym aliasem i tylko po nim jest routowana.

Zmiany w routingu idą wyłącznie przez skrypt, który waliduje konfigurację, sprawdza unikalność aliasów, przeładowuje bramę, odpytuje wszystkie hosty i wycofuje zmianę, jeśli którykolwiek przestał odpowiadać. Ręczny reload omija każdy z tych kroków — dlatego go nie ma w procedurze.

Efekt

Koszt utrzymania spadł o rząd wielkości względem kilkunastu osobnych usług, wdrożenie trwa minuty i kończy się samo, a diagnostyka zadania cyklicznego to jedno kliknięcie w panelu zamiast szukania po logach dostawcy. Przy okazji całość da się odtworzyć na nowym serwerze z repozytorium i jednego skryptu.

Realizacje Napisz