Przejdź do głównej treści
Technologia bez szumu · Wiedza, która działa
Języki programowania

Docker Compose dla Node.js: lokalne środowisko z bazą danych krok po kroku

Sprawdź, jak skonfigurować lokalne środowisko Node.js z PostgreSQL w Docker Compose i uniknąć błędów sieci, startu oraz utraty danych.

Zespół programistów pracujący przy komputerach w nowoczesnym biurze
Źródło: Pexels | Autor: cottonbro studio
Rate this post

W czwartek po południu ktoś dopisuje do compose.yaml drugą usługę, odpala stos i widzi w logach ECONNREFUSED. Aplikacja Node.js wstaje, baza „też wstaje”, a mimo to pierwsze zapytanie pada. Po restarcie raz działa, raz nie. Po docker compose down znikają testowe rekordy. To nie jest pechowy dzień Dockera. To zwykle kilka drobnych założeń, które na hoście są prawdziwe, a wewnątrz kontenera już nie.

Lokalne środowisko deweloperskie ma być nudne w dobrym sensie: ten sam plik, ta sama sieć, ta sama baza, ten sam sposób startu na każdym laptopie. Poniżej pięć błędów, które ten spokój psują, oraz układ dwóch usług — aplikacji Node.js i PostgreSQL — który te błędy zamyka zanim zdążą wejść do repozytorium.

Błąd 1: Próba łączenia się z bazą przez „localhost” zamiast sieci Compose

Na hoście localhost oznacza „ten komputer”. W kontenerze oznacza „ten kontener”. To dwa różne światy, nawet jeśli oba siedzą na tym samym laptopie. Aplikacja, która poza Dockerem łączy się z PostgreSQL pod 127.0.0.1:5432, po przeniesieniu do usługi Compose szuka bazy we własnym namespace sieciowym. Tam jej nie ma.

Dlaczego kontener nie widzi bazy pod adresem localhost?

Każdy kontener ma własny stos sieciowy. Proces Node.js i proces postgres nie dzielą pętli zwrotnej, dopóki nie uruchomisz ich w jednym kontenerze — a tego właśnie nie chcesz robić. Compose domyślnie tworzy sieć projektu i podpina do niej wszystkie usługi. W tej sieci nazwa usługi jest nazwą DNS. Jeśli baza nazywa się db, hostem połączenia z perspektywy aplikacji jest db, a portem port nasłuchu wewnątrz kontenera bazy, zwykle 5432, nie port zmapowany na hosta.

Mapowanie ports w stylu "5432:5432" otwiera drzwi z laptopa do kontenera. Nie tworzy skrótu z jednego kontenera do drugiego przez localhost. Stąd klasyczny objaw: klient SQL na hoście łączy się bez problemu, a ten sam connection string w Node.js w kontenerze kończy się odmową połączenia.

Jak to rozpoznać i co z tego wynika

Rozpoznanie jest dość ostre. Ta sama aplikacja, uruchomiona node server.js na hoście, widzi bazę. Po docker compose up w logach pojawia się ECONNREFUSED 127.0.0.1:5432 albo timeout do adresu, który działał wczoraj w .env skopiowanym ze starego projektu. Czasem ktoś „naprawia” to wystawianiem bazy na 0.0.0.0 i wpisaniem IP mostka Dockera. Działa do następnej zmiany sieci Wi-Fi.

Skutek uboczny to niepotrzebne mapowanie portów na zewnątrz. Do pracy samej aplikacji port bazy na hoście nie jest potrzebny. Przydaje się tylko wtedy, gdy chcesz podłączyć klienta graficznego z laptopa. Jeśli go nie używasz, zostawiasz otwarte drzwi bez zysku.

Gdy usługi korzystają z domyślnej sieci Compose, aplikacja łączy się z inną usługą po jej nazwie, na przykład hostem db, bez ręcznego definiowania links. [4]

Lepszy układ adresów

W kodzie trzymaj host, port, użytkownika, hasło i nazwę bazy w zmiennych. W Compose ustaw je tak, by hostem była nazwa usługi:

services:
  app:
    environment:
      DATABASE_URL: postgres://app:app@db:5432/appdb
  db:
    image: postgres:16

