Jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node
- Jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node
- Po co w ogóle Docker? Krótkie wprowadzenie
- Przygotowanie projektu i wymagane narzędzia
- Dockerfile dla backendu Node.js – pierwszy kontener
- Multi-stage Dockerfile dla frontendu React
- Konfiguracja Nginx dla aplikacji React i proxy do backendu
- Docker Compose – łączenie frontendu i backendu
- Budowanie i uruchamianie kontenerów krok po kroku
- Typowe problemy i kolejne kroki z Dockerem
- Podsumowanie i dalszy rozwój projektu w Dockerze
Jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node
Zastanawiasz się, jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node w jednym projekcie bez znajomości Dockera? To częste pytanie, szczególnie gdy chcesz po prostu uruchomić swoją aplikację w kontenerach, a nie zostać od razu specjalistą od DevOps. Wiele materiałów zakłada już pewną wiedzę, przez co łatwo się zniechęcić.
Poniżej znajdziesz kompletną, praktyczną instrukcję krok po kroku. Nauczysz się, jak przygotować Dockerfile dla backendu Node.js, jak zbudować wydajny obraz frontendu React, a na koniec – jak połączyć oba serwisy za pomocą Docker Compose. Wszystko w jednym projekcie, w formie, którą możesz od razu skopiować i zastosować.
Po przeczytaniu tego przewodnika Twoja aplikacja przestanie mówić „u mnie działa”, a zacznie działać tak samo wszędzie – na Twoim laptopie, u kolegi i na serwerze. Bez magii, za to z dobrze przygotowanymi obrazami Dockera.

