Docker a gyakorlatban: egy teljes webalkalmazás felépítése lépésről lépésre

Üdvözöllek a Docker világában! Ha valaha is szembesültél azzal a klasszikus „nálam működik” problémával, vagy ha eleged van a fejlesztői környezetek beállításának végeláthatatlan küzdelmeiből, akkor jó helyen jársz. A Docker forradalmasította a szoftverfejlesztést és telepítést, lehetővé téve, hogy alkalmazásainkat konzisztens, izolált környezetben futtassuk, legyen szó fejlesztésről, tesztelésről vagy éles üzemről.

Ebben az átfogó útmutatóban lépésről lépésre megmutatjuk, hogyan építhetsz fel egy teljes értékű webalkalmazást a Docker segítségével. Nem csak elméleti tudást szerzel, hanem egy konkrét, működő példán keresztül sajátíthatod el a konténerizálás gyakorlati fortélyait. Készen állsz? Vágjunk is bele!

A Docker Alapjai – Miért pont a Docker?

Mielőtt belevetnénk magunkat a kódolásba, értsük meg, miért is olyan népszerű a Docker. Képzeld el, hogy a számítógéped egy nagy ház, az operációs rendszered a ház alapja. Virtuális gépek (VM-ek) esetén minden egyes alkalmazásodhoz egy külön lakást építesz, saját konyhával, fürdőszobával – azaz saját operációs rendszerrel és erőforrásokkal. Ez rengeteg helyet és erőforrást emészt fel.

Ezzel szemben a Docker konténerek úgy működnek, mintha a lakások helyett csak külön szobákat alakítanál ki a házon belül. Ezek a szobák (konténerek) megosztják a ház alapját (az operációs rendszer kerneljét), de minden szükséges bútort és felszerelést (függőségeket, futtatókörnyezetet) tartalmaznak az adott szoba céljához. Ezáltal a konténerek sokkal könnyebbek, gyorsabban indulnak, és sokkal kevesebb erőforrást igényelnek, mint a VM-ek.

A Docker fő előnyei:

  • Konzisztencia: A „nálam működik” probléma a múlté. A konténerben futó alkalmazás mindenhol ugyanúgy viselkedik.
  • Izoláció: Az alkalmazások és függőségeik elkülönítve futnak, elkerülve a konfliktusokat.
  • Portabilitás: Egy konténerizált alkalmazás könnyedén áthelyezhető különböző környezetek között (fejlesztés, tesztelés, éles üzem).
  • Skálázhatóság: Szükség esetén pillanatok alatt több példányt indíthatsz az alkalmazásodból.
  • Egyszerűbb telepítés: A CI/CD (folyamatos integráció/folyamatos szállítás) folyamatokban is rendkívül hatékony.

Főbb fogalmak, amikkel találkozni fogunk:

  • Dockerfile: Egy szöveges fájl, ami leírja, hogyan építsünk fel egy Docker image-et.
  • Docker Image: Egy olvasható-csak sablon, ami tartalmazza az alkalmazás futtatásához szükséges összes függőséget, kódot és konfigurációt. Ebből indulnak a konténerek.
  • Docker Container: Az image futó példánya.
  • Docker Compose: Eszköz több konténeres alkalmazások definiálására és futtatására. Egyetlen YAML fájlban (docker-compose.yml) leírhatjuk az alkalmazásunk összes szolgáltatását (adatbázis, backend, frontend stb.) és azok kapcsolatát.

A Projekt Felépítése: A Webalkalmazás Rétegei

Példa alkalmazásunk egy tipikus modern webstack-et fog használni:

  • Frontend: Egy egyszerű statikus HTML/CSS/JavaScript alkalmazás, amit Nginx szolgál ki. Az Nginx fordított proxyként is működik majd a backend felé.
  • Backend: Egy egyszerű REST API, Node.js és Express keretrendszerrel.
  • Adatbázis: Egy robusztus és népszerű relációs adatbázis, a PostgreSQL.

