Toate ghidurile

Docker explicat pe înțeles: imagini, containere, volume și Compose

Ghid practic Docker: diferența dintre imagini și containere, Dockerfile, volume, rețele și Docker Compose, cu exemple pe care le rulezi imediat.


„La mine merge” este probabil cea mai cunoscută frază din programare: aplicația funcționează pe un calculator, dar nu și pe al colegului sau pe server, din cauza unei versiuni diferite de Node.js, a unei biblioteci lipsă sau a unei configurări uitate. Docker rezolvă această problemă împachetând aplicația împreună cu tot ce are nevoie ca să ruleze. În ghidul acesta afli ce sunt imaginile, containerele, volumele și Compose, cu exemple pe care le poți rula imediat.

Ce problemă rezolvă Docker

Fără containere, ca să rulezi o aplicație trebuie să instalezi manual pe fiecare mașină versiunea corectă a limbajului, baza de date, dependențele de sistem și variabilele de mediu. Fiecare diferență între mașini este o sursă posibilă de bug-uri.

Cu Docker, descrii toate acestea o singură dată, într-un fișier, iar rezultatul rulează la fel pe laptopul tău, pe al colegului și pe server. Beneficii concrete:

  • Mediu identic în dezvoltare, testare și producție.
  • Pornire rapidă în proiecte noi: în loc de o pagină de instrucțiuni de instalare, rulezi o comandă.
  • Izolare: poți avea două proiecte care cer versiuni diferite de PostgreSQL, fără conflicte.
  • Curățenie: când nu mai ai nevoie de un serviciu, ștergi containerul și nu rămâne nimic instalat în sistem.

Container vs. mașină virtuală

O comparație frecventă este cea cu mașinile virtuale. Diferența ține de ce anume se izolează.

Mașină virtualăContainer
Ce conțineUn sistem de operare complet, cu kernel propriuDoar aplicația și dependențele ei
KernelPropriu, virtualizatPartajat cu sistemul gazdă
PornireZeci de secunde sau minuteDe obicei sub o secundă sau câteva secunde
Dimensiune tipicăGigaoctețiDe la câțiva megaocteți la sute de megaocteți
IzolareFoarte puternicăBună, dar mai slabă decât la o mașină virtuală

Containerele Docker folosesc funcționalități ale kernel-ului Linux. Pe Windows și macOS, Docker Desktop rulează în spate o mică mașină virtuală Linux, dar acest detaliu este transparent pentru tine.

Conceptele de bază

  • Imagine (image): un șablon read-only care conține sistemul de fișiere al aplicației: codul, runtime-ul, bibliotecile. Gândește-te la ea ca la o clasă.
  • Container: o instanță care rulează dintr-o imagine. Din aceeași imagine poți porni oricâte containere, la fel cum creezi mai multe obiecte dintr-o clasă.
  • Dockerfile: fișierul text cu instrucțiunile pentru construirea unei imagini.
  • Registry: locul din care descarci și în care publici imagini. Cel mai cunoscut este Docker Hub, dar există și registry-uri private sau oferite de platformele cloud.
  • Volum (volume): spațiu de stocare care persistă independent de container.
  • Compose: unealta prin care descrii și pornești mai multe containere împreună.

Instalare și primul container

Pe Windows și macOS instalezi Docker Desktop; pe Linux instalezi Docker Engine conform documentației oficiale. Verifici instalarea cu:

docker version
docker run hello-world

Acum pornește un server web real:

docker run -d --name web -p 8080:80 nginx

Deschide http://localhost:8080 în browser și vei vedea pagina de start nginx. Ce s-a întâmplat:

  • Docker a descărcat imaginea nginx din Docker Hub (doar prima dată).
  • -d pornește containerul în fundal (detached).
  • --name web îi dă un nume ușor de folosit.
  • -p 8080:80 leagă portul 8080 de pe calculatorul tău de portul 80 din container.

Comenzile pe care le folosești zilnic

docker ps                     # containerele care rulează
docker ps -a                  # toate containerele, inclusiv cele oprite
docker logs -f web            # log-urile containerului, în timp real
docker exec -it web sh        # deschide un shell în containerul care rulează
docker stop web               # oprește containerul
docker start web              # îl pornește din nou
docker rm web                 # șterge containerul oprit
docker image ls               # imaginile descărcate sau construite local
docker pull postgres:18       # descarcă o imagine fără să o pornească
docker image rm nginx         # șterge o imagine

