Docker
מ-Dockerfile בסיסי לדפוסי production: multi-stage builds, healthchecks, log rotation ו-Compose
תיאוריה
מדוע Docker חשוב בשוק העבודה הישראלי
לפי סריקת שוק העבודה האחרונה (אפריל 2026), Docker מופיע ב-10.6% ממשרות ה-DevOps בישראל — המיקום השלישי אחרי CI/CD ו-Kubernetes. כל ראיון DevOps ב-2026 מניח היכרות עם Docker, ושאלות הראיון לא עוצרות ב-docker run. המראיינים בודקים האם המועמד מבין image layers ו-build cache, האם הוא כותב multi-stage builds, האם הוא יודע לקנפג healthcheck ו-log rotation, ואיך הוא מנהל compose בסביבת production. הסעיפים הבאים מכסים בדיוק את הפערים האלה.
Image layers ו-build cache: כיצד Docker באמת בונה
כל הוראה ב-Dockerfile יוצרת layer חדש. Docker שומר כל layer ב-cache לפי hash של תוכן ה-layer שלפניו. כאשר layer משתנה, כל ה-layers מתחתיו נבנים מחדש — זו הסיבה שסדר ההוראות ב-Dockerfile משנה מאוד לזמני build.
דוגמה מעשית: שימו את COPY requirements.txt . ו-RUN pip install לפני COPY . .. כך, שינוי בקוד האפליקציה לא יגרום להתקנה מחדש של ה-dependencies:
FROM python:3.12-slim
WORKDIR /app
# Layer זה משתנה רק כשמשתנה requirements.txt
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Layer זה משתנה בכל push
COPY . .
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]כלל הזהב: מהיציב ביותר לפחות יציב, מלמעלה למטה.
Multi-stage builds: images קטנים ו-secure ל-production
הבעיה: image שנבנה עם כלי compile (gcc, go, mvn) נושא את הכלים האלה לתוך production — מיותר, כבד, ומסוכן (כל package נוסף הוא attack surface).
הפתרון: multi-stage build. שלב ה-builder מקמפל; שלב ה-runtime לא מכיר את ה-builder ומקבל רק את ה-artifact:
# שלב 1: builder — כלי הבנייה נמצאים כאן
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
# CGO_ENABLED=0 מייצר binary סטטי שלא תלוי ב-glibc
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /out/app ./cmd/server
# שלב 2: runtime — image מינימלי לחלוטין
FROM gcr.io/distroless/static-debian12
USER nonroot:nonroot
COPY --from=builder /out/app /app
EXPOSE 8080
ENTRYPOINT ["/app"]תוצאה: image של Go app עם distroless יכול להיות פחות מ-15MB לעומת 800MB+ עם golang:1.22.
לבדיקה: docker build -t myapp . && docker images myapp
נקודה נוספת: ניתן לשתף שלבים בין builds שונים באמצעות --target:
# בנה רק עד שלב ה-builder לצורך debugging
docker build --target builder -t myapp-debug .ב-CI/CD (GitHub Actions, Jenkins) — תמיד השתמשו ב---cache-from עם registry cache:
docker build \
--cache-from myregistry/myapp:cache \
--build-arg BUILDKIT_INLINE_CACHE=1 \
-t myregistry/myapp:latest .HEALTHCHECK: כיצד Docker יודע שהאפליקציה עובדת
סטטוס running של container לא אומר שהאפליקציה בפנים עובדת. container יכול להיות running בזמן שה-web server בפנים קרס, נתקע ב-deadlock, או לא גמר לאתחל. HEALTHCHECK פותר את הבעיה הזו.
קינפוג מלא ב-Dockerfile:
HEALTHCHECK \
--interval=30s \
--timeout=5s \
--start-period=10s \
--retries=3 \
CMD curl -f http://localhost:8080/health || exit 1הסברים:
- --interval=30s — בדיקה כל 30 שניות. בproduction: לא פחות מ-15s כדי לא להציף את ה-endpoint.
- --timeout=5s — אם הבדיקה לא חזרה תוך 5 שניות, נחשב כ-failure.
- --start-period=10s — grace period לאפליקציה שצריכה זמן לקום (DB migrations, cache warm-up). בדיקות שנכשלות בתוך start-period לא נספרות.
- --retries=3 — אחרי 3 כשלות רצופות, הסטטוס משתנה ל-unhealthy.
בדיקת הסטטוס:
docker inspect --format='{{.State.Health.Status}}' <container_id>
# ערכים: starting | healthy | unhealthyDocker Compose ו-Kubernetes משתמשים בסטטוס הזה לניהול restart policies וtraffic routing. container שהוא unhealthy יופסק ויוחלף אוטומטית אם מוגדר restart: unless-stopped.
עצה לproduction: endpoint ה-/health צריך לבדוק dependencies פנימיים (DB connection, cache) — לא רק שהתהליך חי. מבנה מקובל:
{
"status": "ok",
"db": "ok",
"cache": "ok",
"version": "1.4.2"
}Container log rotation: --log-opt ותפעול logs בproduction
כברירת מחדל, Docker שומר logs של container בקובץ JSON ב-/var/lib/docker/containers/<id>/<id>-json.log — ללא הגבלת גודל. בproduction, container שמדפיס הרבה logs ימלא את ה-disk תוך שעות.
הפתרון: --log-opt עם logging driver json-file:
# בהפעלה ידנית
docker run \
--log-driver=json-file \
--log-opt max-size=10m \
--log-opt max-file=3 \
myapp:latestפירוש:
- max-size=10m — כל קובץ log מוגבל ל-10 מגה. Docker גולל (rotate) אחרי שמגיע לגבול.
- max-file=3 — שומר מקסימום 3 קבצים (current + 2 backups). בסך הכל: לכל היותר 30MB לcontainer.
הגדרה גלובלית ב-Docker daemon (מומלץ לproduction — חל על כל container שלא מגדיר אחרת):
// /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}אחרי שינוי, יש לעשות: sudo systemctl reload docker (containers קיימים לא מושפעים — רק containers חדשים).
logging drivers נוספים לproduction:
- --log-driver=journald — שולח logs ישירות ל-systemd journal; מאפשר journalctl -u docker.service -f
- --log-driver=fluentd — למערכות log aggregation (EFK stack: Elasticsearch, Fluentd, Kibana)
- --log-driver=awslogs — שולח ישירות ל-AWS CloudWatch Logs
לצפייה ב-logs:
docker logs --tail 100 --follow --timestamps <container_id>שגיאה נפוצה בproduction: שינוי daemon.json לא מאפס logs של containers שכבר רצים. צריך להפסיק ולהפעיל מחדש את ה-container.
Docker Compose דפוסי production
Docker Compose מיועד פעם ל-development בלבד, אך בproduction על VPS יחיד (כמו בפלטפורמה הנוכחית) הוא כלי נפוץ ותקף. ההבדל הוא בדפוסי השימוש.
**1. קבצי override: dev vs production**
מוסכמה מקובלת היא לפצל לשני קבצים:
# compose.yml — הגדרות בסיסיות (services, networks, volumes)
# compose.prod.yml — overrides לproduction (log-opts, restart, resource limits)
# הרצה בproduction:
docker compose -f compose.yml -f compose.prod.yml up -dcompose.prod.yml לדוגמה:
services:
api:
restart: unless-stopped
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
cpus: "0.5"
memory: 256M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/health"]
interval: 30s
timeout: 5s
start_period: 10s
retries: 3**2. secrets ו-environment variables**
לעולם לא מכניסים secrets ל-compose.yml ישירות. במקום:
services:
api:
env_file:
- .env.production
environment:
- NODE_ENV=productionקובץ .env.production מוגן בהרשאות:
chmod 600 .env.production
# ו-.gitignore צריך לכלול: .env.production**3. networks מבודדים**
לا מדברים ישירות עם DB מהאינטרנט — backend ו-DB על network פנימי, רק frontend/proxy חשוף:
networks:
frontend:
backend:
services:
nginx:
networks: [frontend, backend]
api:
networks: [backend]
db:
networks: [backend]**4. depends_on עם condition**
services:
api:
depends_on:
db:
condition: service_healthy
db:
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
retries: 5כך ה-api לא מתחיל עד שה-DB באמת מוכן — לא רק שה-container רץ.
**5. zero-downtime deploys בVPS יחיד**
# Pull image חדש
docker compose pull api
# Recreate רק את service זה (Compose יחכה ל-healthcheck)
docker compose up -d --no-deps api
# בדוק שה-container healthy
docker compose ps apiCMD vs ENTRYPOINT: ההבדל שצריך לדעת לראיון
זו שאלה קלאסית בראיונות Docker. ההבדל בא לידי ביטוי בשני ממדים:
| | ENTRYPOINT | CMD |
|---|---|---|
| מטרה | הפקודה הראשית, תמיד רצה | ברירת מחדל לארגומנטים |
| ניתן לדריסה | רק עם --entrypoint | כל ארגומנט שמועבר ב-docker run |
| שניהם ביחד | ENTRYPOINT = הפקודה, CMD = ברירות מחדל לארגומנטים |
דוגמה מעשית:
ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]docker run myimage → מריץ nginx -g daemon off;docker run myimage -g "daemon on;" → מריץ nginx -g daemon on; (CMD נדרס)
שימוש נפוץ בproduction: entrypoint script שמטפל ב-signal handling ו-graceful shutdown:
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "server.js"]חשוב: תמיד השתמשו ב-exec form ["cmd", "arg"] ולא ב-shell form cmd arg. Shell form מריץ את הפקודה כ-/bin/sh -c, כך שהפרוסס לא מקבל SIGTERM ישירות — גורם לבעיות graceful shutdown.
Volumes ו-bind mounts: אחסון נתונים בין הפעלות
כל נתון שנכתב בתוך container מאבד בעת מחיקת ה-container. שלושה סוגי אחסון:
**1. Named volumes** (מומלץ לDB data בproduction):
volumes:
pgdata:
services:
db:
image: postgres:16-alpine
volumes:
- pgdata:/var/lib/postgresql/dataDocker מנהל את המיקום בפועל; ניתן לגבות עם docker run --rm -v pgdata:/data alpine tar czf - /data.
**2. Bind mounts** (מומלץ לdev בלבד):
docker run -v $(pwd)/src:/app/src myappבעיות בproduction: תלות ב-path בhost, בעיות הרשאות (UID mismatch), לא ניתן לשכפל.
**3. tmpfs** (בזיכרון, לנתונים זמניים רגישים):
docker run --tmpfs /tmp:rw,noexec,nosuid,size=100m myappאבטחת containers ב-production
עשרת הפרקטיקות הנפוצות שנשאלות בראיונות:
1. **אל תריצו כ-root**: USER 1000:1000 ב-Dockerfile. בדיקה: docker run --rm myapp whoami.
2. **Read-only filesystem**: docker run --read-only --tmpfs /tmp myapp — container לא יכול לכתוב לשום מקום מלבד /tmp.
3. **Drop capabilities**: docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE myapp.
4. **No new privileges**: docker run --security-opt no-new-privileges myapp.
5. **Scan images**: docker scout cves myapp:latest או trivy image myapp:latest.
6. **Pin base image tags**: FROM python:3.12.3-slim לא FROM python:latest.
7. **Multi-stage**: אל תשלחו dev tools לproduction.
8. **Secrets בmount, לא ב-ENV**: --secret id=dbpass,src=./dbpass עם RUN --mount=type=secret,id=dbpass.
9. **Resource limits**: --memory=256m --cpus=0.5 — מונע resource exhaustion.
10. **Network segregation**: containers שלא צריכים לדבר האחד עם השני לא אמורים להיות באותו network.
תרגול מעשי
1. אימות מבנה multi-stage Dockerfile
הדפס Dockerfile לפלט הסטנדרטי ועבד אותו עם grep ו-awk כדי לאמת שיש בו בדיוק 2 שלבי FROM, שהשלב הראשון מוגדר כ-AS builder, שיש RUN עם CGO_ENABLED=0, ושיש COPY --from=builder. כל בדיקה תדפיס PASS או FAIL. הדפסת כל ה-PASS מוכיחה שה-Dockerfile עומד בדרישות multi-stage production.
2. אימות הגדרות HEALTHCHECK ב-docker inspect JSON
JSON המדמה פלט docker inspect מוזרם ל-python3. חלץ ואמת שה-container מוגדר עם HEALTHCHECK: Interval=30s, Timeout=5s, StartPeriod=10s, Retries=3. חלץ גם את סטטוס ה-health הנוכחי. תרגיל זה בוחן ניתוח פלט inspect — משימה יומיומית בdiagnostics production.
3. ניתוח docker inspect JSON ואיתור הגדרות log rotation
JSON המדמה פלט docker inspect מוזרם ל-jq. חלץ ואמת: (1) LogConfig.Type הוא json-file, (2) הערך של max-size הוא 10m, (3) הערך של max-file הוא 3, (4) RestartPolicy הוא unless-stopped. תרגיל זה בוחן הבנת log rotation configuration ויכולת קריאת inspect output עם jq.
שאלות חיבור
חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.
שאלת חיבור 1
ב-Linux Advanced למדת על logrotate ל-application logs ועל systemd units לניהול שירותים ארוכי טווח. כיצד --log-opt max-size ו-max-file ב-Docker משלימים את logrotate, ולמה פתרונות אלה לא ניתן להחליף האחד את השני כאשר האפליקציה רצה בcontainer?
שאלת חיבור 2
ב-Python for DevOps למדת לכתוב scripts שמשתמשים ב-subprocess ו-os.environ לניהול environment. כיצד הידע הזה קשור לאופן שבו Docker Compose production מנהל secrets וenvironment variables, ומה הסכנה שאתה מזהה מניסיון Python שלך כאשר מישהו מכניס secrets ישירות ל-compose.yml?
מוכן לבחינה?
בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.