A projekt könyvtárstruktúrája a következő lesz:

.
├── docker-compose.yml
├── backend/
│   ├── Dockerfile
│   ├── package.json
│   ├── server.js
│   └── .env
└── frontend/
    ├── Dockerfile
    ├── nginx.conf
    └── html/
        ├── index.html
        └── style.css

Kezdjük is az egyes rétegek konténerizálásával!

1. Lépés: A PostgreSQL Adatbázis Dockerizálása

Miért az adatbázissal kezdjük? Mert a backend alkalmazásunk függeni fog tőle. Először hozzuk létre a fő konfigurációs fájlt, a docker-compose.yml-t a projekt gyökérkönyvtárában:


# docker-compose.yml
version: '3.8'

services:
  db:
    image: postgres:13
    restart: always
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    ports:
      - "5432:5432" # Ezt csak fejlesztéshez használjuk, élesben nem célszerű publikálni a DB portját

volumes:
  db_data:

Nézzük meg, mit csinál ez a konfiguráció:

  • version: '3.8': Meghatározza a Docker Compose fájl formátumának verzióját.
  • services:: Itt definiáljuk az alkalmazásunk egyes szolgáltatásait (konténereit).
  • db:: Ez a szolgáltatás neve, amire a többi konténer hivatkozhat.
  • image: postgres:13: A hivatalos PostgreSQL Docker image 13-as verzióját használjuk. A Docker Hub-ról fogja letölteni.
  • restart: always: Ha a konténer valamilyen okból leáll, a Docker automatikusan újraindítja.
  • environment:: Környezeti változókat állítunk be a PostgreSQL számára, mint például az adatbázis nevét (POSTGRES_DB), felhasználónevét (POSTGRES_USER) és jelszavát (POSTGRES_PASSWORD). Ezek alapvető fontosságúak az adatbázis inicializálásához.
  • volumes:: Itt definiálunk egy Docker volume-ot, db_data néven. Ez biztosítja, hogy az adatbázis adatai megmaradjanak akkor is, ha a konténer megsemmisül vagy újraindul. A /var/lib/postgresql/data a PostgreSQL alapértelmezett adatkönyvtára a konténeren belül.
  • ports:: A konténer 5432-es portját (ahol a PostgreSQL fut) a gazdagép 5432-es portjára képezzük le. Ez lehetővé teszi számunkra, hogy kívülről, például egy adatbázis klienssel csatlakozzunk az adatbázishoz. Fejlesztés során hasznos, éles környezetben azonban az adatbázis portjait nem szokás publikusan elérhetővé tenni.

Ezzel a PostgreSQL konténerünk készen áll. Egyelőre ne indítsuk el, haladjunk tovább a backenddel!

2. Lépés: A Node.js Backend Alkalmazás Dockerizálása

Most hozzunk létre egy egyszerű Node.js Express API-t a backend/ könyvtárban. Először a backend/package.json:


// backend/package.json
{
  "name": "backend",
  "version": "1.0.0",
  "description": "Simple Node.js Express API",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "express": "^4.17.1",
    "pg": "^8.7.1"
  }
}

A backend/server.js fájl, ami egy egyszerű végpontot és adatbázis kapcsolatot tartalmaz:


// backend/server.js
const express = require('express');
const { Pool } = require('pg');
const app = express();
const port = 3000;

// PostgreSQL kapcsolat konfigurálása
const pool = new Pool({
  user: process.env.POSTGRES_USER || 'user',
  host: process.env.DB_HOST || 'localhost', // Fontos: DB_HOST-ként fogunk hivatkozni a docker-compose-ban
  database: process.env.POSTGRES_DB || 'mydatabase',
  password: process.env.POSTGRES_PASSWORD || 'password',
  port: 5432,
});

