Railway, czyli infrastruktura bez konfigurowania infrastruktury
Railway zajmuje miejsce między hostingiem dla stron a pełną chmurą. Podłączasz repozytorium, platforma sama wykrywa technologię, buduje obraz i uruchamia usługę, a bazę danych dokładasz jednym kliknięciem.
Różnica wobec platform nastawionych na frontend polega na tym, że tutaj uruchamiasz procesy długo działające: serwer w dowolnym języku, zadanie w tle, konsumenta kolejki. Nie ma podziału na funkcje i statyczne pliki, jest kontener działający tak długo, jak trzeba.
Różnica wobec pełnej chmury polega na tym, czego nie musisz robić. Nie ma sieci wirtualnych, grup bezpieczeństwa, ról ani konfiguracji równoważenia obciążenia. Jest repozytorium, zmienne środowiskowe i adres, pod którym działa aplikacja.
Model rozliczeń, czyli rzecz do zrozumienia na starcie
Tu leży największa różnica wobec konkurencji i zarazem najczęstsza przyczyna nieprzyjemnych niespodzianek.
Nie płacisz za plan z przydzielonymi zasobami, tylko za rzeczywiste zużycie procesora, pamięci, dysku i ruchu wychodzącego, naliczane sekundowo. Usługa stojąca bezczynnie kosztuje ułamek tego, co usługa pod obciążeniem.
Plan podstawowy kosztuje pięć dolarów miesięcznie i zawiera w cenie zużycie o tej samej wartości. Plan zespołowy to dwadzieścia dolarów miesięcznie za przestrzeń roboczą, również z kredytem równym abonamentowi. Powyżej wliczonego progu płacisz za faktyczne zużycie po tych samych stawkach: około czternastu centów za gigabajtogodzinę pamięci, około dwudziestu ośmiu centów za godzinę rdzenia i pięć centów za gigabajt ruchu wychodzącego. Rozliczenie idzie co sekundę, więc usługa wyłączona na noc faktycznie nie kosztuje.
Stałego darmowego progu nie ma. Nowe konto dostaje jednorazowy kredyt próbny na krótki okres, a dalsza praca wymaga planu płatnego. To warto wiedzieć przed obiecaniem komuś darmowego środowiska testowego.
Ten model ma jedną zaletę i jedną wadę, obie warte nazwania. Zaletą jest przewidywalna oszczędność przy obciążeniu nierównomiernym: środowiska testowe, narzędzia wewnętrzne i usługi używane w godzinach pracy kosztują wyraźnie mniej niż przy stałej opłacie za maszynę. Wadą jest brak górnego ograniczenia, jeśli sam go nie ustawisz.
Limit wydatków ustaw przed pierwszym wdrożeniem. Pętla w kodzie, zapytanie bez indeksu albo zadanie uruchamiane częściej, niż zakładałeś, potrafią zamienić rachunek na dziesięć dolarów w rachunek na trzysta, a limit jest jedynym mechanizmem działającym bez Twojej uwagi.
Wdrożenie i budowanie
Podłączenie repozytorium wystarcza do pierwszego uruchomienia, bo system budowania rozpoznaje popularne technologie sam.
railway login
railway init
railway upWykrywanie działa dobrze przy typowych projektach i przestaje wystarczać przy nietypowych. Wtedy sięgasz po własny plik opisujący obraz, i to jest właściwa droga, bo daje pełną kontrolę i przenosi się na inne platformy.
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
RUN npm run build
CMD ["node", "dist/server.js"]Kilka rzeczy warto ustawić od początku, bo później poprawia się je niechętnie. Wszystkie mieszczą się w jednym pliku konfiguracyjnym trzymanym w repozytorium obok kodu.
{
"$schema": "https://railway.com/railway.schema.json",
"build": {
"builder": "DOCKERFILE",
"dockerfilePath": "Dockerfile",
"watchPatterns": ["src/**", "package.json"]
},
"deploy": {
"startCommand": "node dist/server.js",
"healthcheckPath": "/health",
"healthcheckTimeout": 30,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 3
}
}Sprawdzenie stanu usługi pozwala platformie odróżnić proces uruchomiony od procesu gotowego przyjmować ruch. Bez niego wdrożenie kończy się sukcesem, mimo że aplikacja jeszcze nie wstała, a użytkownicy widzą błąd przez kilkanaście sekund. Sama trasa może być banalna, byle odpowiadała dopiero wtedy, gdy zależności są podpięte.
app.get('/health', async (_req, res) => {
try {
await db.query('SELECT 1')
res.status(200).json({ status: 'ok' })
} catch {
res.status(503).json({ status: 'degraded' })
}
})Port odczytuj ze zmiennej środowiskowej, a nie na sztywno. Platforma przydziela go sama i wpisany na stałe numer działa lokalnie, a na produkcji kończy się usługą nieodpowiadającą na żaden ruch. Nasłuchiwać trzeba też na wszystkich interfejsach, bo domyślne wiązanie z pętlą zwrotną odcina ruch z zewnątrz kontenera.
const port = Number(process.env.PORT) || 3000
app.listen(port, '0.0.0.0', () => {
console.log(JSON.stringify({ level: 'info', msg: 'server up', port }))
})Zmienne środowiskowe dziel na środowiska. Klucz produkcyjny wpisany do środowiska testowego to najczęstsza przyczyna sytuacji, w której test wysyła prawdziwe wiadomości do prawdziwych klientów.
railway variable set STRIPE_KEY=sk_test_...
railway variable list
railway run npm startOstatnie polecenie jest tu najbardziej użyteczne na co dzień: uruchamia proces lokalnie, ale ze zmiennymi pobranymi z wybranego środowiska, więc nie musisz trzymać drugiej kopii konfiguracji w pliku lokalnym.
Bazy danych i wolumeny
Baza uruchamia się z gotowego szablonu, a adres połączenia pojawia się jako zmienna środowiskowa dostępna dla pozostałych usług w projekcie.
railway add --database postgresPo dodaniu usługi adres połączenia czytasz w kodzie jak każdą inną zmienną, bez wpisywania hosta i hasła gdziekolwiek w repozytorium.
import { Pool } from 'pg'
export const db = new Pool({
connectionString: process.env.DATABASE_URL,
max: 10,
idleTimeoutMillis: 30_000
})Rozmiar puli połączeń warto ustawić świadomie, bo to najczęstsza przyczyna wyczerpania limitu po stronie bazy. Liczba instancji pomnożona przez rozmiar puli musi zmieścić się w limicie połączeń bazy, a wartość domyślna w wielu bibliotekach jest na to za wysoka.
To wygodne i warto rozumieć, co dostajesz. Baza działa w kontenerze, z danymi na wolumenie trwałym, więc przetrwa restart i wdrożenie. Nie ma natomiast automatycznego przełączania przy awarii ani replik do odczytu, więc to jest rozwiązanie dla aplikacji, które zniosą kilkuminutową przerwę.
Kopie zapasowe zaplanuj samodzielnie, zanim będą potrzebne, i sprawdź ich odtworzenie na osobnym środowisku. Kopia, której nikt nigdy nie odtworzył, jest kopią wyłącznie z nazwy, a to zdanie kosztowało już wiele zespołów bardzo dużo.
Przy poważniejszych wymaganiach warto rozważyć bazę u wyspecjalizowanego dostawcy, na przykład opisaną w tekstach o Neonie albo PlanetScale, i zostawić tutaj samą aplikację. Zyskujesz gałęzie bazy, kopie w standardzie i mechanizmy odporności na awarie, kosztem drugiego rachunku.
Wolumeny przydają się też poza bazami: do plików wgrywanych przez użytkowników, pamięci podręcznej i danych roboczych. Warto natomiast pamiętać, że wolumen przypina usługę do jednej instancji, więc skalowanie poziome przestaje być możliwe.
To ograniczenie ma prostą konsekwencję projektową. Pliki wgrywane przez użytkowników lepiej od początku kierować do magazynu obiektowego, nawet jeśli wolumen wydaje się prostszy. Przeniesienie ich później wymaga migracji danych i zmiany w kodzie, a wolumen w międzyczasie blokuje możliwość uruchomienia drugiej instancji akurat wtedy, gdy zaczyna być potrzebna.
Warto natomiast znać drugą stronę tej zależności. Część gotowych zapleczy jest wręcz zaprojektowana wokół jednego pliku z danymi, jak PocketBase, które mieści bazę, uwierzytelnianie i pliki w jednym programie. Taka usługa wdraża się tutaj w kilka minut i korzysta z wolumenu zgodnie z przeznaczeniem, pod warunkiem że przyjmujesz jedną instancję i skalowanie wyłącznie w górę.
Środowiska i praca zespołowa
Projekt może mieć kilka środowisk, a każde z nich to osobny zestaw usług z własnymi zmiennymi i własną bazą. To rozwiązuje problem, który przy wielu platformach trzeba budować samodzielnie.
railway environment new staging
railway environment
railway logs --buildUstawienia da się nadpisywać per środowisko w tym samym pliku konfiguracyjnym, co pozwala trzymać różnice w repozytorium zamiast w cudzej głowie. Osobny wpis pr obsługuje środowiska powstające dla zgłoszeń scalenia.
{
"$schema": "https://railway.com/railway.schema.json",
"deploy": { "startCommand": "node dist/server.js" },
"environments": {
"staging": {
"deploy": { "startCommand": "node dist/server.js --seed" }
},
"pr": {
"deploy": { "healthcheckTimeout": 90 }
}
}
}Najczęściej stosowany układ obejmuje trzy środowiska: produkcyjne, testowe i osobne dla każdej gałęzi kodu wymagającej podglądu. To trzecie warto włączyć, bo przegląd zmian na działającej wersji jest nieporównanie skuteczniejszy niż czytanie kodu i zgadywanie. Nie każdy powód wymaga jednak osobnego środowiska: żeby pokazać komuś zmianę na kwadrans albo odebrać webhooka z zewnętrznej usługi, wystarczy tunel z localhosta w rodzaju tunnl.gg, stawiany jedną komendą SSH bez konta i bez instalacji. Do niczego trwalszego się nie nadaje, bo tunel wygasa po dobie, subdomena jest losowa, a ruch jest odszyfrowywany na cudzym serwerze.
Rzecz do rozstrzygnięcia od początku dotyczy danych w środowiskach pobocznych. Kopia bazy produkcyjnej daje realistyczne testy i wprowadza dane osobowe do miejsca o słabszych zabezpieczeniach. Zestaw danych wygenerowanych sztucznie jest bezpieczny i mniej wiarygodny. Trzecia droga, zwykle najlepsza, to kopia produkcyjna z zamaskowanymi danymi wrażliwymi, przygotowywana skryptem uruchamianym przy tworzeniu środowiska.
Warto też pilnować sprzątania. Środowiska podglądowe powstają automatycznie razem z gałęzią i zostają po jej scaleniu, jeśli nikt ich nie usunie, a każde nalicza zużycie. Powiązanie usunięcia środowiska z zamknięciem zgłoszenia w repozytorium rozwiązuje to raz i nie polega na niczyjej pamięci.
Monitorowanie i diagnostyka
Platforma pokazuje dzienniki i podstawowe wykresy zużycia, i to wystarcza do momentu, w którym coś zaczyna działać niepoprawnie w sposób nieoczywisty.
Trzy rzeczy warto dołożyć wcześnie. Pierwsza to dzienniki w formacie strukturalnym zamiast zwykłego tekstu. Wpis zawierający identyfikator żądania, poziom i czas da się filtrować, a zdanie napisane po ludzku nadaje się wyłącznie do czytania w kolejności.
import pino from 'pino'
export const log = pino({
level: process.env.LOG_LEVEL ?? 'info',
base: { service: 'api', env: process.env.RAILWAY_ENVIRONMENT_NAME }
})
log.info({ requestId, userId, durationMs: 142 }, 'zamowienie utworzone')Dopisanie nazwy środowiska do każdego wpisu kosztuje jedną linijkę, a oszczędza godzinę przy pierwszej sytuacji, w której okaże się, że patrzysz na dzienniki z niewłaściwego środowiska.
Druga to śledzenie błędów w osobnym narzędziu. Dzienniki pokazują, że coś się wydarzyło, a narzędzie do błędów pokazuje, ile razy, u ilu użytkowników i od którego wdrożenia, co jest zupełnie inną informacją.
Trzecia to obserwowanie zużycia w powiązaniu z ruchem. Wykres pamięci rosnący liniowo mimo stałego ruchu oznacza wyciek, a to jest jedyny sposób, żeby zauważyć go przed restartem usługi w środku dnia.
Warto również ustawić powiadomienie o nieudanym wdrożeniu. Domyślnie wdrożenie, które się nie powiodło, po prostu nie zastępuje poprzedniej wersji, więc aplikacja działa dalej i nikt nie zauważa problemu, dopóki ktoś nie sprawdzi, dlaczego poprawka sprzed dwóch dni nadal nie działa.
Railway kontra alternatywy
| Rozwiązanie | Mocna strona | Słabość | Kiedy wybrać |
|---|---|---|---|
| Railway | Procesy długo działające, bazy w komplecie, rozliczenie sekundowe | Brak darmowego progu, rachunek bez limitu, jeśli go nie ustawisz | Backend, zadania w tle, narzędzia wewnętrzne |
| Vercel | Najlepsze wsparcie dla frontendu i renderowania na krawędzi | Procesy długo działające poza głównym przeznaczeniem | Aplikacja na Next.js |
| Kontener u dostawcy chmury | Pełna kontrola, najniższa cena przy dużej skali | Konfiguracja i utrzymanie po Twojej stronie | Zespół z osobą od infrastruktury |
| Własny serwer | Najtańszy przy stałym obciążeniu | Wszystko utrzymujesz sam | Stałe, przewidywalne obciążenie |
Pierwsze kryterium to rodzaj uruchamianego procesu. Jeśli aplikacja to serwer działający ciągle, konsument kolejki albo zadanie cykliczne, pierwszy wiersz pasuje lepiej niż platformy zaprojektowane wokół funkcji uruchamianych na żądanie.
Drugie to obciążenie w czasie. Przy ruchu nierównomiernym rozliczenie sekundowe wychodzi taniej niż stała opłata za maszynę. Przy obciążeniu stałym i wysokim proporcja się odwraca i własny serwer albo kontener u dostawcy chmury bywa kilkukrotnie tańszy.
Trzecie to liczba osób, które mają to utrzymywać. Zespół bez osoby od infrastruktury zyskuje tu najwięcej, bo cała warstwa konfiguracji po prostu nie istnieje i nikt nie musi się jej uczyć.
Jak realnie obniżyć rachunek
Skoro rozliczenie idzie za zużycie, warto wiedzieć, co je napędza, bo intuicja myli się tu regularnie.
Pamięć kosztuje zwykle więcej niż procesor, bo usługa trzyma ją zajętą przez cały czas działania, podczas gdy procesor pracuje tylko przy żądaniach. Aplikacja w środowisku uruchomieniowym z odśmiecaniem potrafi zajmować kilkaset megabajtów bez powodu, a ustawienie górnego limitu sterty często obniża to o połowę bez żadnego wpływu na działanie.
Ruch wychodzący jest drugą pozycją, o której się nie myśli. Obrazy i pliki serwowane bezpośrednio z aplikacji przechodzą przez tę pozycję, a przeniesienie ich do magazynu obiektowego z siecią dostarczania treści zdejmuje ją niemal w całości.
Środowiska poboczne to trzecia pozycja i zwykle najłatwiejsza do ograniczenia. Baza w środowisku testowym działa całą dobę, choć nikt jej nie używa poza godzinami pracy. Wyłączanie środowisk pobocznych na noc to zmiana, która potrafi zbić rachunek o jedną trzecią.
Czwarta to usługi zapomniane. Projekt sprzed roku, prototyp, który miał działać tydzień, baza pozostała po migracji, z której już nie korzystasz. Przegląd listy usług raz na kwartał zajmuje kwadrans i regularnie znajduje coś, co naliczało się przez wiele miesięcy.
Warto też sprawdzić, czy aplikacja faktycznie potrzebuje działać ciągle. Zadanie uruchamiane raz na dobę nie musi stać jako usługa czekająca w gotowości, tylko może być zadaniem cyklicznym, które startuje, wykonuje pracę i kończy się.
Typowe błędy
Pierwszy to brak limitu wydatków. Rozliczenie za zużycie nie ma górnej granicy, więc pętla w kodzie potrafi wygenerować rachunek nieproporcjonalny do skali projektu.
Drugi to port wpisany na sztywno w kodzie. Platforma przydziela go zmienną środowiskową, a stała wartość działa lokalnie i zawodzi zaraz po wdrożeniu.
Trzeci to brak sprawdzenia stanu usługi. Wdrożenie kończy się sukcesem, zanim aplikacja wstanie, więc użytkownicy widzą błąd przez kilkanaście sekund przy każdym wdrożeniu.
Czwarty to poleganie na wbudowanej bazie bez własnych kopii zapasowych. Zaplanuj je i sprawdź odtworzenie, zanim będą potrzebne.
Piąty to mieszanie zmiennych środowiskowych między środowiskami. Klucz produkcyjny w środowisku testowym to sytuacja, którą zauważa się po fakcie.
Szósty to zakładanie darmowego progu w planach. Kredyt próbny jest jednorazowy i krótkoterminowy, więc koszt trzeba przewidzieć od początku projektu, a nie po wyczerpaniu kredytu.
Siódmy to zostawianie środowisk podglądowych po scaleniu gałęzi. Powstają automatycznie, nie znikają automatycznie i każde nalicza zużycie tak długo, aż ktoś je zauważy.
FAQ
Czy Railway ma darmowy plan?
Nie w sensie stałego darmowego progu. Nowe konto dostaje pięć dolarów kredytu próbnego na trzydzieści dni, bez podawania karty, a dalsza praca wymaga planu płatnego. Plan podstawowy kosztuje pięć dolarów miesięcznie i zawiera w cenie zużycie o tej samej wartości.
Jak wygląda rozliczenie?
Za faktyczne zużycie procesora, pamięci, dysku i ruchu wychodzącego, naliczane sekundowo. Usługa bezczynna kosztuje ułamek tego, co obciążona, więc przy nierównomiernym ruchu wychodzi taniej niż stała opłata za maszynę. Limit wydatków warto ustawić przed pierwszym wdrożeniem.
Czym różni się od platform dla frontendu?
Rodzajem uruchamianych procesów. Tutaj działa kontener tak długo, jak trzeba, więc serwer, konsument kolejki i zadanie cykliczne są naturalne. Platformy takie jak Vercel są zaprojektowane wokół funkcji uruchamianych na żądanie i renderowania stron.
Czy wbudowana baza wystarczy na produkcji?
Do aplikacji, które zniosą kilkuminutową przerwę, tak. Nie ma automatycznego przełączania przy awarii ani replik do odczytu, więc przy wyższych wymaganiach warto rozważyć wyspecjalizowanego dostawcę bazy i zostawić tu samą aplikację.
Czy da się skalować poziomo?
Tak, o ile usługa nie korzysta z wolumenu, bo ten przypina ją do jednej instancji. Stan trzymaj w bazie albo w magazynie zewnętrznym, a wtedy zwiększenie liczby instancji jest zmianą jednego ustawienia.
Dokumentacja stoi na stronie projektu, a szczegóły planów opisuje strona cenowa.