Jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node

Marek Radoszewski Marek Radoszewski
Backend i Frontend
04.08.2026 10 min
Jak stworzyć pierwszy Dockerfile dla aplikacji React z backendem Node

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.

Schemat aplikacji React z backendem Node w kontenerach Docker, pokazujący frontend, backend i wspólną sieć w jednym projekcie

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:

  1. Docker Desktop – zainstalowany i uruchomiony na Twoim systemie (Windows, macOS lub Linux).
  2. Edytor kodu – np. VS Code, WebStorm lub inny ulubiony edytor.
  3. Działająca aplikacja React + Node.js – tak skonfigurowana, aby npm install i npm start w katalogach frontend oraz backend dział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*.json pozwala 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ą w package-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 do index.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 /api do usługi backend na porcie 3001. Nazwa backend bę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.

Schemat multi-stage build: etap builder z Node.js oraz etap runner z Nginx serwującym statyczną aplikację React w kontenerze

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 ./backend jako /app. Dodatkowy wolumen /app/node_modules zapewnia, że kontener używa swoich własnych zależności, a nie tych z hosta.
  • services.frontend – buduje obraz z ./frontend/Dockerfile i mapuje port 80 w kontenerze Nginx na port 3000 na hoście. Dzięki temu aplikacja React jest dostępna pod http://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ć adresu http://backend:3001 w 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ć.

  1. 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.

  1. 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

  1. 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.

  1. Dostęp do aplikacji

  2. Frontend React (serwowany przez Nginx): http://localhost:3000

  3. 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.

  1. 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 environment w docker-compose.yml – dobra dla mniej wrażliwych danych, jak NODE_ENV czy numery portów.
  • Plik .env w katalogu z docker-compose.yml – Docker Compose automatycznie go wczytuje i podstawia wartości w konfiguracji.
  • Opcja --env-file dla 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ć nodemon jako devDependency i używać go w skrypcie start, 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.

Marek Radoszewski

Autor

Marek Radoszewski

Freelance developer i tech blogger od 7 lat. Pracował przy projektach dla klientów z Polski, UK i USA. Na blogu pisze o praktycznych aspektach programowania, narzędziach i tym, jak skutecznie rozwijać karierę jako niezależny programista.

Wróć do kategorii Backend i Frontend