Na hoście, gdy odpalasz Node bez kontenera, DATABASE_URL może wskazywać localhost i opublikowany port. W kontenerze — nazwę usługi. Dwa konteksty, dwa zestawy zmiennych, zero magii w kodzie.

Skąd łączysz się z baząHostKtóry port
Inna usługa w tym samym Composenazwa usługi, np. dbport wewnątrz kontenera
Proces na laptopie (klient SQL, Node poza Dockerem)localhostport z lewej strony ports
Kontener aplikacji przez localhostnie działawłasna pętla zwrotna kontenera, nie hosta

Wyjątek, o którym łatwo zapomnieć: network_mode: host na Linuksie rzeczywiście współdzieli sieć z hostem, więc localhost nagle „działa”. Na Docker Desktop dla macOS i Windows ten tryb nie zachowuje się tak samo. Konfiguracja, która przechodzi tylko na jednej maszynie w zespole, nie jest lokalnym środowiskiem. Jest loterią.

Błąd 2: Iluzja kolejności startu, czyli poleganie na samym „depends_on”

depends_on bez warunku zdrowia mówi Compose: najpierw utwórz i uruchom kontener bazy, potem kontener aplikacji. Uruchomiony kontener to proces, który dostał PID. PostgreSQL w tym momencie dopiero czyta konfigurację, odtwarza pliki i otwiera gniazdo. Node.js, który w pierwszym tiknięciu robi pool.connect() albo odpala migracje, trafia w zamknięte drzwi.

Dlaczego startujący proces bazy to za mało dla Node.js?

Aplikacje w Node rzadko czekają cierpliwie. Sterownik rzuca błąd, proces kończy się kodem niezerowym, a polityka restartu — jeśli ją ustawisz — zapętla start. Bez restartu stos „wstaje”, ale API jest martwe do ręcznego docker compose restart app. Na szybkim dysku SSD wyścig bywa wygrany. Na wolniejszym laptopie albo przy pierwszym init wolumenu — przegrany. Stąd wrażenie, że „u mnie działa”.

Sam depends_on nie gwarantuje, że PostgreSQL przyjmuje już połączenia. Kolejność tworzenia kontenerów i gotowość protokołu to dwie różne rzeczy. Migracje odpalane w command przed serwerem HTTP są szczególnie wrażliwe: jeden nieudany start czyści nadzieję, ale nie czyści częściowo zaaplikowanego stanu, jeśli skrypt nie jest idempotentny.

Objawy wyścigu

Log aplikacji: connect ECONNREFUSED db:5432 albo komunikat sterownika o zamkniętym gnieździe, pojawiający się tylko przy zimnym starcie. Po kilku sekundach ręczne wejście do kontenera i ponowne zapytanie działa. Czasem stos wstaje za pierwszym razem, czasem za trzecim. To nie flaky test. To brak warunku gotowości.

Drugie rozpoznanie: docker compose ps pokazuje bazę jako running, zanim pg_isready zwróci sukces. Status kontenera i status silnika nie są synonimami.

Warunek service_healthy i pg_isready

W Compose aplikacja może czekać na bazę przez depends_on z warunkiem condition: service_healthy, a gotowość PostgreSQL sprawdza się poleceniem pg_isready w healthcheck. [2]

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: appdb
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 5s
      timeout: 5s
      retries: 10
  app:
    depends_on:
      db:
        condition: service_healthy

pg_isready sprawdza, czy postmaster przyjmuje połączenia. Nie sprawdza, czy Twoja migracja ma sens, ani czy schemat jest aktualny. To wystarcza, żeby nie walić w zamknięte gniazdo. Dłuższą inicjalizację — duży dump, rozszerzenia — lepiej obsłużyć osobnym krokiem, nie przez wydłużanie retries w nieskończoność.

W kodzie i tak przyda się krótki retry przy starcie puli. Healthcheck zamyka wyścig między kontenerami. Nie zastępuje obsługi chwilowej niedostępności, gdy ktoś zrestartuje tylko usługę db w trakcie pracy.

Błąd 3: Znikające rekordy i brak trwałego wolumenu dla plików bazy

