Containerul tău a rulat brici săptămâni la rând. Apoi începe brusc să se restarteze la fiecare câteva ore. Te uiți în loguri și e beznă totală, nicio eroare utilă. Dai un docker ps și vezi că statusul face ping-pong între Up și Restarting. Singurul indiciu real este un exit code 137, care înseamnă că procesul a primit un SIGKILL direct. Pe un VPS, în 99% din cazuri, asta e semnătura clară că Linux Out-Of-Memory (OOM) killer-ul a intrat în acțiune.

Acest articol îți arată exact workflow-ul de debugging pentru a confirma un OOM kill, a depista procesul care a umplut memoria și a rezolva cauza de fond. Fără dat cu presupusul. Fără restarturi disperate ale VPS-ului în speranța că problema „se rezolvă de la sine”.

Ce înseamnă de fapt exit code 137

Exit code 137 este rezultatul matematic a două numere: 128 (baza pentru procesele terminate printr-un semnal extern) plus 9 (semnalul SIGKILL). Când Docker îți aruncă eroarea asta, înseamnă că procesul principal din container a fost oprit forțat, fără drept de apel, de o entitate externă.

Există doar două explicații logice:

  • OOM killer-ul din kernel-ul Linux a omorât procesul pentru că sistemul a rămas fără RAM și avea nevoie disperată să elibereze memorie ca să nu crape OS-ul.
  • Cineva (sau un script de automatizare) a executat manual docker kill sau kill -9 pe acel PID.

Dacă nu i-ai dat tu kill intenționat, e clar OOM killer-ul. Atenție: OOM killer-ul este un mecanism vital al kernel-ului Linux, nu o invenție Docker. E prezent pe orice distribuție și se activează automat când kernel-ul nu mai are de unde să aloce memorie. Rolul lui e cinic, dar necesar: alege un proces și îl execută ca să elibereze RAM.

Pe un VPS mic care nu are swap configurat (sau care are deja swap-ul plin ochi), OOM killer-ul nu stă la discuții. Lovește fie cel mai mare consumator de RAM, fie ultimul proces pornit, fie unul cu un scor mare de „badness” calculat de kernel. Victima poate fi baza ta de date, reverse proxy-ul sau chiar daemon-ul Docker în sine.

Pasul 1: Confirmă că a fost OOM kill

Nu trage concluzii pripite. Bate următoarele trei comenzi, fix în ordinea asta, ca să fii sigur.

1. Verifică docker inspect

docker inspect NUME_CONTAINER --format='{{.State.OOMKilled}} {{.State.ExitCode}}'

Dacă îți returnează true 137, Docker îți confirmă negru pe alb că OOM killer-ul este responsabil. Dacă scrie false 137, înseamnă că altceva a trimis SIGKILL-ul. Verifică-ți scripturile de monitorizare, pipeline-urile CI/CD sau istoricul de comenzi.

2. Verifică log-urile kernel-ului

sudo dmesg | grep -i "killed process"

Kernel-ul notează absolut fiecare acțiune a OOM killer-ului. Ar trebui să vezi o linie care arată cam așa:

[123456.789012] Out of memory: Killed process 12345 (postgres) total-vm:1048576kB, anon-rss:512000kB, file-rss:0kB, shmem-rss:0kB

Linia asta e aur: îți spune exact ce proces a picat, PID-ul lui și câtă memorie reală folosea în acel moment. Dacă vezi zeci de astfel de linii cu PID-uri diferite, înseamnă că OOM killer-ul rulează la foc automat și VPS-ul tău e gâtuit de memorie cronic.

3. Verifică docker stats

docker stats --no-stream

Comanda asta îți arată cât RAM și CPU suge fiecare container în timp real. Uită-te după containere cu un procent mare la MEM %. Mai important: dacă la coloana MEM LIMIT vezi valoarea totală a RAM-ului de pe server la fiecare container în parte, înseamnă că nu ai setat limite de memorie absolut deloc. Fix aia e cauza de fond.

Pasul 2: Găsește vinovatul care halește memoria

