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ține | Un sistem de operare complet, cu kernel propriu | Doar aplicația și dependențele ei |
| Kernel | Propriu, virtualizat | Partajat cu sistemul gazdă |
| Pornire | Zeci de secunde sau minute | De obicei sub o secundă sau câteva secunde |
| Dimensiune tipică | Gigaocteți | De la câțiva megaocteți la sute de megaocteți |
| Izolare | Foarte 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
nginxdin Docker Hub (doar prima dată). -dpornește containerul în fundal (detached).--name webîi dă un nume ușor de folosit.-p 8080:80leagă 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:
FROMalege imaginea de bază: Node.js 24 pe Alpine Linux, o distribuție foarte mică.WORKDIRsetează directorul de lucru din container.COPY package*.jsonșiRUN npm ciinstalează dependențele înainte de a copia restul codului (vezi secțiunea despre cache).EXPOSEdocumentează portul pe care ascultă aplicația; nu îl publică singur.USER noderulează aplicația cu un utilizator fără drepturi de administrator, existent deja în imaginile oficiale Node.js.CMDeste 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.
| Tip | Exemplu | Când îl folosești |
|---|---|---|
| Volum numit | -v date-pg:/var/lib/postgresql | Date 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_oncucondition: service_healthyporneș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 estedb, numele serviciului, nulocalhost.
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:
- 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. - 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. - 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 exemplupostgres: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
alpinesauslim, 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
- Pornește containerul nginx din acest ghid, apoi intră în el cu
docker exec -it web shși explorează fișierele. - Scrie un Dockerfile pentru un proiect personal și adaugă un
compose.yamlcu o bază de date. - Parcurge roadmap-ul Docker pentru subiecte precum registry-uri, securitatea imaginilor și integrarea în CI/CD.
- După ce te simți confortabil cu containerele, continuă cu Kubernetes, unde înveți să rulezi containere la scară, pe mai multe servere.