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_moduleskihagyá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 apackage.jsonnem változik, a Docker a cache-ből veszi anpm ciréteget, és nem telepít újra mindent minden apró kódváltozásnál.npm cianpm installhelyett – aciapackage-lock.jsonalapján determinisztikusan telepít, és tisztán hagyja anode_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-alpineimage-ben már létezik egynodenevű, nem root felhasználó. Egyszerűen tedd ki aUSER nodesort — 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ítettnode_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!