כללי הבדיקה של Dockerfile
מה כל כלל של בודק ה-Dockerfile בודק, למה זה חשוב ואיך מתקנים, עם דוגמה לפני ואחרי. כל כלל מפנה לתיעוד של Docker שממנו הוא בא.
AQ-DF-001 ה-image הסופי רץ כ-root
חומרה: גבוהה
למה זה חשוב
בלי USER, התהליך רץ כ-root בתוך ה-container. פרצה באפליקציה מקבלת אז הרשאות root על כל מה שמחובר אליו, ובריחה מה-container מתחילה מ-root. images רשמיים של שרתים כמו PostgreSQL או nginx מתחילים כ-root בכוונה ועוברים למשתמש משלהם ב-entrypoint; ה-Linter לא רואה את זה, ולכן כדאי לבדוק בתיעוד של ה-image לפני שינוי.
איך מתקנים
ליצור משתמש בלי הרשאות (או להשתמש במשתמש שכבר יש ב-image, כמו node), לתת לו רק את הנתיבים שהוא כותב אליהם, ולהוסיף USER לפני CMD.
FROM node:24-alpine
WORKDIR /app
COPY server.js .
CMD ["node", "server.js"]
FROM node:24-alpine
WORKDIR /app
COPY server.js .
USER node
CMD ["node", "server.js"]
AQ-DF-002 image בסיס עם התג latest
חומרה: בינונית
למה זה חשוב
latest הוא מה שהמפרסם דחף אחרון. שני builds של אותו Dockerfile יכולים לקבל image בסיס שונה, וגרסה ראשית חדשה יכולה לשבור את ה-build או את האפליקציה בלי שום שינוי מצדכם.
איך מתקנים
להצמיד תג גרסה (python:3.13-slim), או digest (@sha256:…) ל-builds שחייבים להיות ניתנים לשחזור.
FROM python:latest
CMD ["python3", "--version"]
FROM python:3.13-slim
CMD ["python3", "--version"]
AQ-DF-003 image בסיס בלי תג
חומרה: בינונית
למה זה חשוב
image בלי תג ובלי digest פירושו latest, עם אותה בעיה: הבסיס יכול להשתנות בין builds.
איך מתקנים
להוסיף תג גרסה (nginx:1.29-alpine), או digest ל-builds ניתנים לשחזור.
FROM nginx
COPY site/ /usr/share/nginx/html/
FROM nginx:1.29-alpine
COPY site/ /usr/share/nginx/html/
AQ-DF-004 סוד ב-ENV או ב-ARG
חומרה: גבוהה
למה זה חשוב
ערכי ENV נשמרים ב-image וגלויים לכל מי שיכול למשוך אותו (docker image inspect). ערכי ARG נרשמים בהיסטוריית ה-build וב-provenance. אף אחד מהם אינו מקום לסיסמה, token או מפתח.
איך מתקנים
להסיר את הערך, להחליף אותו אם הוא כבר נכנס ל-commit, ולהעביר אותו בזמן build עם secret mount (RUN --mount=type=secret) או בזמן ריצה כמשתנה סביבה או כקובץ.
FROM alpine:3.22
ENV API_TOKEN=s3cr3t-value-123
RUN ./fetch.sh
FROM alpine:3.22
RUN --mount=type=secret,id=api_token,env=API_TOKEN ./fetch.sh
AQ-DF-005 ADD מכתובת URL בלי checksum
חומרה: בינונית
למה זה חשוב
ADD <url> מוריד בזמן ה-build וסומך על כל מה שהשרת מחזיר. אם הקובץ משתנה או שהשרת נפרץ, ה-image משתנה בלי שום עקבה ב-Dockerfile.
איך מתקנים
להוסיף --checksum=sha256:… עם ה-hash שפורסם לקובץ, או להוריד אותו בשלב RUN שבודק את ה-hash.
FROM alpine:3.22
ADD https://example.com/tool.tar.gz /opt/
FROM alpine:3.22
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://example.com/tool.tar.gz /opt/
AQ-DF-006 apt-get install בלי --no-install-recommends
חומרה: נמוכה
למה זה חשוב
כברירת מחדל apt מתקין גם חבילות מומלצות. הן מגדילות את ה-image ומוסיפות תוכנה, ואיתה פגיעויות, שהאפליקציה לא משתמשת בה.
איך מתקנים
להוסיף --no-install-recommends, ולפרט במפורש את החבילות שצריך.
FROM debian:13-slim
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
FROM debian:13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
AQ-DF-007 רשימות apt נשארות ב-image
חומרה: נמוכה
למה זה חשוב
apt-get update מוריד רשימות חבילות ל-/var/lib/apt/lists. אם מוחקים אותן ב-RUN מאוחר יותר, הן עדיין תופסות מקום בשכבה קודמת; צריך למחוק אותן באותו RUN שמתקין.
איך מתקנים
לסיים את אותו RUN ב-&& rm -rf /var/lib/apt/lists/*, או להשתמש ב-cache mount (--mount=type=cache,target=/var/cache/apt).
FROM debian:13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl
FROM debian:13-slim
RUN apt-get update && apt-get install -y --no-install-recommends curl \
&& rm -rf /var/lib/apt/lists/*
AQ-DF-008 הרבה שכבות RUN ברצף
חומרה: מידע
למה זה חשוב
כל RUN הוא שכבה. צעדים ששייכים יחד, כמו התקנת חבילות, קטנים יותר ומנצלים את ה-cache טוב יותר כ-RUN אחד. זו הצעה: צעדים נפרדים יכולים להיות נכונים כשהם משתנים בקצב שונה.
איך מתקנים
לחבר פקודות קשורות עם && והמשכי שורה, חבילה אחת בכל שורה, ממוינות.
FROM alpine:3.22
RUN apk add --no-cache git
RUN apk add --no-cache curl
RUN apk add --no-cache jq
FROM alpine:3.22
RUN apk add --no-cache \
curl \
git \
jq
AQ-DF-009 COPY . בלי .dockerignore
חומרה: נמוכה
למה זה חשוב
COPY . מעתיק את כל ה-build context. בלי .dockerignore, זה כולל את .git, קובצי .env, node_modules מקומי וכל דבר אחר בתיקייה: סודות נכנסים ל-image וכל שינוי שובר את ה-cache. ה-Linter רואה רק את ה-Dockerfile, ולכן לא יודע אם יש לכם קובץ כזה.
איך מתקנים
להוסיף .dockerignore ליד ה-Dockerfile (לפחות .git, .env*, node_modules ותוצרי build), או להעתיק רק את הקבצים שה-image צריך.
FROM node:24-alpine
WORKDIR /app
COPY . .
USER node
CMD ["node", "server.js"]
FROM node:24-alpine
WORKDIR /app
COPY package.json server.js ./
USER node
CMD ["node", "server.js"]
AQ-DF-010 אין HEALTHCHECK
חומרה: מידע
למה זה חשוב
בלי HEALTHCHECK, Docker יודע רק אם התהליך רץ, לא אם הוא עונה. depends_on: service_healthy ב-Compose ו-orchestrators צריכים מצב בריאות. ייתכן שה-image הבסיסי מגדיר אחד; Kubernetes מתעלם ממנו ומשתמש ב-probes משלו.
איך מתקנים
להוסיף HEALTHCHECK שקורא ל-endpoint זול בכלי שיש ב-image (wget -qO-, curl -f), או HEALTHCHECK NONE אם הבריאות נבדקת במקום אחר.
FROM nginx:1.29-alpine
COPY site/ /usr/share/nginx/html/
FROM nginx:1.29-alpine
COPY site/ /usr/share/nginx/html/
HEALTHCHECK --interval=30s --timeout=3s CMD wget -qO- http://127.0.0.1/ >/dev/null || exit 1
AQ-DF-011 CMD או ENTRYPOINT בצורת shell
חומרה: נמוכה
למה זה חשוב
בצורת shell הפקודה רצה תחת /bin/sh -c, כך שה-shell הוא PID 1 ו-docker stop שולח SIGTERM אליו ולא לתהליך שלכם: ה-container ממתין עד ה-timeout ואז נהרג. ENTRYPOINT בצורת shell גם מתעלם מ-CMD ומהארגומנטים של docker run.
איך מתקנים
להשתמש בצורת exec, מערך JSON: CMD ["node", "server.js"]. אם צריך shell, לסיים את הסקריפט ב-exec.
FROM node:24-alpine
USER node
CMD node server.js
FROM node:24-alpine
USER node
CMD ["node", "server.js"]
AQ-DF-012 סקריפט שהורד מועבר ישירות ל-shell
חומרה: גבוהה
למה זה חשוב
curl … | sh מריץ כל מה שהשרת מחזיר, בלי בדיקה ועם ההרשאות של ה-build. סקריפט ששונה או נפרץ משנה את ה-image בשקט, וחיבור שנקטע יכול להריץ חצי סקריפט.
איך מתקנים
להוריד את הקובץ עם ADD --checksum=sha256:… (או להוריד ולבדוק את ה-hash ב-RUN), ורק אז להריץ; עדיף להתקין ממאגר חבילות.
FROM debian:13-slim
RUN curl -fsSL https://example.com/install.sh | sh
FROM debian:13-slim
ADD --checksum=sha256:24454f830cdb571e2c4ad15481119c43b3cafd48dd869a9b2945d1036d1dc68d https://example.com/install.sh /tmp/install.sh
RUN sh /tmp/install.sh && rm /tmp/install.sh
false positives ידועים
עשרה Dockerfiles רשמיים הם חלק מהבדיקות של ה-Linter. כשממצא שגוי לאחד מהם, הוא מופיע כאן עם הסיבה:
- AQ-DF-001 (postgres, redis): ה-entrypoint הרשמי מתחיל כ-root כדי לתקן את הבעלות על volume הנתונים, ואז מריץ את השרת כמשתמש משלו בלי הרשאות (gosu או su-exec).
- AQ-DF-001 (nginx, httpd): התהליך הראשי מאזין לפורטים 80 ו-443 כ-root; תהליכי ה-worker שמשרתים בקשות רצים כמשתמש בלי הרשאות.
- AQ-DF-001 (gitea): מנהל תהליכים (s6) מתחיל כ-root כדי להריץ את sshd על פורט 22, ומריץ את האפליקציה עצמה כמשתמש משלה; קיימת גרסה rootless של ה-image.
- AQ-DF-001 (python): image בסיס של שפה: images שנבנים ממנו מגדירים
USERמשלהם, ו-USERכאן היה שובר את התקנות החבילות שהם מריצים.
מה הכלי עושה
לכל כלל יש מזהה קבוע (AQ-DF-001 עד AQ-DF-012) שה-Linter מציג ליד הממצאים שלו, וחומרה: גבוהה למה שחושף את ה-container או את הסודות שלו, בינונית למה שהופך builds ללא צפויים, ונמוכה ומידע לגודל, ל-cache ולתפעול. הדוגמאות כאן נבדקות בבדיקות של ה-Linter, כך שכל אחת מפעילה את הכלל שלה והתיקון שלה לא.
מקורות
קשורים
הצעד הבא
כלי ציבורי חינמי של אנרוק, חברה שבונה תשתיות מחשוב. מה אנרוק בונה