app.get('/', (req, res) => {
  res.send('Hello from Backend API!');
});

app.get('/api/test-db', async (req, res) => {
  try {
    const client = await pool.connect();
    const result = await client.query('SELECT NOW()');
    client.release();
    res.json({ message: 'Successfully connected to DB!', time: result.rows[0].now });
  } catch (err) {
    console.error('Database connection error', err);
    res.status(500).json({ error: 'Failed to connect to database', details: err.message });
  }
});

app.listen(port, () => {
  console.log(`Backend listening at http://localhost:${port}`);
});

Figyeljük meg a DB_HOST környezeti változót! Ezt fogjuk beállítani a docker-compose.yml-ben, hogy a backend konténer elérje az adatbázis konténert.

Most hozzunk létre egy Dockerfile-t a backend/ könyvtárban:


# backend/Dockerfile
FROM node:16-alpine # Alap image: Node.js 16 Alpine Linux-szal (kisebb méret)

WORKDIR /app # Meghatározza a munkakönyvtárat a konténeren belül

COPY package*.json ./ # Másolja a package.json és package-lock.json fájlokat

RUN npm install # Telepíti a függőségeket

COPY . . # Másolja az összes többi fájlt a munkakönyvtárba

EXPOSE 3000 # Jelzi, hogy az alkalmazás a 3000-es porton hallgat

CMD ["npm", "start"] # Ez a parancs fut le, amikor a konténer elindul

Magyarázat a Dockerfile-hoz:

  • FROM node:16-alpine: Az alap Docker image, amire építünk. Az alpine verziók kisebb méretűek.
  • WORKDIR /app: Beállítja az /app könyvtárat, mint alapértelmezett munkakönyvtárat a konténeren belül.
  • COPY package*.json ./: Kimásolja a package.json és package-lock.json fájlokat a gazdagépről a konténer munkakönyvtárába. Fontos, hogy ezt még az npm install előtt tegyük, mert ha csak a package.json változik, a Docker újraépítéskor felhasználhatja a cache-elt függőségeket, felgyorsítva a folyamatot.
  • RUN npm install: Telepíti az alkalmazás Node.js függőségeit.
  • COPY . .: Az összes többi fájlt (pl. server.js) átmásolja a munkakönyvtárba.
  • EXPOSE 3000: Jelzi, hogy az alkalmazás a konténer 3000-es portján fog figyelni. Ez csak dokumentáció, nem publikálja a portot kívülről.
  • CMD ["npm", "start"]: A parancs, ami elindul, amikor a konténer futni kezd.

Most adjuk hozzá a backend szolgáltatást a docker-compose.yml-hez:


# docker-compose.yml (frissített)
version: '3.8'

services:
  db:
    # ... (a fentebbi db konfiguráció)
    image: postgres:13
    restart: always
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  backend:
    build: ./backend # A Dockerfile elérési útja
    ports:
      - "3000:3000" # A backend konténer 3000-es portját a gazdagép 3000-es portjára képezi le
    environment:
      DB_HOST: db # A Docker Compose szolgáltatás neve a db konténer számára
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    depends_on:
      - db # A backend szolgáltatás a db szolgáltatástól függ

volumes:
  db_data:

Újdonságok a backend szolgáltatásban:

  • build: ./backend: A Docker Compose ebből a könyvtárból fogja megkeresni a Dockerfile-t, és felépíti az image-et.
  • ports: "3000:3000": A backend konténer 3000-es portját (ahol az Express API fut) leképezi a gazdagép 3000-es portjára.
  • environment:: Itt állítjuk be a DB_HOST-ot db-re. Ez azért működik, mert a Docker Compose automatikusan beállítja a szolgáltatásneveket (pl. db) hosztnévként, lehetővé téve a konténerek közötti kommunikációt. A többi DB változóval együtt adunk át minden szükséges adatot a backendnek a DB-hez való kapcsolódáshoz.
  • depends_on: - db: Ez azt jelzi a Docker Compose-nak, hogy a backend szolgáltatás függ a db szolgáltatástól. Bár ez nem garantálja, hogy az adatbázis teljesen inicializálódott és fogadja a kapcsolatokat, biztosítja, hogy a db konténer előbb elinduljon, mint a backend konténer.