Kontener jest wymienny. Dane, których nie wyniesiesz na wolumen, żyją w jego zapisywalnej warstwie. docker compose down usuwa kontenery. Przebudowa obrazu aplikacji tego nie rusza, ale usunięcie kontenera bazy bez wolumenu kasuje pliki silnika. Następny up tworzy pusty klaster. Seed wraca. Testowe konta znikają. Ktoś oskarża migracje.

Co się dzieje z danymi, gdy kontener zostaje zatrzymany lub przebudowany?

docker compose stop i zwykły restart zwykle zostawiają warstwę kontenera, więc dane „jakby” trwają — dopóki nikt nie zrobi down, rm albo nie zmieni czegoś, co wymusi odtworzenie kontenera. To złudna trwałość. Wystarczy nowy członek zespołu, który sprząta wiszące kontenery, i lokalna baza jest pusta.

Nazwany wolumen Compose przechowuje dane poza cyklem życia kontenera i można go zamontować w katalogu danych bazy. [3] Ścieżka tego katalogu zależy od obrazu i jego dokumentacji. Dla oficjalnego obrazu PostgreSQL najczęściej jest to /var/lib/postgresql/data, ale inny obraz lub inna wersja mogą używać innej lokalizacji. Zanim przypniesz wolumen, sprawdź VOLUME i opis obrazu, którego naprawdę używasz.

Jak poznać, że wolumenu brakuje

Po docker compose down i ponownym up tabele są puste albo w ogóle nie istnieją, choć wczoraj wieczorem seed przeszedł. docker volume ls nie pokazuje wolumenu projektu. W compose.yaml usługa db nie ma sekcji volumes. Czasem wolumen jest, ale podpięty pod zły katalog — pliki lądują obok, a silnik dalej pisze do warstwy kontenera.

Bind mount katalogu z hosta na dane PostgreSQL bywa kuszącą „naprawą”, bo widać pliki w Eksploratorze. Na macOS i Windows wydajność i uprawnienia często gryzą się z procesem bazy. Do lokalnego stanu nazwanego wolumenu zwykle wystarcza. Bind zostaw na dumpach SQL, które chcesz wersjonować albo przekazać komuś jako plik.

Przypięcie nazwanego wolumenu

services:
  db:
    image: postgres:16
    volumes:
      - pgdata:/var/lib/postgresql/data
volumes:
  pgdata:

Ten fragment zakłada oficjalny obraz i jego typowy katalog. Jeśli dokumentacja obrazu podaje inną ścieżkę, użyj jej. Inaczej wolumen będzie pustą szufladą obok prawdziwych plików.

docker compose down -v usuwa nazwane wolumeny projektu. To świadome sprzątanie, nie codzienna komenda. Warto ją oddzielić od zwykłego zatrzymania, choćby komentarzem w README zespołu: bez -v stan zostaje, z -v zaczynasz od zera.

Zmiana hasła POSTGRES_PASSWORD po pierwszym init nie przepisuje hasła w już zainicjowanym klastrze. Obraz ustawia użytkownika przy tworzeniu katalogu danych. Pusty wolumen plus nowe hasło działa. Stary wolumen plus nowe hasło w pliku — nie. Objaw to „nagle” błąd uwierzytelnienia po zmianie .env, choć healthcheck z pg_isready jest zielony, bo gotowość gniazda nie sprawdza Twojego hasła aplikacyjnego.

Ekran komputera z kodem programistycznym i odbiciami światła
Źródło: Pexels | Autor: Daniil Komov

Błąd 4: Zepsuty cykl developerski przez błędny montaż kodu i katalogu node_modules

Obraz buduje zależności w Linuksie kontenera. Bind mount katalogu projektu z hosta nakłada na /app drzewo z laptopa, łącznie z node_modules z Windowsa albo macOS. Natywne moduły skompilowane na hoście nie pasują do glibc w kontenerze. Serwer albo nie wstaje, albo wstaje bez nasłuchu plików, bo komenda w obrazie to node dist/server.js, a nie proces w trybie watch.

Problem nadpisywania zależności systemowych przez bind mount z hosta

