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 varianteslimsaualpinepentru 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 loculADDdacă 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ă linieRUNpentru 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 folosiARGpentru 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
alpinesauslimca 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
–secretla build sau variabile de mediu la runtime - Scanează imaginile cu
docker scoutsautrivy
5. Un proces per container
Un container = un singur proces principal. Evită să rulezi mai multe servicii (nginx + app + db) în același container.