====== Dockerfile - Construirea unei imagini proprii de la zero ======
Acest articol explică ce este un **Dockerfile**, cum funcționează sistemul de straturi (layers) al Docker și cum poți construi o imagine personalizată pas cu pas.
----
===== Ce este un Dockerfile? =====
Un **Dockerfile** este un fișier text simplu, fără extensie, care conține o succesiune de instrucțiuni ce descriu cum se construiește o **imagine Docker**.
Procesul de build urmează pașii:
- Scrii un ''Dockerfile''
- Rulezi ''docker build''
- Docker produce o **imagine** (read-only, formată din straturi)
- Rulezi imaginea ca **container** (adaugă un strat read-write deasupra)
- Opțional, distribui imaginea printr-un **Registry** (Docker Hub, ECR etc.)
----
===== Structura de bază =====
Fiecare linie dintr-un Dockerfile este o **instrucțiune**. Ordinea contează - Docker execută instrucțiunile de sus în jos și creează câte un **strat (layer)** pentru fiecare.
# Comentariile încep cu #
INSTRUCȚIUNE argumente
----
===== Instrucțiunile esențiale =====
==== FROM ====
**Obligatorie. Prima instrucțiune.** Specifică imaginea de bază (//base image//).
FROM ubuntu:22.04
FROM node:20-alpine
FROM python:3.12-slim
FROM scratch # imagine goală, pentru binare statice
> **Recomandare:** Folosește variante ''slim'' sau ''alpine'' pentru imagini mai mici. Evită ''latest'' - fixează versiunea explicit.
==== WORKDIR ====
Setează directorul de lucru pentru toate instrucțiunile următoare. Dacă nu există, îl creează.
WORKDIR /app
Echivalentul unui ''cd /app'' persistent în imagine.
==== COPY și ADD ====
Copiază fișiere din contextul de build în imagine.
COPY src/ /app/src/ # copiază directorul src/
COPY package.json . # copiază în WORKDIR curent
ADD archive.tar.gz /data/ # ADD dezarhivează automat
^ Instrucțiune ^ Dezarhivare automată ^ URL remote ^
| ''COPY'' | Nu | Nu |
| ''ADD'' | Da (.tar, .gz) | Da |
> **Recomandare:** Preferă ''COPY'' în locul ''ADD'' dacă nu ai nevoie de dezarhivare - comportamentul este mai previzibil.
==== RUN ====
Execută comenzi în shell **la momentul build-ului**. Fiecare ''RUN'' creează un strat nou.
RUN apt-get update && apt-get install -y curl git
RUN npm install
RUN pip install -r requirements.txt
> **Recomandare:** Combină comenzile cu ''&&'' pe o singură linie ''RUN'' pentru a reduce numărul de straturi și dimensiunea imaginii.
# Bine
RUN apt-get update \
&& apt-get install -y curl git \
&& rm -rf /var/lib/apt/lists/*
# Evită (3 straturi separate)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git
==== ENV ====
Setează variabile de mediu disponibile atât la build, cât și la runtime.
ENV NODE_ENV=production
ENV PORT=3000 HOST=0.0.0.0
==== ARG ====
Variabile disponibile **doar la build-time**, nu în containerul final.
ARG VERSION=1.0
ARG SECRET_KEY # transmis cu --build-arg SECRET_KEY=xyz
> **Atenție:** Nu folosi ''ARG'' pentru secrete - valorile sunt vizibile în istoricul imaginii (''docker history'').
==== EXPOSE ====
Documentează portul pe care aplicația ascultă. Nu publică portul - doar înregistrează intenția.
EXPOSE 3000
EXPOSE 8080/tcp
EXPOSE 5353/udp
Publicarea efectivă se face la rulare cu ''-p 3000:3000''.
==== CMD și ENTRYPOINT ====
Definesc comanda implicită la pornirea containerului.
^ Instrucțiune ^ Scop ^ Poate fi suprascris ^
| ''CMD'' | Comandă implicită | Da (''docker run ... mycommand'') |
| ''ENTRYPOINT''| Punctul de intrare fix | Greu (''--entrypoint'') |
# Forma exec (preferată - fără shell intermediar)
CMD ["node", "server.js"]
ENTRYPOINT ["python", "-m", "gunicorn"]
# Forma shell (evită)
CMD node server.js
**Combinarea lor:**
ENTRYPOINT ["python", "app.py"]
CMD ["--port", "8080"] # argumente implicite, suprascribile
==== VOLUME ====
Declară un punct de montare pentru date persistente.
VOLUME ["/data", "/logs"]
==== USER ====
Schimbă utilizatorul pentru instrucțiunile următoare și pentru procesul final.
RUN useradd -m appuser
USER appuser
> **Recomandare de securitate:** Rulează întotdeauna aplicațiile ca utilizator non-root.
==== LABEL ====
Adaugă metadate la imagine (autor, versiune, descriere).
LABEL maintainer="echipa@exemplu.ro"
LABEL version="2.1.0"
LABEL description="API REST pentru serviciul X"
==== HEALTHCHECK ====
Definește o comandă pentru verificarea stării containerului.
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD curl -f http://localhost:3000/health || exit 1
----
===== Exemplu complet: aplicație Node.js =====
# ── Etapa 1: dependențe ──────────────────────────────────────
FROM node:20-alpine AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
# ── Etapa 2: build ───────────────────────────────────────────
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# ── Etapa 3: imagine finală ───────────────────────────────────
FROM node:20-alpine AS final
LABEL maintainer="dev@exemplu.ro"
LABEL version="1.0.0"
ENV NODE_ENV=production
ENV PORT=3000
WORKDIR /app
# Copiază doar ce e necesar
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist
# Utilizator non-root
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s \
CMD wget -qO- http://localhost:3000/health || exit 1
CMD ["node", "dist/server.js"]
Aceasta folosește **multi-stage build** - o tehnică prin care imaginea finală conține doar artefactele necesare, nu întregul toolchain de build.
----
===== Fișierul .dockerignore =====
Similar cu ''.gitignore'', exclude fișiere din contextul de build (măresc dimensiunea și pot expune date sensibile).
# .dockerignore
node_modules/
.git/
*.log
.env
.env.local
dist/
coverage/
**/*.test.ts
Dockerfile
docker-compose*.yml
README.md
----
===== Comenzi de lucru =====
# Construiește imaginea cu tag
docker build -t myapp:1.0 .
# Specifică un Dockerfile cu alt nume
docker build -f Dockerfile.prod -t myapp:prod .
# Transmite argumente de build
docker build --build-arg VERSION=2.0 -t myapp:2.0 .
# Fără cache (rebuild complet)
docker build --no-cache -t myapp:latest .
# Construiește o anumită etapă (multi-stage)
docker build --target builder -t myapp:debug .
# Rulează containerul
docker run -d -p 3000:3000 --name myapp myapp:1.0
# Rulează interactiv (debug)
docker run -it --rm myapp:1.0 sh
# Inspectează straturile imaginii
docker history myapp:1.0
# Publică imaginea
docker tag myapp:1.0 utilizator/myapp:1.0
docker push utilizator/myapp:1.0
----
===== Bune practici =====
=== 1. Ordinea instrucțiunilor - cache eficient ===
Docker reutilizează layerele din cache dacă instrucțiunile nu s-au schimbat. Pune fișierele care se schimbă rar **sus**, cele care se schimbă des **jos**.
# Bine: package.json se schimbă rar față de codul sursă
COPY package*.json ./
RUN npm ci
COPY . . # sursele se schimbă des - copiază la final
# Evită: orice schimbare în sursă invalidează și npm ci
COPY . .
RUN npm ci
=== 2. Imagini mici ===
* Folosește imagini ''alpine'' sau ''slim'' ca bază
* Curăță cache-ul managerului de pachete în același ''RUN'':
RUN apt-get update \
&& apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
=== 3. Multi-stage builds ===
Separă etapa de compilare de imaginea finală de producție. Rezultatul: imagini de 5–10× mai mici.
=== 4. Securitate ===
* Nu rula ca root - creează un utilizator dedicat cu ''USER''
* Nu include secrete în Dockerfile - folosește ''--secret'' la build sau variabile de mediu la runtime
* Scanează imaginile cu ''docker scout'' sau ''trivy''
=== 5. Un proces per container ===
Un container = un singur proces principal. Evită să rulezi mai multe servicii (nginx + app + db) în același container.
----
===== Referințe =====
* [[https://docs.docker.com/engine/reference/builder/|Referința oficială Dockerfile]]
* [[https://docs.docker.com/develop/develop-images/dockerfile_best-practices/|Best practices - documentație oficială]]
* [[https://docs.docker.com/build/building/multi-stage/|Multi-stage builds]]
* [[https://docs.docker.com/scout/|Docker Scout - scanare vulnerabilități]]
{{tag>docker dockerfile devops container image build linux infrastructura ci-cd securitate}}