Kolejność jest ważna. Najpierw montujesz katalog roboczy, potem anonimowy wolumen na /app/node_modules. Drugi montaż zasłania katalog zależności z hosta i zostawia ten, który powstał w obrazie albo przy npm ci wewnątrz kontenera. Bez tej zasłony każde npm install na laptopie potrafi zepsuć kontener, a każde npm ci w kontenerze — zepsuć hosta, jeśli systemy się różnią.

Hot reload wymaga dwóch rzeczy naraz: widocznego kodu źródłowego (bind mount) i procesu, który ten kod obserwuje (node --watch, tsx watch, nodemon albo dev server frameworka). Sam mount nic nie przeładowuje. Sam watch bez mountu widzi tylko pliki w obrazie, czyli stan z chwili docker build.

Objawy i prostsza korekta

Zapisujesz .js albo .ts, a w logach cisza. Albo odwrotnie: logi krzyczą o invalid ELF header, ERR_DLOPEN_FAILED albo o module skompilowanym pod inną architekturę. Trzeci wariant: na Macu watch „nie widzi” zapisu, bo zdarzenia systemu plików nie przechodzą przez wirtualizację. Wtedy pomaga polling w narzędziu watch, nie kolejny restart całego stosu.

services:
  app:
    build:
      context: .
      target: dev
    command: npm run dev
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      CHOKIDAR_USEPOLLING: "true"

CHOKIDAR_USEPOLLING włączaj tylko tam, gdzie watch oparty o zdarzenia naprawdę milczy. Na Linuksie zwykle nie jest potrzebny i tylko podnosi zużycie CPU. Anonimowy wolumen na node_modules nie jest backupem zależności. Po zmianie package-lock.json przebuduj obraz albo uruchom instalację w kontenerze. Inaczej zasłonięty katalog zostanie przy starej wersji, a host będzie miał nową — i znowu „u mnie działa”.

Nie montuj całego projektu na katalog, w którym Dockerfile robi WORKDIR, a potem licz, że COPY z buildu przetrwa. Bind mount wygrywa w runtime. To, co weszło do warstw obrazu, zostaje pod spodem, niewidoczne.

Błąd 5: Wrażliwe dane na wierzchu i brak separacji konfiguracji

Hasło wpisane w compose.yaml obok obrazu wygląda niewinnie, dopóki plik nie trafi do publicznego repozytorium albo do zrzutu ekranu w tickecie. Lokalne hasło app nie jest sekretem produkcyjnym, ale ten sam nawyk później wkleja prawdziwe dane. Drugi problem jest przyziemniejszy: dwie osoby w zespole chcą innych portów, innej nazwy bazy, innego poziomu logów. Plik Compose śledzony przez Git nie jest dobrym miejscem na te różnice.

Dlaczego wpisywanie loginów i haseł bezpośrednio do compose.yaml to pułapka?

Plik Compose opisuje kształt stosu: usługi, sieci, wolumeny, healthcheck. Wartości, które zmieniają się między maszynami, należą do środowiska. Compose czyta plik .env z katalogu projektu i podstawia składnię ${POSTGRES_PASSWORD}. Jawny string POSTGRES_PASSWORD: super-tajne w pliku śledzonym przez Git to sygnał, że granica się rozmyła.

Plik .env z hasłem lokalnym nie jest równoważny bezpiecznemu zarządzaniu sekretami w środowisku produkcyjnym. Leży na dysku w plaintext, często bez szyfrowania, czasem z prawami zbyt szerokimi. Do laptopa, przy haśle używanym tylko przez kontener na Twojej maszynie, to akceptowalny kompromis. Do wdrożenia — nie. Konfiguracja lokalna Compose sama w sobie nie jest konfiguracją gotową na produkcję: brakuje tam polityki sekretów, limitów, sieci zewnętrznej, kopii zapasowych i tego, kto ma prawo czytać zmienne.

Jak to zobaczyć w repozytorium

W historii Gita widać POSTGRES_PASSWORD, DATABASE_URL z użytkownikiem i hasłem albo klucz API „tylko do sandboxa”. .env nie jest w .gitignore. Nowy clone nie startuje, bo brakuje zmiennych, a nikt nie zostawił .env.example bez sekretów — same nazwy i puste wartości albo oczywiste lokale typu app.