Odată ce ai confirmat OOM kill-ul, trebuie să afli ce anume din interiorul containerului a secătuit resursele. Iată suspecții de serviciu pentru setup-urile self-hosted pe VPS-uri.

Containere cu modele AI (Machine Learning)

Orice container care procesează inferență AI va arunca întregul model direct în RAM la pornire. Ollama, modulul de machine learning din Immich și alte servicii similare au nevoie de 2-8 GB de RAM doar ca să respire. Dacă tu ai un VPS de 2 GB, containerul o să-și ia OOM kill fix în momentul inițializării. Nu degeaba specificăm clar în ghidul de self-hosting pentru Immich că stack-ul de ML cere minimum 2-4 GB de RAM dedicați exclusiv pentru recunoaștere facială.

Baze de date lăsate cu setările default la conexiuni

PostgreSQL, MariaDB și Redis alocă memorie per conexiune activă. O configurație default de Postgres e gândită să accepte sute de conexiuni, fiecare ciupind câțiva megabytes de RAM. Matematica e simplă: dacă aplicația ta deschide un connection pool de 100 de conexiuni pe un VPS amărât de 2 GB, baza de date îți va culca serverul garantat.

Aplicații Java fără heap explicit

Prin definiție, JVM-ul (Java) își alocă automat un heap de 25% din RAM-ul total al sistemului. Pe un server de 4 GB, asta înseamnă 1 GB luat ostatic doar pentru heap, plus memoria necesară pentru thread-uri și spațiul off-heap. Dacă ai mai multe servicii Java pornite simultan, ele vor cere împreună mult mai mult RAM decât are host-ul fizic disponibil.

Build-uri rulate live cu docker compose up --build

Când construiești imagini Docker direct pe serverul de producție (în special proiecte Node.js sau Python), RAM-ul bubuie temporar la maxim. Un simplu npm install sau o compilare de pachete poate trigera OOM killer-ul care va ucide alte containere absolut nevinovate din sistem. Este exact avertismentul pe care îl dăm în ghidul pentru Coolify: un proces greu de build Nixpacks pe un VPS de 2 GB o să-și ia OOM kill înainte să apuce să termine de descărcat dependențele.

Pipeline-uri brutale de OCR și documente

Motoarele OCR precum Tesseract (folosit de Paperless-ngx) încarcă documentele pagină cu pagină în memorie. Dacă îi arunci un PDF scanat de 100 de pagini, RAM-ul va face un spike uriaș care durează minute întregi. În ghidul de arhitectură pentru Paperless-ngx am explicat de ce nu poți rula stack-ul cu mai puțin de 2 GB RAM, chiar și pentru un volum mic de documente.

Pasul 3: Cum oprești hemoragia acum (Soluții imediate)

Ai trei moduri de a opri ciclul de OOM kills, de la bandajul rapid la rezolvarea de fond.

Soluția A: Pune limite hard de memorie pe container

Limitezi direct "blast radius-ul". Dacă limitezi RAM-ul la nivel de container și acesta face overcommit, Docker va ucide strict acel container. Restul sistemului, baza de date și celelalte servicii rămân neatinse. Adaugă blocul ăsta în docker-compose.yml:

services:
  app:
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "1.0"

Ajustează valoarea pe logica serviciului. Un Postgres mic poate sta în 1 GB. Un API simplu de Node.js poate fi strâns la 256 MB. Regula de aur e să pui o limită globală adunată care să fie sub memoria fizică a serverului tău.

Soluția B: Configurează un fișier de Swap

Swap-ul nu înlocuiește RAM-ul, dar îți cumpără timp. Când memoria se umple, kernel-ul mută datele inactive pe SSD. Asta te salvează de la OOM kills în timpul spike-urilor temporare (cum ar fi un build Docker sau importul unui fișier imens).

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

Pentru persistență după reboot, lipește asta în /etc/fstab:

/swapfile none swap sw 0 0

Pe un VPS cu SSD, 2 GB de swap sunt de ajuns pentru absorbția șocurilor. Nu pica în capcana de a te baza pe swap pentru consum susținut. Dacă containerul tău face swap încontinuu, îți omoară IOPS-urile discului; are nevoie de RAM real, nu de bandaje.