docker exec -it este foarte util la depanare: intri în container și verifici fișierele, variabilele de mediu sau conexiunea la alte servicii, ca și cum ai fi pe un server.

Construiește propria imagine cu un Dockerfile

Să presupunem că ai o aplicație Node.js simplă, cu package.json și server.js, care ascultă pe portul 3000. Un Dockerfile pentru ea:

FROM node:24-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --omit=dev

COPY . .

ENV NODE_ENV=production
EXPOSE 3000
USER node

CMD ["node", "server.js"]

Pe rând:

  • FROM alege imaginea de bază: Node.js 24 pe Alpine Linux, o distribuție foarte mică.
  • WORKDIR setează directorul de lucru din container.
  • COPY package*.json și RUN npm ci instalează dependențele înainte de a copia restul codului (vezi secțiunea despre cache).
  • EXPOSE documentează portul pe care ascultă aplicația; nu îl publică singur.
  • USER node rulează aplicația cu un utilizator fără drepturi de administrator, existent deja în imaginile oficiale Node.js.
  • CMD este comanda executată la pornirea containerului.

Adaugă și un fișier .dockerignore, ca să nu copiezi în imagine fișiere inutile sau secrete:

node_modules
.git
.env
*.log

Construiești și rulezi imaginea:

docker build -t magazin-api:1.0 .
docker run -d --name api -p 3000:3000 magazin-api:1.0

Straturi și cache

Fiecare instrucțiune din Dockerfile creează un strat (layer). La următorul build, Docker refolosește straturile care nu s-au schimbat. De aceea copiem întâi doar package.json și instalăm dependențele: dacă modifici doar codul, pasul npm ci (de obicei cel mai lent) vine din cache, iar build-ul durează câteva secunde.

Dacă ai scrie COPY . . înainte de npm ci, orice modificare într-un fișier sursă ar invalida cache-ul și ar reinstala toate dependențele.

Multi-stage builds

Pentru aplicațiile care au un pas de build, poți folosi mai multe etape și păstra în imaginea finală doar rezultatul. Exemplu pentru un frontend construit cu Vite și servit de nginx:

FROM node:24-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html

Imaginea finală conține doar nginx și fișierele statice, fără Node.js, fără node_modules și fără codul sursă.

Volume: datele care trebuie să supraviețuiască

Sistemul de fișiere al unui container este temporar: când ștergi containerul, se pierde tot ce s-a scris în el. Pentru o bază de date, asta ar fi un dezastru. Soluția sunt volumele.

TipExempluCând îl folosești
Volum numit-v date-pg:/var/lib/postgresqlDate persistente gestionate de Docker (baze de date)
Bind mount-v "$(pwd)":/appÎn dezvoltare, ca modificările din editor să apară imediat în container
docker volume create date-pg
docker run -d --name db \
  -e POSTGRES_PASSWORD=parola-locala \
  -v date-pg:/var/lib/postgresql \
  -p 5432:5432 \
  postgres:18

Acum poți șterge și recrea containerul db; datele rămân în volumul date-pg. Vezi volumele existente cu docker volume ls.

Rețele: cum comunică containerele

Fiecare container are propria interfață de rețea. Cea mai frecventă greșeală a începătorilor: aplicația din container încearcă să se conecteze la baza de date pe localhost. În interiorul unui container, localhost înseamnă containerul însuși, nu calculatorul tău și nici alt container.

Containerele aflate în aceeași rețea Docker definită de utilizator se găsesc între ele după nume. Compose creează automat o astfel de rețea pentru fiecare proiect, așa că aplicația se conectează la baza de date folosind numele serviciului, de exemplu db.

Docker Compose: mai multe servicii, o singură comandă

O aplicație reală are de obicei mai multe componente: API, bază de date, poate un cache. În loc de comenzi docker run lungi, le descrii într-un fișier compose.yaml:

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:${POSTGRES_PASSWORD}@db:5432/magazin
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:18
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_DB: magazin
    volumes:
      - date-pg:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d magazin"]
      interval: 5s
      timeout: 3s
      retries: 10

