Răspuns scurt
- Nu lași node_modules și npm pe serverul de producție pentru că fiecare pachet instalat acolo e cod străin executabil, cu scripturi de instalare care rulează pe mașina ta și cu dependențe de dezvoltare inutile aplicației.
- Construiești aplicația în CI, din package-lock.json, și livrezi pe server doar artefactele și dependențele de producție.
- Dacă totuși instalezi pe server, folosești npm ci --omit=dev --ignore-scripts, niciodată npm install.
- În Docker separi etapa de build de imaginea finală, ca runtime-ul să nu conțină npm, compilatoare sau devDependencies.
- Comanda de instalare pe producție
- npm ci --omit=dev --ignore-scripts
- Fișier obligatoriu pentru npm ci
- package-lock.json
- Vectorul Shai-Hulud (2025)
- script preinstall în pachete compromise
Ce se întâmplă când node_modules rămâne pe server
Un npm install rulat pe producție descarcă sute de pachete și execută scripturile lor preinstall și postinstall cu utilizatorul aplicației. Dacă unul e compromis, atacatorul are acces la variabilele de mediu, la fișierele .env și la rețeaua internă. Fără lockfile respectat strict primești versiuni diferite de cele testate în CI, deci un bug de pe server nu se reproduce nicăieri. Cache-ul npm poate păstra token-uri de registru privat, iar devDependencies aduc compilatoare și unelte de test pe o mașină care ar trebui să ruleze doar aplicația.
De ce npm ci cu --omit=dev și --ignore-scripts, nu npm install
npm ci refuză să ruleze fără package-lock.json, șterge node_modules și instalează exact versiunile din lockfile, deci obții același arbore ca în CI. --omit=dev exclude devDependencies. --ignore-scripts oprește preinstall și postinstall, vectorul folosit de Shai-Hulud în noiembrie 2025. Nici așa nu elimini riscul supply-chain: chalk și debug erau dependențe de producție când au fost compromise. Controlul real e să nu instalezi deloc pe server, ci să construiești o singură dată în CI, unde scanezi pachetele.
Ce spune legislația din România
OUG 155/2024, care transpune NIS2 și e aprobată prin Legea 124/2025, cere entităților înregistrate la DNSC măsuri pentru securitatea lanțului de aprovizionare, inclusiv pentru componentele software terțe. Un proces de build reproductibil, cu lockfile și fără instalări ad-hoc pe producție, e dovada cea mai simplă că știi ce cod rulează. Dacă aplicația prelucrează date personale, art. 32 GDPR cere măsuri tehnice adecvate, iar ANSPDCP verifică exact acest control la un incident.
Unde rulează fiecare pas în lanțul de livrare Node.js| Pas | În CI | Pe producție | Motiv |
|---|
| Rezolvare versiuni (npm install) | Da | Nu | Versiunile se decid o singură dată, în lockfile |
|---|
| Scripturi preinstall / postinstall | Da, controlat | Nu | Vectorul atacurilor supply-chain din 2025 |
|---|
| Build (tsc, bundler) | Da | Nu | Compilatoarele nu au loc pe runtime |
|---|
| Rulare aplicație | Nu | Da | Doar artefacte și dependențe de producție |
|---|
Comenzi și configurări
Instalare reproductibilă, fără devDependencies și fără scripturinpm ci --omit=dev --ignore-scripts
.npmrc pe server și în CIignore-scripts=true
fund=false
Dockerfile multi-stage fără npm în runtimeFROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --ignore-scripts
COPY . .
RUN npm run build && npm prune --omit=dev
FROM node:22-alpine AS runtime
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
USER node
CMD ["node", "dist/server.js"]
Exemple practice
SaaS din Iași, API Node.js pe VPS
Echipa rula npm install la fiecare deploy, direct pe server. După ce a mutat build-ul în GitHub Actions și a livrat doar directorul dist plus dependențele de producție, a dispărut o clasă întreagă de erori de tip „merge la mine, nu merge pe server”.
Agenție web din Timișoara, site Next.js
Un pachet de dezvoltare compromis a fost prins în CI de scanerul de dependențe, înainte de deploy. Pentru că serverul de producție nu avea npm și nici devDependencies, incidentul s-a oprit la pipeline, fără să atingă datele clienților.
Greșeli frecvente
- Crezi că npm install --production pe server e la fel de sigur: fără lockfile nu e reproductibil, iar scripturile de instalare rulează oricum.
- Crezi că --omit=dev elimină riscul supply-chain, deși dependențele de producție pot fi și ele compromise.
- Nu commitezi package-lock.json și lași fiecare deploy să rezolve versiunile din nou.
- Păstrezi npm și compilatoarele în imaginea Docker finală pentru „depanare rapidă”.
Cum te ajută 4hosting
- Abonamentele VPS includ lunar 1–8 ore de DevOps, serverele dedicate 5–10 ore, în funcție de plan, iar cele GPU 10 ore; le ceri din panoul de client, secțiunea „Servicii DevOps”, de exemplu pentru a muta build-ul în CI.
- Serviciul CI/CD & Monitoring configurează GitHub Actions sau GitLab CI cu build reproductibil și scanare de dependențe.
- Serviciul Docker & Kubernetes construiește imagini multi-stage fără npm în runtime.
Întrebări conexe
Pot rula npm ci --ignore-scripts dacă am pachete native, cum e sharp?
Da. Multe pachete native nu mai compilează la instalare: sharp, de pildă, livrează binare prin dependențe opționale de la versiunea 0.33, deci --ignore-scripts nu îl afectează, dar --omit=optional l-ar rupe. Pentru pachetele care chiar au nevoie de scriptul lor, rulezi npm ci --ignore-scripts pe tot arborele, apoi npm rebuild <pachet> explicit. Așa lista scripturilor permise rămâne scurtă și cunoscută.
Ce e diferit între npm ci și npm install?
npm ci cere lockfile, șterge node_modules și instalează exact versiunile fixate, fără să modifice package-lock.json. npm install rezolvă versiunile din nou și poate rescrie lockfile-ul, deci două rulări pot da arbori diferiți. Pe producție și în CI folosești întotdeauna npm ci.
Trebuie să fac asta și pentru un site mic cu câteva pachete?
Da, pentru că numărul de pachete declarate direct nu spune nimic despre arborele complet, care ajunge frecvent la sute de dependențe tranzitive. Un Dockerfile multi-stage sau un pipeline simplu de build costă câteva ore o singură dată.