← Vissza a bloghoz Útmutató

Node.js alkalmazás dockerizálása lépésről lépésre

Egy valódi Node.js (Express) alkalmazás konténerbe csomagolása: optimalizált Dockerfile, .dockerignore, multi-stage build és Compose a fejlesztéshez.

A Node.js és a Docker párosa az egyik legkellemesebb belépő a konténerek világába: a Node ökoszisztéma könnyű, az image-ek gyorsan épülnek, és pár perc alatt eljuthatsz egy futó, hordozható alkalmazásig. Ebben a cikkben egy valódi Express appot csomagolunk konténerbe — megírjuk a Dockerfile-t, optimalizáljuk a cache-elésre, hozzáadunk egy multi-stage produkciós buildet, és a végén egy fejlesztői Compose setupot is kapsz hot reloaddal.

A példa alkalmazás

Induljunk egy minimális Express alkalmazásból. A projekt szerkezete:

my-app/
├── package.json
├── src/
│   └── index.js

A src/index.js tartalma:

const express = require("express");
const app = express();
const PORT = process.env.PORT || 3000;

app.get("/", (req, res) => {
  res.json({ message: "Helló a konténerből!" });
});

app.listen(PORT, () => {
  console.log(`Fut a ${PORT} porton`);
});

A package.json lényege:

{
  "name": "my-app",
  "version": "1.0.0",
  "main": "src/index.js",
  "scripts": {
    "start": "node src/index.js",
    "dev": "node --watch src/index.js"
  },
  "dependencies": {
    "express": "^4.19.2"
  }
}

Ha a Dockerfile alapjaival még nem vagy otthon, érdemes előbb átfutni a Dockerfile írása lépésről lépésre cikket — itt feltételezem, hogy az alapokat ismered.

A .dockerignore — ezzel kezdd!

Mielőtt bármit buildelnél, hozz létre egy .dockerignore fájlt. Ez megakadályozza, hogy a felesleges és káros fájlok bekerüljenek a build kontextusba:

node_modules
npm-debug.log
.git
.env
Dockerfile
.dockerignore
coverage

⚠️ Figyelem: A node_modules kihagyása nem opcionális! Ha a hoston telepített csomagok bekerülnek a konténerbe, könnyen platform-inkompatibilis natív bináris kódot viszel be (pl. macOS-en fordult modult Linux image-be). A függőségeket mindig a konténeren belül telepítsd.

Az alap Dockerfile

Most jöjjön a Dockerfile. A sorrend nem véletlen — a cache-elésre optimalizáltuk:

FROM node:20-alpine

WORKDIR /app

# Először csak a függőséglistát másoljuk be
COPY package*.json ./

# Reprodukálható telepítés a lockfile alapján
RUN npm ci --omit=dev

# Most jön a forráskód
COPY . .

EXPOSE 3000

CMD ["node", "src/index.js"]

Nézzük a fontos döntéseket:

  • node:20-alpine – az Alpine-alapú image jóval kisebb, mint a teljes Debian-alapú. Egy karcsú alap gyorsabb deploy és kisebb támadási felület.
  • COPY package*.json ./ a forrás előtt – ez a kulcs a gyors buildhez. Amíg a package.json nem változik, a Docker a cache-ből veszi a npm ci réteget, és nem telepít újra mindent minden apró kódváltozásnál.
  • npm ci a npm install helyett – a ci a package-lock.json alapján determinisztikusan telepít, és tisztán hagyja a node_modules-t. Pontosan ezt akarod egy reprodukálható buildben.

Build és futtatás

docker build -t my-app:1.0 .
docker run -d -p 3000:3000 --name my-app my-app:1.0

Ezután a http://localhost:3000 címen elérhető az alkalmazásod. A docker logs my-app paranccsal megnézheted a kimenetet, a docker stop my-app-pal pedig leállíthatod.

Multi-stage build a produkcióhoz

A fenti image működik, de tovább karcsúsítható és biztonságosabbá tehető. A multi-stage build lehetővé teszi, hogy a build során használt eszközöket ne vidd magaddal a végső image-be, és ne root-ként fuss:

# 1. szakasz: függőségek telepítése
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev

# 2. szakasz: a végső, karcsú futtató image
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production

# Csak a telepített függőségeket és a forrást másoljuk át
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Nem root felhasználóként futunk (a node user benne van az image-ben)
USER node

EXPOSE 3000
CMD ["node", "src/index.js"]

💡 Tipp: A node:20-alpine image-ben már létezik egy node nevű, nem root felhasználó. Egyszerűen tedd ki a USER node sort — ezzel jelentősen csökkented a kockázatot, ha valaki betörne a konténerbe.

Az ENV NODE_ENV=production beállítás sok könyvtárat is gyorsabb, biztonságosabb módba kapcsol, és az Express is ekkor a leghatékonyabb.

Fejlesztői Compose hot reloaddal

Fejlesztés közben kényelmetlen lenne minden mentésnél újraépíteni az image-et. A megoldás egy compose.yaml, amely a forráskódot bind mounttal köti be, így a változások azonnal látszanak. Használjuk a Node beépített --watch módját (a fenti dev script):

services:
  web:
    build: .
    command: npm run dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development

Két fontos részlet a volumes alatt:

  • .:/app – a teljes projektkönyvtárat beköti a konténerbe, így a kódmódosítások élőben látszanak.
  • /app/node_modules – ez egy anonim volume, amely “megvédi” a konténerben telepített node_modules-t attól, hogy a host (esetleg üres vagy más platformú) mappája felülírja. E nélkül a bind mount letakarná a konténer függőségeit.

Indítás:

docker compose up

Mostantól, ha szerkeszted a src/index.js-t, a Node automatikusan újraindítja a szervert a konténeren belül. A Compose alaposabb megértéséhez ajánlom a Docker Compose bevezető cikket.

Összefoglalás

Egy Node.js alkalmazás dockerizálása néhány jól átgondolt lépés: kezdd a .dockerignore-ral (a node_modules mindenképp menjen ki), építsd a Dockerfile-t cache-barát sorrendben (előbb package*.json, npm ci, aztán a forrás), majd produkcióra használj multi-stage buildet karcsú alappal és USER node-dal. Fejlesztéshez egy bind mountos compose.yaml ad kényelmes hot reloadot. Ezekkel az eszközökkel a “nálam működik” garantáltan “mindenhol működik” lesz.

Ha most ismerkedsz a konténerekkel, nézd meg a Kezdő lépések útmutatót, a telepítéshez pedig a Telepítés oldalt. Fogd a saját Express projektedet, másold be ezt a Dockerfile-t, és pár perc múlva már egy hordozható konténerben futtathatod!