volumes:
  date-pg:

Parola nu este scrisă direct în fișier: Compose citește automat variabilele dintr-un fișier .env aflat în același director, pe care îl adaugi în .gitignore:

POSTGRES_PASSWORD=o-parola-locala-lunga

Câteva observații:

  • Câmpul version: din fișierele Compose mai vechi nu mai este necesar; versiunile actuale îl ignoră și afișează un avertisment.
  • depends_on cu condition: service_healthy pornește API-ul abia după ce baza de date răspunde la verificarea de sănătate, nu doar după ce containerul a pornit.
  • În DATABASE_URL, host-ul este db, numele serviciului, nu localhost.

Comenzile Compose folosesc sintaxa v2, cu spațiu (docker compose), nu vechiul docker-compose:

docker compose up -d            # pornește toate serviciile în fundal
docker compose up -d --build    # reconstruiește imaginile înainte de pornire
docker compose ps               # starea serviciilor
docker compose logs -f api      # log-urile unui serviciu
docker compose exec db psql -U app -d magazin   # consolă SQL în baza de date
docker compose down             # oprește și șterge containerele și rețeaua
docker compose down -v          # la fel, dar șterge și volumele (datele!)

Atenție la down -v: șterge volumele declarate în fișier, deci și datele din baza de date. Folosește-l doar când vrei explicit să pornești de la zero.

Pentru dezvoltare, Compose are și comanda docker compose watch, care poate sincroniza fișierele modificate în container sau reconstrui imaginea automat; detaliile sunt în documentația Compose.

Curățenie

În timp, imaginile vechi, containerele oprite și cache-ul de build ocupă mult spațiu pe disc.

docker system df            # cât spațiu ocupă fiecare categorie
docker container prune      # șterge containerele oprite
docker image prune          # șterge imaginile fără tag (dangling)
docker system prune         # containere oprite, rețele nefolosite, imagini dangling, cache de build

Comenzile prune cer confirmare înainte de ștergere. Citește lista afișată și nu adăuga --volumes decât dacă ești sigur că nu ai date importante în volumele nefolosite.

Cum arată Docker într-un proiect real

În echipe, Docker apare de obicei în trei locuri:

  1. Dezvoltare locală. Mulți programatori rulează aplicația direct pe calculator, pentru viteză și debugging comod, dar pornesc dependențele (baza de date, Redis, un broker de mesaje) cu docker compose up -d. Un coleg nou are tot mediul funcțional în câteva minute.
  2. CI/CD. La fiecare modificare pe main, pipeline-ul construiește imaginea, rulează testele în ea și o publică într-un registry cu un tag unic, de exemplu hash-ul commit-ului.
  3. Producție. Serverul sau platforma de orchestrare descarcă exact acea imagine și o pornește. Ce a fost testat este exact ce rulează.

Pentru proiectele personale, punctul 1 este cel mai util de exersat primul: îți ține sistemul curat și te obișnuiește cu comenzile de bază.

Greșeli frecvente

  • Folosirea tag-ului latest în producție. Nu știi exact ce versiune rulezi, iar un build nou poate aduce schimbări neașteptate. Folosește versiuni explicite, de exemplu postgres:18.
  • Secrete în imagine. Orice fișier copiat sau variabilă setată cu ENV în Dockerfile rămâne în imagine și poate fi extras. Transmite secretele la rulare.
  • Date importante fără volum. Dacă baza de date nu are volum, datele dispar odată cu containerul.
  • localhost între containere. Folosește numele serviciului.
  • Imagini uriașe. Pornește de la imagini alpine sau slim, folosește .dockerignore și multi-stage builds.
  • Rularea ca root. Adaugă USER în Dockerfile când imaginea de bază oferă un utilizator neprivilegiat.

Pașii următori

  1. Pornește containerul nginx din acest ghid, apoi intră în el cu docker exec -it web sh și explorează fișierele.
  2. Scrie un Dockerfile pentru un proiect personal și adaugă un compose.yaml cu o bază de date.
  3. Parcurge roadmap-ul Docker pentru subiecte precum registry-uri, securitatea imaginilor și integrarea în CI/CD.
  4. După ce te simți confortabil cu containerele, continuă cu Kubernetes, unde înveți să rulezi containere la scară, pe mai multe servere.