3. Lépés: A Nginx Frontend és Statikus Fájlok Dockerizálása

Végül jöjjön a frontend réteg. Hozzunk létre egy frontend/html/index.html fájlt:


<!-- frontend/html/index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Docker Webapp</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <h1>Üdvözlünk a Dockerizált Webalkalmazásban!</h1>
    <p>Ez a statikus tartalom az Nginx konténerből származik.</p>
    <button onclick="fetchBackend()">Backend API tesztelése</button>
    <button onclick="fetchDatabase()">Adatbázis kapcsolat tesztelése</button>
    <div id="output"></div>

    <script>
        async function fetchBackend() {
            const response = await fetch('/api'); // Nginx fordított proxy
            const data = await response.text();
            document.getElementById('output').innerText = `Backend üzenet: ${data}`;
        }
        async function fetchDatabase() {
            const response = await fetch('/api/test-db'); // Nginx fordított proxy
            const data = await response.json();
            document.getElementById('output').innerText = `Adatbázis teszt üzenet: ${JSON.stringify(data, null, 2)}`;
        }
    </script>
</body>
</html>

És egy frontend/html/style.css:


/* frontend/html/style.css */
body {
    font-family: Arial, sans-serif;
    margin: 20px;
    background-color: #f4f4f4;
    color: #333;
}
h1 {
    color: #0056b3;
}
button {
    padding: 10px 15px;
    margin-right: 10px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
button:hover {
    background-color: #0056b3;
}
#output {
    margin-top: 20px;
    padding: 15px;
    background-color: #e9ecef;
    border-left: 5px solid #007bff;
    white-space: pre-wrap;
}

Az nginx.conf fájl a frontend/ könyvtárban fogja konfigurálni az Nginx-et, hogy kiszolgálja a statikus fájlokat, és az /api útvonalon érkező kéréseket átirányítsa a Node.js backendre:


# frontend/nginx.conf
events {
    worker_connections 1024;
}

http {
    server {
        listen 80;

        location / {
            root /usr/share/nginx/html;
            index index.html;
            try_files $uri $uri/ /index.html; # SPA-khoz is jó
        }

        location /api/ {
            proxy_pass http://backend:3000; # Fontos: a 'backend' a Docker Compose szolgáltatás neve
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
        }
    }
}

Az Nginx konfiguráció részletei:

  • listen 80;: Az Nginx a 80-as porton figyel.
  • location / { ... }: Ez a blokk a gyökér URL-re érkező kéréseket kezeli.
    • root /usr/share/nginx/html;: Itt találhatók a statikus fájlok a konténeren belül.
    • index index.html;: Az alapértelmezett fájl, ha egy könyvtárra hivatkozunk.
  • location /api/ { ... }: Ez a blokk minden olyan kérést átirányít, ami /api/-val kezdődik.
    • proxy_pass http://backend:3000;: Itt történik a fordított proxyzás. A kérések átkerülnek a backend szolgáltatásra (a backend konténerre) a 3000-es porton.

Most pedig az Dockerfile a frontend/ könyvtárban:


# frontend/Dockerfile
FROM nginx:alpine # Alap image: Nginx Alpine Linux-szal

COPY ./nginx.conf /etc/nginx/conf.d/default.conf # Másolja az egyedi Nginx konfigurációt
COPY ./html /usr/share/nginx/html # Másolja a statikus HTML fájlokat

EXPOSE 80 # Jelzi, hogy az Nginx a 80-as porton hallgat

CMD ["nginx", "-g", "daemon off;"] # Parancs az Nginx indítására