Oficjalny układ lokalny dla Node.js to właśnie dwie usługi w compose.yaml: aplikacja i PostgreSQL, z parametrami połączenia przekazanymi do aplikacji. [1] Parametry mogą pochodzić ze środowiska, nie muszą być wklejone na stałe.

Krok po kroku: kompletny, bezbłędny plik compose.yaml w praktyce

Pięć poprawek składa się w jeden plik. Poniższy układ jest lokalnym środowiskiem developerskim: usługa app i usługa db, bez haseł wpisanych na stałe i bez pretensji do konfiguracji produkcyjnej.

1. Zostaw sekrety poza plikiem Compose

W katalogu projektu trzymaj .env w .gitignore oraz .env.example z samymi nazwami. Compose podstawi zmienne przy starcie.

POSTGRES_USER=app
POSTGRES_PASSWORD=app
POSTGRES_DB=app
DATABASE_URL=postgres://app:app@db:5432/app

Hasło app jest tu umownym lokalnym ciągiem, nie wzorcem na produkcję. W repozytorium commituj tylko .env.example.

2. Złóż obie usługi w compose.yaml

services:
  app:
    build:
      context: .
      target: dev
    command: npm run dev
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: ${DATABASE_URL}
      CHOKIDAR_USEPOLLING: ${CHOKIDAR_USEPOLLING:-false}
    volumes:
      - .:/app
      - /app/node_modules
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16
    environment:
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: ${POSTGRES_DB}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 5s
      timeout: 5s
      retries: 10

volumes:
  pgdata:

Host w DATABASE_URL to nazwa usługi, db. Aplikacja czeka na warunek service_healthy, nie na sam start kontenera. Nazwany wolumen pgdata jest podpięty pod katalog danych oficjalnego obrazu PostgreSQL 16; inny obraz może wymagać innej ścieżki z jego dokumentacji. Bind .:/app pokazuje kod źródłowy, a anonimowy wolumen zasłania /app/node_modules z kontenera. CHOKIDAR_USEPOLLING zostaje wyłączony, dopóki watch na Twoim systemie naprawdę nie milczy.

3. Uruchom stos i sprawdź, czy poprawki trzymają się razem

Po zapisaniu pliku stos wstaje jedną komendą: docker compose up --build. [1] Flaga --build jest potrzebna po zmianie Dockerfile albo lockfile’a, nie przy każdym zapisie pliku .js.

Log aplikacji powinien pokazać nasłuch, a nie ECONNREFUSED na localhost. docker compose ps dla db powinno pokazać stan healthy, zanim proces Node otworzy pulę. Zapis w tabeli ma przetrwać docker compose down i kolejne up. Znika dopiero po świadomym docker compose down -v.

Checklista przed pierwszym commitem

  • DATABASE_URL wskazuje host db, nie localhost.
  • depends_on ma condition: service_healthy, a healthcheck woła pg_isready.
  • Dane bazy siedzą na nazwanym wolumenie, pod ścieżką z dokumentacji obrazu.
  • Kod jest zbindowany, node_modules zasłonięty osobnym wolumenem, komenda to proces dev z watchem.
  • Hasła i URL są w .env, wzorzec nazw w .env.example, .env jest w .gitignore.
  • README odróżnia zwykłe down od down -v.

Szybka weryfikacja: jak diagnozować problemy, gdy kontenery nadal nie współpracują

Nawet przy poprawnym pliku konfiguracyjnym wdrożenie na nowej maszynie potrafi zatrzymać się na drobiazgach związanych ze stanem samego Dockera. Zamiast resetować cały system lub wprowadzać losowe zmiany w plikach konfiguracyjnych, warto przejść przez krótką ścieżkę eliminacji anomalii.

1. Zawsze izoluj logi konkretnej usługi

Zbiorczy widok po uruchomieniu stosu często miesza wyjścia obu procesów, przez co błąd inicjalizacji bazy ginie pod powtarzającymi się komunikatami frameworka Node.js. Weryfikację problemu zacznij od odpytania pojedynczej usługi:

docker compose logs db --tail=50
docker compose logs app --tail=50 -f

