Chcesz uruchomić aplikację w Node.js, ale nie masz ochoty na VPS z własną zaporą, certyfikatami
i aktualizacjami systemu? U nas aplikacja jest zwykłym procesem w Twoim kontenerze. Pilnuje go
systemd, a pod Twoją domenę wystawia go serwer WWW Apache. Zapory ani certyfikatów nie
konfigurujesz: wgrywasz kod i dodajesz aplikację w panelu. Poniżej cała droga od git clone
do strony pod Twoją domeną po HTTPS. Po drodze mówimy też, czego u nas nie zrobisz.
Czego potrzebujesz, zanim zaczniesz
Najpierw pakiet, bo od niego zależy, czy w ogóle zainstalujesz zależności.
- Pakiet z powłoką SSH. Node zaczyna się u nas od pakietu Standard. Powód jest techniczny:
zależności instalujesz komendą
npm cina serwerze, a Start ma tylko SFTP. Gotowy katalognode_modulesda się wgrać przez SFTP, tylko że każda zmiana zależności oznacza wtedy wysyłkę całego katalogu od nowa. Nie polecamy. - Kod, który nasłuchuje na porcie ze zmiennej
PORT. To jedyny warunek po stronie aplikacji. Skąd bierze się ten port, wyjaśnia krok 1.
Domena może poczekać. Aplikację dodasz też bez niej, do testów, i będzie wtedy działać tylko wewnętrznie.
Node jest w każdym kontenerze, w liniach 20, 22 i 24 z obrazu serwera. Domyślna to 22. Wersję wybierasz w panelu osobno dla każdej aplikacji, a lista do wyboru jest czytana z Twojego kontenera, więc nie wybierzesz wersji, której tam nie ma.
Krok 1: plik startowy i port
Tu obowiązuje konwencja, bez pól do wypełniania. Kod leży w katalogu ~/apps/<nazwa>, plik startowy
to server.js, a aplikacja nasłuchuje na porcie z process.env.PORT, na adresie lokalnym.
Numeru portu nie wybierasz: przydzielamy go przy dodawaniu aplikacji z puli od 3001 do 3099
na Twoim kontenerze i wpisujemy do środowiska procesu. Serwer WWW kieruje na ten port ruch
z domeny.
Najkrótszy server.js, który spełnia oba warunki:
import { createServer } from 'node:http';
const port = Number(process.env.PORT);
createServer((req, res) => {
res.setHeader('content-type', 'text/plain; charset=utf-8');
res.end('Działa: ' + req.url);
}).listen(port, '127.0.0.1', () => {
console.log('nasłuchuję na porcie ' + port);
});
Z Expressem mieści się to w jednej linii: app.listen(process.env.PORT, '127.0.0.1').
Frameworki, które same czytają PORT (Next.js, Fastify z konfiguracją, Nest), wymagają tylko
tego, żeby skrypt startowy nazywał się server.js albo żeby server.js je uruchamiał.
Dwie pułapki z pierwszych dni:
- Port wpisany na sztywno (
listen(3000)). Aplikacja wstanie, ale serwer WWW szuka jej na porcie z puli, więc pod domeną zobaczysz błąd bramy. Zawszeprocess.env.PORT. - Nasłuch na wszystkich interfejsach (
0.0.0.0). Niepotrzebny: ruch wchodzi do aplikacji wyłącznie przez serwer WWW w tym samym kontenerze, po127.0.0.1.
Krok 2: wgranie kodu i instalacja zależności przez SSH
Adres serwera, port i login znajdziesz w panelu, w sekcji SFTP swojej usługi. Tam też włączasz
powłokę, jeśli jeszcze jej nie masz. Logujesz się hasłem albo kluczem, działają oba sposoby.
W kontenerze czekają git, rsync, tmux i sqlite3, więc kod pobierzesz wprost
z repozytorium:
mkdir -p ~/apps && cd ~/apps
git clone ADRES_REPOZYTORIUM sklep
cd sklep
node -v # 22.x, domyślna linia z obrazu
npm ci --omit=dev # zależności produkcyjne z package-lock.json
npm run build # jeśli projekt ma krok budowania
Chcesz budować inną linią niż domyślna? Binarki wszystkich linii leżą w /opt/nodejs:
/opt/nodejs/24/bin/npm ci zainstaluje zależności pod Node 24. Proces uruchomi potem ta sama
wersja, którą wybierzesz w panelu, więc paczki natywne (na przykład sharp czy bcrypt)
zbudują się pod właściwe ABI.
Automatycznego budowania nie ma: nie reagujemy na wysłanie zmian do repozytorium i nie
kompilujemy kodu za Ciebie. Każde wdrożenie to git pull, npm ci, ewentualnie
npm run build i restart z panelu. Przy jednej aplikacji i kilku wdrożeniach w miesiącu to
trzy, cztery polecenia w terminalu i każde widzisz na ekranie. Wdrażasz kilka razy
dziennie? Zamknij te komendy w jednym skrypcie i uruchamiaj go przez SSH. Restart i tak
klikasz w panelu.
Krok 3: dodanie aplikacji w panelu
W usłudze otwórz „Aplikacje” i kliknij „Dodaj aplikację”. Formularz ma pięć pól:
| Pole | Co wpisać |
|---|---|
| Nazwa | małe litery, cyfry, myślnik. To także nazwa katalogu w ~/apps i nazwa procesu, na przykład sklep |
| Środowisko | Node.js |
| Wersja | 22 (zalecana), 20 albo 24 |
| Domena | jedna z domen usługi albo „Bez domeny (tylko wewnętrznie)” |
| Ścieżka | / = cała domena albo podścieżka w rodzaju /api |
Po zatwierdzeniu panel po kolei przydziela port, zapisuje plik środowiska z PORT
i NODE_ENV=production, tworzy usługę systemd redicon-app@sklep, wpina ją w domenę
i uruchamia proces. Potem czeka 5 sekund i sprawdza, czy proces się utrzymał. Jeśli nie,
dodanie kończy się błędem, a w komunikacie dostajesz ogon logu aplikacji. To jedyny ekran, na
którym dowiadujesz się, dlaczego Twój kod nie wstał, więc nie zasłaniamy go zielonym znaczkiem.
Najczęściej przy pierwszym dodaniu brakuje server.js w katalogu albo node_modules
(pominięty krok 2). Zdarza się też błąd w kodzie przy starcie, a każdy z tych powodów widać
w ogonie logu.
Krok 4: domena, HTTPS i websockety
Przed aplikacją stoi Apache w Twoim kontenerze. Przyjmuje ruch z domeny i przekazuje go na port
aplikacji, a odwiedzający widzi zwykłą stronę pod https://. Certyfikat Let’s Encrypt jest ten
sam, co dla stron PHP, i odnawia się sam. Wystawić go można dopiero wtedy, gdy domena wskazuje
już na nas. Do tego czasu przeglądarka może ostrzegać o certyfikacie, a my przez pierwszą
godzinę co pięć minut sprawdzamy, czy domena już do nas trafia.
Websockety przechodzą przez to samo wpięcie, bez osobnej konfiguracji.
Aplikacja może stać pod korzeniem domeny albo pod podścieżką. Przy /api serwer WWW zdejmuje
prefiks, więc Twój kod widzi /x, a nie /api/x. Żądanie GET /api bez ukośnika dostaje
przekierowanie na /api/. Na jednej domenie zmieści się kilka aplikacji, jeśli stoją na
rozłącznych ścieżkach (/api i /panel). Aplikacja pod / zajmuje całą domenę i druga już
się nie zmieści. Próba wpięcia nachodzącej ścieżki kończy się odmową z nazwą kolidującej
aplikacji.
Co robi systemd, a czego nie robi
Aplikację pilnuje systemd, jak każdą inną usługę systemu. pm2 nie stawiamy, bo robiłby to samo, co system robi już bez niego. Co z tego wynika w dniu, w którym coś padnie:
Wstaje po restarcie serwera i po własnym upadku. Po awarii system podnosi proces z rosnącym odstępem: 2, 4, 8, 15, 30 i wreszcie 60 sekund między próbami. Odstęp rośnie celowo. Przyczyną bywa coś po naszej stronie, na przykład restart wspólnej bazy, i wtedy aplikacja ma wstać sama, gdy tylko baza wróci.
Trwała awaria kończy się stanem „nie działa”. Limit to 12 prób w oknie 15 minut. Potem
system przestaje podnosić proces, panel pokazuje „nie działa” i ogon logu, a Ty naprawiasz
kod i klikasz „Restartuj”. Ten przycisk robi więcej niż systemctl restart: zeruje licznik
nieudanych startów i po 5 sekundach sprawdza, czy proces naprawdę stoi.
Pojedynczy proces ma sufit pamięci: 60% RAM kontenera. Reszta zostaje dla serwera WWW i PHP, żeby jedna aplikacja nie położyła strony stojącej obok niej na tym samym koncie. Liczby dla każdego pakietu podaje strona o hostingu Node.js.
Logi procesu masz w panelu. Przycisk „Logi” przy aplikacji pokazuje ostatnie linie
wypisane przez proces (console.log, stack trace). Access log i error log Apache są gdzie
indziej, na ekranie logów domeny. Potrzebujesz dłuższej historii? Pisz logi aplikacji także
do własnego pliku w jej katalogu. To Twój katalog i Twoja rotacja.
Zmienne środowiskowe, klucze API i baza
Plik środowiska usługi (PORT, NODE_ENV) odtwarzamy przy każdej zmianie ustawień
aplikacji, więc Twoje dopiski zniknęłyby z niego w najmniej spodziewanym momencie. Własne
zmienne trzymaj w pliku swojej aplikacji, na przykład .env w katalogu ~/apps/sklep,
wczytywanym na starcie przez dotenv albo process.loadEnvFile(). Katalog jest Twój i poza
Tobą nikt do niego nie zagląda.
Bazę zakładasz w panelu, MariaDB albo PostgreSQL do wyboru przy tworzeniu. Z internetu baz
nie wystawiamy. Aplikacja łączy się z nimi z Twojego kontenera, a Ty z przeglądarki, przez
panel. Dane dostępowe wpisujesz do swojego .env.
Zmiana wersji Node
Przy aplikacji w panelu przycisk „Edytuj” otwiera okno „Zmień środowisko aplikacji”.
Przełączasz w nim wersję, a proces startuje na nowo z nowym interpreterem. Paczki natywne
w node_modules są skompilowane pod ABI konkretnej linii, więc po zmianie wykonaj przez SSH
npm rebuild (albo rm -rf node_modules && /opt/nodejs/24/bin/npm ci). Panel przypomina
o tym przed zatwierdzeniem.
Linia 20 zakończyła cykl wsparcia 30 kwietnia 2026 i nie dostaje już poprawek bezpieczeństwa. Trzymamy ją, żeby starsze aplikacje w ogóle wstały. Nową stawiaj na 22 albo 24.
Czego tu nie ma
- Dockera. Nie uruchamiamy Twojego
Dockerfile. Aplikacja startuje jako zwykły proces z katalogu, który wgrywasz. - Budowania po wysłaniu zmian do repozytorium. Instalacja i build to Twoje komendy przez SSH.
- Memcached ani Redisa po TCP. Redisa włączasz w panelu, ale słucha wyłącznie na gnieździe uniksowym w Twoim kontenerze, nie na 127.0.0.1:6379. Biblioteka, która umie tylko TCP, się nie połączy. Memcached nie ma.
- Wersji Node spoza obrazu w roli runtime. Inną linię zainstalujemy Ci na życzenie w katalogu domowym do pracy przez SSH, ale aplikacja prowadzona z panelu chodzi wyłącznie na wersjach z obrazu (20, 22, 24).
Lista kontrolna przed pierwszym uruchomieniem
- pakiet z powłoką SSH (Standard albo wyżej), powłoka włączona w panelu,
- kod w
~/apps/<nazwa>, plik startowyserver.js, - nasłuch na
process.env.PORTi127.0.0.1, npm ciwykonane tą samą linią Node, którą wybierzesz w panelu,- własne zmienne w pliku aplikacji, nie w pliku środowiska usługi,
- domena podpięta do usługi i wskazująca na nas, jeśli aplikacja ma być publiczna.
Któryś punkt nie pasuje do Twojego projektu? Napisz do nas przed zamówieniem. Odpiszemy, czy i jak to u nas wstanie, a jeśli nie wstanie, powiemy to wprost.