Magyarázat a Dockerfile-hoz:

  • FROM nginx:alpine: Az alap Nginx Docker image.
  • COPY ./nginx.conf /etc/nginx/conf.d/default.conf: Átmásolja az egyedi Nginx konfigurációs fájlunkat a konténer megfelelő helyére. Ez felülírja az alapértelmezett konfigurációt.
  • COPY ./html /usr/share/nginx/html: Átmásolja a statikus HTML/CSS/JS fájljainkat arra a helyre a konténeren belül, ahonnan az Nginx kiszolgálja őket.
  • EXPOSE 80: Jelzi, hogy az Nginx a konténer 80-as portján figyel.
  • CMD ["nginx", "-g", "daemon off;"]: Elindítja az Nginx-et a konténerben. A daemon off; paraméter fontos, mert ez tartja a konténert futva az előtérben.

Végül, de nem utolsósorban, adjuk hozzá a frontend szolgáltatást a docker-compose.yml-hez:


# docker-compose.yml (végső verzió)
version: '3.8'

services:
  db:
    image: postgres:13
    restart: always
    environment:
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    volumes:
      - db_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  backend:
    build: ./backend
    ports:
      - "3000:3000"
    environment:
      DB_HOST: db
      POSTGRES_DB: mydatabase
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    depends_on:
      - db

  frontend:
    build: ./frontend
    ports:
      - "80:80" # A frontend konténer 80-as portját a gazdagép 80-as portjára képezi le
    depends_on:
      - backend # A frontend szolgáltatás a backend szolgáltatástól függ (proxy miatt)

volumes:
  db_data:

Újdonságok a frontend szolgáltatásban:

  • build: ./frontend: Felépíti a frontend image-et a frontend/Dockerfile alapján.
  • ports: "80:80": Leképezi az Nginx konténer 80-as portját a gazdagép 80-as portjára, így a webböngészőből közvetlenül elérhető lesz az alkalmazás.
  • depends_on: - backend: A frontend függ a backendtől, mivel proxyzza annak kéréseit.

A Teljes Alkalmazás Indítása és Tesztelése

Elérkeztünk a legizgalmasabb részhez! Most, hogy minden Dockerfile és a docker-compose.yml konfiguráció is elkészült, egyetlen paranccsal elindíthatjuk a teljes webalkalmazásunkat.

Nyiss meg egy terminált a projekt gyökérkönyvtárában, ahol a docker-compose.yml található, és futtasd a következő parancsot:


docker-compose up --build -d

Magyarázat a parancshoz:

  • docker-compose up: Elindítja az összes szolgáltatást, ami a docker-compose.yml fájlban definiálva van.
  • --build: Először felépíti (vagy újraépíti, ha van változás) azokat az image-eket, amelyek build utasítást tartalmaznak (esetünkben a backend és a frontend).
  • -d: „Detached mode” – a konténerek a háttérben fognak futni, így a terminál azonnal felszabadul.

Ha minden rendben ment, látnod kell, ahogy a Docker letölti az alap image-eket (ha még nincsenek helyben), majd felépíti és elindítja a db, backend és frontend konténereket. Néhány pillanat múlva az alkalmazásodnak futnia kell!

Tesztelés:

  1. Nyisd meg a böngésződet, és navigálj a http://localhost/ címre. Látnod kell a frontend index.html oldalunkat.
  2. Kattints a „Backend API tesztelése” gombra. Látnod kell a „Hello from Backend API!” üzenetet. Ez bizonyítja, hogy az Nginx sikeresen proxyzza a kérést a backend felé.
  3. Kattints az „Adatbázis kapcsolat tesztelése” gombra. Ha minden jól ment, egy JSON objektumot kell látnod, ami jelzi a sikeres adatbázis kapcsolatot és az aktuális időt. Ez azt mutatja, hogy a Node.js backend sikeresen csatlakozott a PostgreSQL adatbázishoz.