Soluția C: Fă upgrade la VPS (Scale up)

Dacă ai pus deja limite, ai adăugat swap, dar containerele încă se lovesc de tavan, înseamnă că ești subdimensionat. E o poveste clasică în self-hosting: începi cu un simplu reverse proxy, pui un wiki, arunci și o bază de date, iar 2 GB de RAM ajung să fie o glumă proastă.

La noi, poți face upgrade la un VPS KVM ServerSpan on-the-fly, fără să pierzi date. Dacă treci la 4 GB RAM, ai loc suficient pentru a respira fără să fii forțat să faci micro-management la swap și memory limits în fiecare săptămână.

Pasul 4: Fă tuning direct în interiorul aplicației

Limitele din Docker previn crash-ul întregului OS. Ca să previi crash-ul containerului, trebuie să-i tai pofta de RAM direct din fișierele lui de configurare.

PostgreSQL: strânge cureaua la max_connections

max_connections = 50
shared_buffers = 128MB

Nu lăsa pe default (100+ conexiuni) pe un server mic. 50 de conexiuni pentru un setup de self-hosting pe un VPS de 2 GB sunt mai mult decât suficiente.

Redis: forțează maxmemory

maxmemory 256mb
maxmemory-policy allkeys-lru

Dacă nu-l limitezi, Redis o să tot scrie în RAM până dă de OOM killer. Setarea allkeys-lru curăță elegant cheile vechi și nefolosite atunci când atinge cei 256 MB.

Java: setează clar Xmx și Xms

-Xmx512m -Xms256m

Oprește JVM-ul din a-și aloca aberant un sfert din RAM-ul VPS-ului tău. Așa îl blochezi fix la 512 MB de heap.

Nextcloud: limitează workerii PHP-FPM

Imaginile oficiale de Nextcloud vin cu pool-uri agresive pe PHP-FPM. Dacă lași 50 de workeri pe 2 GB de RAM, îți va crăpa la prima sincronizare serioasă a desktop client-ului. Vezi în ghidul nostru de stabilitate Nextcloud formula matematică clară ca să setezi fix numărul de workeri pe care VPS-ul tău îi poate duce.

Pasul 5: Pune monitorizare ca să vezi dezastrul înainte să crape

OOM kill-ul n-ar trebui să fie o surpriză. Păzește-ți spatele cu niște alerte de bază.

Urmărește consumul live din terminal

watch -n 5 docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"

Comanda face refresh la fiecare 5 secunde. Dacă vezi un container la care procentul crește continuu, ai un memory leak (scurgere de memorie).

Verifică corect RAM-ul cu comanda free

free -h

Regula numărul 1 la sysadmini pe Linux: nu te uita la coloana free, uită-te la available. Linux-ul bagă RAM-ul „liber” în buffere de disc, așa că valoarea available îți arată cât RAM este efectiv disponibil pentru o alocare nouă. Dacă scade sub 10% din total, urmează un OOM kill.

Bagă un script de alertare în cron

#!/bin/bash
AVAILABLE=$(free | awk '/Mem:/ {printf "%.0f", $7/$2 * 100}')
if [ "$AVAILABLE" -lt 15 ]; then
  echo "AVERTIZARE: Doar ${AVAILABLE}% RAM disponibil pe $(hostname)" | logger
fi

Pune asta să ruleze din minut în minut. Când available scade sub 15%, scrie o alertă în syslog. De aici, poți trimite simplu log-ul pe email sau spre un webhook de Discord/Telegram.

Workflow-ul clar de troubleshooting pentru OOM