Po co w ogóle Docker? Krótkie wprowadzenie
Wyobraź sobie sytuację: Twoja aplikacja działa idealnie na Twoim komputerze. Wysyłasz kod koledze, a on po chwili pisze, że nic mu nie działa i widzi dziwny błąd. To klasyczny problem „u mnie działa”, wynikający z różnic w środowisku, wersjach bibliotek czy systemu.
Docker rozwiązuje ten problem, pakując aplikację wraz z całym środowiskiem uruchomieniowym w kontener. Taki kontener zawiera system, biblioteki, Node.js, Nginx i wszystko, czego potrzebuje Twoja aplikacja. Uruchomiony obraz Dockera zachowuje się identycznie na każdym komputerze, niezależnie od systemu hosta.
W praktyce pojęcia, które musisz znać, to:
- Obraz Dockera (image) – szablon opisujący, co ma być w kontenerze
- Kontener – działająca instancja obrazu
- Dockerfile – instrukcja, jak zbudować obraz
- Docker Compose – narzędzie do uruchamiania wielu kontenerów jako jednej aplikacji
Dzięki temu zyskujesz powtarzalność, izolację, łatwiejsze wdrażanie i dużo większą kontrolę nad środowiskiem. Kontenery są przy tym lżejsze niż maszyny wirtualne, więc startują szybciej i zużywają mniej zasobów.
Przygotowanie projektu i wymagane narzędzia
Zanim zaczniesz pisać pierwszy Dockerfile, warto uporządkować strukturę kodu. Załóżmy, że masz monorepo, w którym frontend i backend znajdują się w oddzielnych katalogach. To typowy i wygodny układ do konteneryzacji.
Przykładowa struktura projektu może wyglądać tak:
moj-super-projekt/
├── backend/
│ ├── src/
│ ├── node_modules/
│ ├── package.json
│ ├── package-lock.json
│ └── ... (pliki backendu Node.js)
├── frontend/
│ ├── public/
│ ├── src/
│ ├── node_modules/
│ ├── package.json
│ ├── package-lock.json
│ └── ... (pliki frontendu React)
├── .gitignore
├── docker-compose.yml
└── README.md
Do pracy z Dockerem w takim projekcie potrzebujesz kilku podstawowych elementów:
- Docker Desktop – zainstalowany i uruchomiony na Twoim systemie (Windows, macOS lub Linux).
- Edytor kodu – np. VS Code, WebStorm lub inny ulubiony edytor.
- Działająca aplikacja React + Node.js – tak skonfigurowana, aby
npm installinpm startw katalogachfrontendorazbackenddziałały poprawnie.
Gdy projekt jest gotowy, a narzędzia zainstalowane, możesz przejść do „dockerizacji” backendu i frontendu oraz przygotowania pliku docker-compose.yml, który wszystko połączy.
Dockerfile dla backendu Node.js – pierwszy kontener
Zaczniesz od backendu Node.js, ponieważ jego konteneryzacja jest prostsza niż w przypadku Reacta. Cała logika sprowadza się do zainstalowania zależności, skopiowania kodu i uruchomienia serwera w odpowiednim porcie.
W katalogu backend/ utwórz plik Dockerfile:
# backend/Dockerfile
# 1. Obraz bazowy – oficjalny Node.js 18 na Alpine (lekka dystrybucja)
FROM node:18-alpine
# 2. Katalog roboczy wewnątrz kontenera
WORKDIR /app
# 3. Kopiujemy pliki z zależnościami
COPY package*.json ./
# 4. Instalujemy zależności w trybie CI (deterministycznie)
RUN npm ci
# 5. Kopiujemy resztę kodu backendu
COPY . .
# 6. Deklarujemy port, na którym backend nasłuchuje
EXPOSE 3001
# 7. Domyślne polecenie startowe
CMD ["npm", "start"]
Ten Dockerfile opiera się na kilku zasadach, które warto zapamiętać:
- Cache warstw – osobne kopiowanie
package*.jsonpozwala uniknąć ponownej instalacji zależności, gdy zmienia się tylko kod źródłowy. npm ci– gwarantuje instalację dokładnie tych wersji, które są wpackage-lock.json, co jest idealne do powtarzalnych buildów.EXPOSE 3001– odpowiada portowi, na którym nasłuchuje Twój serwer Node (np.process.env.PORT || 3001).
Dodatkowo w katalogu backend/ utwórz plik .dockerignore, aby nie kopiować zbędnych plików:
# backend/.dockerignore
node_modules
npm-debug.log
.git
.env
Dzięki temu obraz Docker dla backendu będzie lżejszy, a budowanie – szybsze i bardziej przewidywalne.
Multi-stage Dockerfile dla frontendu React
Frontend React wymaga osobnego podejścia, ponieważ najpierw musisz zbudować statyczne pliki, a dopiero potem je serwować. Do budowy możesz użyć Node.js, ale do serwowania lepiej wykorzystać lekki serwer HTTP, np. Nginx. Zrobisz to przy pomocy multi-stage build w jednym Dockerfile.
W katalogu frontend/ utwórz plik Dockerfile:
# frontend/Dockerfile
# Etap 1: builder – budowanie aplikacji React
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Etap 2: runner – serwowanie zbudowanej aplikacji za pomocą Nginx
FROM nginx:alpine
COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
W pierwszym etapie używasz Node.js do zbudowania Reacta (npm run build), co tworzy zoptymalizowany katalog build. Drugi etap zaczyna się od nowego obrazu nginx:alpine, do którego kopiujesz tylko gotowe statyczne pliki. Node i narzędzia budujące zostają w poprzednim etapie, więc finalny obraz jest znacznie mniejszy.
W katalogu frontend/ dodaj też .dockerignore:
# frontend/.dockerignore
node_modules
build
.git
.env
Tutaj ignorujesz również katalog build, ponieważ będzie tworzony wewnątrz kontenera, a nie kopiowany z Twojego hosta. Dzięki temu zachowujesz czysty, powtarzalny proces budowania frontendu React.
Konfiguracja Nginx dla aplikacji React i proxy do backendu
Aby Nginx poprawnie serwował aplikację React i przekazywał zapytania API do backendu Node, potrzebujesz pliku konfiguracyjnego. Będzie on obsługiwał zarówno statyczne pliki, jak i routing typowy dla SPA (Single Page Application).
W katalogu frontend/ utwórz podkatalog nginx, a w nim plik nginx.conf:
# frontend/nginx/nginx.conf
server {
listen 80;
root /usr/share/nginx/html;
index index.html index.htm;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://backend:3001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
}
Ta konfiguracja robi kilka istotnych rzeczy:
location /+try_files– wszystkie nieznalezione ścieżki przekierowuje doindex.html. Dzięki temu React Router może obsługiwać routing po stronie klienta bez błędu 404 przy odświeżeniu.location /api– przekazuje wszystkie żądania rozpoczynające się od/apido usługibackendna porcie3001. Nazwabackendbędzie nazwą serwisu w Docker Compose, więc kontenery mogą komunikować się po nazwie.
Jeśli w React wysyłasz zapytania np. na /api/users, Nginx odbierze je na porcie 80 (zmapowanym na 3000 na hoście), a następnie przekaże je do http://backend:3001/api/users wewnątrz sieci dockera.