Jeśli w logach bazy pojawia się komunikat o zablokowanym porcie lub problemie z zapisem w katalogu danych, wiesz od razu, że problem leży po stronie silnika bazy lub uprawnień systemowych, a nie samej aplikacji Node.js.

2. Usuwanie konfliktów po zmianie nazw usług

Jeśli w trakcie pracy zmieniła się nazwa usługi (na przykład z database na db) albo przełączano gałęzie w repozytorium z inną strukturą compose.yaml, w domyślnej sieci mogły pozostać stare kontenery. Powoduje to konflikty nazw lub nieoczekiwane przekierowania ruchu:

docker compose down --remove-orphans

Flaga --remove-orphans czyści kontenery zdefiniowane w poprzednich wersjach pliku, które nie są już częścią aktualnego środowiska, nie ruszając przy tym zadeklarowanych wolumenów z danymi.

3. Zgodność uprawnień użytkownika na Linuksie

Na systemach z jądrem Linux proces w kontenerze domyślnie działa jako root (chyba że w Dockerfile zdefiniowano dyrektywę USER node). Pliki tworzone wewnątrz montowanego katalogu (np. wygenerowane logi czy zaktualizowany lockfile) mogą zyskać uprawnienia roota, uniemożliwiając ich edycję ze zwykłego konta na hoście. Jeśli po uruchomieniu środowiska edytor zgłasza brak uprawnień do zapisu plików w projekcie, zweryfikuj identyfikator użytkownika w kontenerze aplikacji:

services:
  app:
    user: "${UID:-1000}:${GID:-1000}"

Dopasowanie UID i GID kontenera do uprawnień użytkownika lokalnego eliminuje problem blokowania plików bez konieczności ciągłego wywoływania chmod na hoście.

Pytania od czytelników

Pytanie czytelnika

Czy port PostgreSQL trzeba publikować, jeśli korzysta z niego tylko aplikacja w Compose?

Odpowiedź redakcji: Nie. Aplikacja może łączyć się z bazą po nazwie usługi, na przykład db, i porcie wewnętrznym 5432. Mapowanie portu jest potrzebne tylko wtedy, gdy chcesz łączyć się z bazą bezpośrednio z laptopa.
Pytanie czytelnika

Co zrobić, gdy zmiana hasła w pliku Compose nie działa dla istniejącej bazy?

Odpowiedź redakcji: Zmienne inicjalizacyjne obrazu PostgreSQL nie zmieniają danych w już utworzonym klastrze. Możesz zmienić hasło ręcznie w bazie albo usunąć wolumen i rozpocząć inicjalizację od zera, pamiętając, że usunie to zapisane dane.
Pytanie czytelnika

Czy healthcheck zastępuje ponawianie połączenia w aplikacji Node.js?

Odpowiedź redakcji: Nie. Healthcheck pomaga uruchomić aplikację dopiero wtedy, gdy PostgreSQL przyjmuje połączenia, ale nie chroni przed chwilową niedostępnością bazy po restarcie w trakcie pracy. Krótki retry po stronie aplikacji nadal może być potrzebny.
Pytanie czytelnika

Jak bezpiecznie wyczyścić lokalną bazę przed ponownym seedowaniem?

Odpowiedź redakcji: Użyj docker compose down -v tylko wtedy, gdy świadomie chcesz usunąć nazwane wolumeny i wszystkie lokalne dane. Następne uruchomienie utworzy pusty klaster, w którym można ponownie wykonać migracje i seed.

Źródła

Podobał Ci się ten materiał? Odkrywaj kolejne tematy ↗
B

Bartosz Kubiak

Bartosz Kubiak – inżynier uczenia maszynowego i entuzjasta praktycznych zastosowań AI. Pracował przy projektach wykorzystujących modele językowe, systemy rekomendacyjne i analitykę danych. Na CPModa.pl opisuje, jak bezpiecznie wdrażać AI w biznesie, jak dobierać narzędzia i jak oceniać jakość modeli. Każdy artykuł opiera na eksperymentach, dokumentacji technicznej i publikacjach naukowych, które przekłada na zrozumiały język. Szczególną wagę przykłada do kwestii etyki, prywatności danych i transparentności działania algorytmów.