Nu sări etapele. Urmează exact pașii ăștia data viitoare când moare un container:

  1. Dai un docker inspect. E OOMKilled pe true? Dacă nu, ia la puricat scripturile că cineva i-a dat kill manual.
  2. Dai un dmesg | grep "killed process". Afli cine a picat și cu cât RAM în brațe.
  3. Rulezi docker stats ca să vezi cine-i cel mai gras consumator activ.
  4. Verifici limitele de RAM pe container. Dacă n-ai, pui una imediat în Compose.
  5. Verifici dacă ai swap activ pe VPS. Dacă nu, faci un fișier de 1-2 GB.
  6. Intri în config-ul aplicației și tai din connection pools, limitezi heap-ul sau tai din workeri.
  7. Dacă ai făcut tot tuning-ul corect și containerul încă suferă, scoate portofelul și mută-te pe un plan cu mai mult RAM.

Greșeli fatale (Ce să NU faci)

  • Nu opri sub nicio formă OOM killer-ul. El e acolo fix ca să nu-ți crape kernel-ul complet. Fără el, tot VPS-ul va face freeze.
  • Nu pune restart: always la nimereală pe containere fără limite de memorie. Dacă containerul ia OOM kill și se restartează de 50 de ori pe oră, îți toacă CPU-ul și I/O-ul discului scriind gigabytes de log-uri de eroare.
  • Nu crea un fișier de swap de 10 GB pe un server de 2 GB. Îți vei transforma SSD-ul într-un bottleneck oribil și serverul va deveni lent ca un melc fără să pice efectiv.
  • Nu ignora exit code-ul 137 crezând că e „o fiță de la Docker”. Este confirmarea strictă matematică a faptului că ți-ai depășit resursele.

Checklist pentru prevenție pe termen lung

  • Setează limite de memorie pe 100% din containerele expuse în producție.
  • Setează limite de CPU dacă ai containere care compilează cod sau fac procesări masive de tip batch.
  • Tunează parametrii din baze de date ca să nu depășească memoria fizică disponibilă a host-ului.
  • Pentru orice aplicație Java, declară explicit mărimea de heap în environment variables.
  • Aruncă mereu 1-2 GB de swap pentru plasa de siguranță la spike-uri.
  • Stai cu ochii pe docker stats primele ore după un deployment nou.
  • Lasă obligatoriu un buffer de 20-30% din RAM liber strict pentru overhead-ul sistemului de operare și al Docker daemon-ului.
  • Ai grijă cu ce unealtă faci backup. Comparația BorgBackup vs Restic îți arată clar care dintre ele știe să gestioneze corect memoria pe servere mici fără să ceară OOM kill în timpul proceselor masive de deduplicare.

Când soluția este pur și simplu hardware mai puternic

Ajungi rapid la un prag la care niciun tutorial de tuning nu te mai ajută. Dacă încerci să rulezi pe același VPS de 2 GB un Nextcloud (care generează cache video), un Immich (care rulează scanări ML de față) și o instanță grea de PostgreSQL, faci echilibristică pe gheață. Pur și simplu agregatele cer mai mult hardware.

Pentru stack-uri care conțin elemente de OCR, Java masiv sau baze de date grele, instanța minimă recomandată este un VPS KVM ServerSpan din clasa ct.Steady (4 Core, 4 GB RAM, 50 GB SSD). Trecerea la hardware superior este singura opțiune sustenabilă când ai plafonat eficienta optimizărilor software.

Costul unui upgrade mic de VPS se amortizează instant dacă pui în balanță orele pierdute citind loguri, coruperea datelor la fiecare hard kill luat de baza de date și downtime-ul aferent. Urmează pașii în ordinea logică: 1. Diagnostichează clar. 2. Tunează configurațiile. 3. Mărește resursele fizice (Scale up).

Iar dacă vrei o perspectivă mai tehnică despre cum să eviți picajele în producție cauzate strict de neglijența în administrare, citește analiza incidentului Docker 29 — o radiografie clară despre cum auto-update-urile la orb, healthcheck-urile ignorate și scripturile netestate duc fix la aceleași downtime-uri penibile.

Sursă și Atribuire

Aceast articol se bazează pe date originale ale serverspan.com. Pentru metodologia completă și pentru a asigura integritatea datelor, articolul original trebuie citat. Sursa canonică este disponibilă la: Containerul Docker mi-a mâncat tot RAM-ul de pe VPS: cum diagnostichezi și repari OOM Kills.