Docker Compose – łączenie frontendu i backendu
Masz już dwa Dockerfile – dla backendu Node i frontendu React. Teraz czas je połączyć w jedną, spójną aplikację przy pomocy Docker Compose. To narzędzie pozwala zdefiniować w jednym pliku kilka usług (kontenerów) oraz opisać ich zależności, sieci i mapowanie portów.
W głównym katalogu projektu (moj-super-projekt/) utwórz plik docker-compose.yml:
# docker-compose.yml
version: '3.8'
services:
backend:
build:
context: ./backend
dockerfile: Dockerfile
ports:
- "3001:3001"
volumes:
- ./backend:/app
- /app/node_modules
environment:
NODE_ENV: development
PORT: 3001
networks:
- app-network
frontend:
build:
context: ./frontend
dockerfile: Dockerfile
ports:
- "3000:80"
depends_on:
- backend
networks:
- app-network
networks:
app-network:
driver: bridge
Najważniejsze elementy tej konfiguracji to:
services.backend– buduje obraz z./backend/Dockerfile, wystawia port 3001 na hoście i montuje katalog./backendjako/app. Dodatkowy wolumen/app/node_moduleszapewnia, że kontener używa swoich własnych zależności, a nie tych z hosta.services.frontend– buduje obraz z./frontend/Dockerfilei mapuje port 80 w kontenerze Nginx na port 3000 na hoście. Dzięki temu aplikacja React jest dostępna podhttp://localhost:3000.depends_on– określa, że frontend zależy od backendu, więc Docker Compose uruchomi backend jako pierwszy.networks.app-network– wspólna sieć, w której działają oba serwisy. Dzięki temu możesz używać adresuhttp://backend:3001w konfiguracji Nginx zamiast twardo zakodowanych IP.
Taka konfiguracja Docker Compose tworzy prostą, ale kompletną orkiestrację: backend Node.js, frontend React serwowany przez Nginx i wspólna sieć umożliwiająca komunikację między nimi.
Budowanie i uruchamianie kontenerów krok po kroku
Gdy masz przygotowane oba Dockerfile i plik docker-compose.yml, możesz zbudować obrazy i uruchomić całą aplikację jednym poleceniem. Poniżej znajdziesz krótki przewodnik po komendach, których będziesz najczęściej używać.
- Budowanie obrazów dla wszystkich serwisów
W głównym katalogu projektu (moj-super-projekt/) uruchom:
bash
docker-compose build
Docker pobierze obrazy bazowe, zainstaluje zależności i zbuduje aplikację React. Przy pierwszym uruchomieniu może to potrwać dłużej, później zadziała cache warstw i kolejne buildy będą dużo szybsze.
- Uruchamianie kontenerów
Po zbudowaniu obrazów wystartuj oba serwisy:
bash
docker-compose up
To polecenie uruchomi backend Node i frontend Nginx, a logi z obu kontenerów pojawią się w terminalu. Jeśli chcesz uruchomić kontenery w tle, możesz użyć wersji:
bash
docker-compose up -d
- Sprawdzanie działających kontenerów
Aby upewnić się, że wszystko działa poprawnie, wykonaj:
bash
docker ps
Powinieneś zobaczyć dwa działające kontenery – jeden dla backendu, drugi dla frontendu. W kolumnie PORTS znajdziesz informacje o mapowaniu portów, np. 0.0.0.0:3000->80/tcp dla frontendu.
-
Dostęp do aplikacji
-
Frontend React (serwowany przez Nginx):
http://localhost:3000 - Backend Node.js:
http://localhost:3001
Jeżeli frontend wysyła zapytania pod adresy /api/..., Nginx automatycznie przekaże je do backend:3001 wewnątrz sieci Docker, zgodnie z konfiguracją w nginx.conf.
- Zatrzymywanie i usuwanie kontenerów
Jeśli uruchomiłeś docker-compose up w trybie „foreground”, zatrzymasz kontenery skrótem Ctrl + C. Gdy kontenery działają w tle (-d), użyj:
bash
docker-compose down
To polecenie zatrzyma i usunie kontenery oraz sieć, ale pozostawi zbudowane obrazy, by kolejne uruchomienie było szybsze.
Typowe problemy i kolejne kroki z Dockerem
Po skonfigurowaniu pierwszego projektu w Dockerze możesz napotkać kilka typowych wyzwań. Warto je znać, aby szybciej diagnozować problemy i rozwijać konfigurację o kolejne elementy, takie jak bazy danych czy CI/CD.
CORS a komunikacja frontend–backend
Jeśli frontend i backend działają na różnych portach (np. 3000 i 3001), przeglądarka traktuje je jako różne źródła. Może to powodować błędy CORS przy zapytaniach AJAX. Masz dwa główne rozwiązania:
- Skonfigurować middleware CORS w backendzie Node.js, aby zezwalał na połączenia z
http://localhost:3000. - Skorzystać z Nginx jako reverse proxy, tak jak w tym przewodniku. Wtedy frontend i backend są widoczne z przeglądarki jako jeden host (np.
localhost:3000), a problem CORS znika.
W środowisku produkcyjnym drugi wariant jest zazwyczaj wygodniejszy i bardziej elastyczny.
Zmienne środowiskowe i pliki .env
W projektach Node i React często używasz plików .env. W kontenerach Dockera masz kilka opcji przekazywania konfiguracji:
- Sekcja
environmentwdocker-compose.yml– dobra dla mniej wrażliwych danych, jakNODE_ENVczy numery portów. - Plik
.envw katalogu zdocker-compose.yml– Docker Compose automatycznie go wczytuje i podstawia wartości w konfiguracji. - Opcja
--env-filedla bardziej rozbudowanych scenariuszy, gdy chcesz mieć różne zestawy zmiennych dla różnych środowisk.
Pamiętaj, aby nie kopiować lokalnego .env do obrazu (jest w .dockerignore) i nie commitować go do repozytorium.
Baza danych i persystencja danych
Jeśli Twoja aplikacja React + Node korzysta z bazy danych (MongoDB, PostgreSQL, MySQL), również możesz uruchomić ją w osobnym kontenerze. W tym celu dodaj kolejny serwis do docker-compose.yml i użyj wolumenów (volumes), aby zachować dane poza cyklem życia kontenera.
Przykładowo możesz zdefiniować serwis db z mapowaniem katalogu danych na hosta. Dzięki temu nawet po usunięciu kontenera dane bazy pozostaną bezpieczne. To naturalne rozszerzenie układu frontend + backend o trwałą warstwę danych.
Tryb deweloperski vs produkcyjny
Konfiguracja przedstawiona wyżej dobrze sprawdza się w środowisku developerskim, a także jako podstawa pod produkcję. Warto jednak pamiętać o kilku różnicach:
- W development często używa się wolumenów, aby widzieć zmiany w kodzie bez ponownego budowania obrazu.
- Dla backendu możesz dodać
nodemonjakodevDependencyi używać go w skrypciestart, aby serwer restartował się automatycznie po zmianach. - W produkcji zazwyczaj unika się wolumenów z kodem, bo artefakt budowania (obraz Docker) jest traktowany jako gotowy produkt, a nie coś, co dynamicznie się zmienia.
Kolejnym krokiem po opanowaniu podstawowych Dockerfile i Docker Compose może być wprowadzenie pipeline’ów CI/CD, które automatycznie budują obrazy i wdrażają je na serwery testowe czy produkcyjne.
Podsumowanie i dalszy rozwój projektu w Dockerze
W jednym projekcie udało Ci się przygotować kompletną konfigurację Dockera dla aplikacji React z backendem Node. Masz:
- Dockerfile dla backendu Node.js, instalujący zależności i uruchamiający serwer.
- Multi-stage Dockerfile dla frontendu React, który buduje aplikację i serwuje ją przez Nginx.
- Plik
nginx.conf, obsługujący routing SPA i proxy do backendu. - Plik
docker-compose.yml, łączący oba serwisy w jedną aplikację działającą w kontenerach.
Taki zestaw pozwala osiągnąć powtarzalne, przewidywalne środowisko, w którym Twoja aplikacja będzie działała tak samo na każdym komputerze i serwerze. To solidny fundament zarówno do pracy lokalnej, jak i do pierwszych wdrożeń produkcyjnych.
Możesz teraz eksperymentować z dodaniem bazy danych jako kolejnego serwisu, rozdzielaniem konfiguracji na różne środowiska czy budowaniem pipeline’ów CI/CD. Każdy kolejny krok będzie łatwiejszy, bo najważniejszy element – konteneryzacja frontendu React i backendu Node w jednym projekcie – masz już opanowany.