Gratulálok! Sikeresen felépítettél és elindítottál egy teljes webalkalmazást Docker és Docker Compose segítségével!

Amikor befejezted a munkát, a következő paranccsal állíthatod le és törölheted a konténereket (de a db_data volume megmarad):


docker-compose down

Ha a volume-ot is törölni szeretnéd (és ezzel az adatbázis adatait), használd:


docker-compose down --volumes

További Tippek és Jó Gyakorlatok

Most, hogy megvan az alap, nézzünk néhány hasznos tippet és jó gyakorlatot:

  • Fejlesztői és Éles Környezet Különbségei: Gyakran más Docker Compose konfigurációt használnak fejlesztésre és éles üzemre. Fejlesztéshez érdemes lehet volume-okat csatolni a forráskódhoz (pl. ./backend:/app), hogy a változtatások azonnal megjelenjenek anélkül, hogy újra kellene építeni az image-et. Élesben viszont a beépített image-ek a preferáltak.
  • .dockerignore fájl: Hasonlóan a .gitignore-hoz, ez a fájl megmondja a Docker-nek, hogy mely fájlokat és könyvtárakat hagyja figyelmen kívül az image építése során. Pl. node_modules (ha az npm install a konténerben fut), .git, .env. Ez segít csökkenteni az image méretét és a build időt.
  • Multi-stage builds: Komplexebb alkalmazásoknál, főleg frontendek esetében, a multi-stage builds lehetővé teszi, hogy egy image-ben építsd fel az alkalmazást (pl. React build), majd egy másik image-be másold át csak a kész, statikus build fájlokat (pl. Nginx image-be). Ez drámaian csökkenti a végső image méretét.
  • Környezeti változók kezelése: Az érzékeny adatok (pl. adatbázis jelszavak) közvetlen beírása a docker-compose.yml-be nem biztonságos éles környezetben. Használhatsz .env fájlokat a Docker Compose-zal, vagy még jobb, titkos kezelő rendszereket (pl. Docker Secrets, Kubernetes Secrets).
  • Naplózás és Hibakeresés: A docker logs [konténer_neve] paranccsal megtekintheted egy adott konténer naplóit, ami kritikus fontosságú a hibakereséshez. A docker exec -it [konténer_neve] bash paranccsal beléphetsz egy futó konténerbe és parancsokat futtathatsz rajta.
  • Egyszerűségre törekvés: Kezdetben tartsuk egyszerűen a Dockerfile-okat és a Docker Compose konfigurációt. Csak akkor adjunk hozzá komplexitást, ha az feltétlenül szükséges.

Összefoglalás és Következtetések

Ebben a részletes útmutatóban megismerkedtél a Docker alapjaival, és lépésről lépésre felépítettél egy teljes webalkalmazást, ami egy Nginx frontendből, egy Node.js backendből és egy PostgreSQL adatbázisból állt. Láthattad, hogyan használjuk a Dockerfile-okat az egyes szolgáltatások konténerizálására, és a Docker Compose-t a teljes stack kezelésére.

A Docker nem csupán egy eszköz; egy szemléletmód, ami megkönnyíti a szoftverek fejlesztését, tesztelését és üzembe helyezését. Az alkalmazásaink izolált, konzisztens környezetben futnak, csökkentve a „nálam működik” problémát és felgyorsítva a fejlesztési ciklust. A megszerzett gyakorlati tudással most már képes vagy konténerizálni saját alkalmazásaidat, és a Docker erejét kihasználni a mindennapi munkádban.

Ez az út csak a kezdet. Fedezd fel a Docker Swarm-ot, a Kubernetes-t, a Docker Hub-ot és a CI/CD integrációkat. A konténerizálás világa hatalmas, és rengeteg lehetőséget rejt magában a modern szoftverfejlesztés számára. Boldog konténerizálást!

Leave a Reply

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük