- להסביר למה הנתונים בתוך container נעלמים כשמוחקים אותו, ולהפעיל named volume כך שמסד-נתונים שורד
docker rm,docker system prune, ואפילו reboot של המכונה. - להבחין בין named volume (אחסון מנוהל על-ידי Docker, אידיאלי למסדי-נתונים ו-state בפרודקשן) לבין bind mount (תיקיית מארח, אידיאלי ל-live-editing של קוד בפיתוח) — ולבחור נכון לכל תרחיש.
- להעביר config (כתובות מסד, פורטים, feature flags) ב-runtime עם
-e KEY=valueועם--env-file .env, בלי לקבע ערכים בתוך ה-Dockerfile או ה-image. - לזהות ולמנוע אפיית סודות (secret baking) לתוך image — לא hard-coded keys ב-Dockerfile, לא
ENV API_KEY=..., לאARG SECRET=...שלא מנוקה — ולהבין למה זו הטעות שקיימת ב-48% מאפליקציות ה-AI שמפורסמות ברשת. - לאמת ב-
docker historyשאף סוד לא נכנס ל-image לפני push ל-registry, ולהגדיר.envכך שהוא מוחרג גם מ-Git וגם מ-.dockerignore.
- פרקים קודמים: פרקים 1-3. אתם צריכים להכיר את ההבחנה בין image ל-container, את
docker run -p host:container, ואת ה-Dockerfile ש-docker initיצר לכם. בלי זה, הפרק ירגיש מנותק. - Image רץ של האפליקציה שלכם — אם
docker run -p 3000:3000 myappעובד, אתם מוכנים. אם לא — חיזרו לסוף פרק 3 לפני שתמשיכו. הפרק הזה מניח שיש לכם image אמיתי לעבוד איתו. - מסד-נתונים או state כלשהו לשמור — PostgreSQL, SQLite, קובץ JSON, או כל דבר שאתם רוצים שיישאר בין הרצות. גם אם האפליקציה שלכם רק כותבת ללוג — זו דוגמה טובה ל-volumes.
- לפחות משתנה סביבה אחד עם סוד — API key של OpenAI / Anthropic / Stripe / SendGrid, או סיסמת מסד. גם אם עדיין אין לכם, צרו
.env.exampleריק כדי לתרגל את המבנה. - זמן משוער: 80-100 דקות. זה פרק ביניים שמכין אתכם ל-persistence (פרק 5) ול-deploy אמיתי (פרק 6).
אתם בלב הפרויקט המרכזי של הקורס. בפרק 1 הבנתם למה "רץ אצלי במחשב" לא מספיק. בפרק 2 התקנתם את Docker, הרצתם nginx ב-browser, ולמדתם את ששת הפעלים היומיומיים. בפרק 3 בניתם image משלכם עם docker init, סידרתם את ה-Dockerfile (dependencies לפני COPY . ., base slim, USER appuser), וקיבלתם image קטן ומאובטח בסיסית.
בפרק הזה אתם פותרים שתי בעיות שעדיין מפרידות את ה-image שלכם מאפליקציה אמיתית:
- נתונים שלא נעלמים — ה-image שלכם רץ, אבל אם תעצרו ותמחקו את ה-container, כל מה שהיה בתוכו — מסד הנתונים, קבצי המשתמש, הלוג — נמחק. תלמדו named volume ו-bind mount, ותבינו מתי להשתמש באיזה.
- סודות שלא דולפים — כל אפליקציה שמתחברת ל-API חיצוני צריכה key. הדרך הלא-נכונה היא לכתוב את ה-key ב-Dockerfile. הדרך הנכונה היא
--env-file .env, ולוודא ש-.envמוחרג גם מ-Git וגם מ-.dockerignore.
מה הלאה: בפרק 5 נחבר את האפליקציה למסד-נתונים ב-compose.yaml אחד, ובפרק 6 נדחוף את ה-image ל-ghcr.io ונפרוס אותו על VPS או Coolify. ה-Dockerfile, ה-volume, וקובץ ה-.env שתבנו היום הם הבסיס לכל זה.
למה הנתונים שלכם נעלמים — ה-writable layer ה-ephemeral
זה הרגע שבו רוב ה-Vibe Coders נתקלים בפעם הראשונה ב"באג שאף אחד לא מדבר עליו." אתם מריצים את האפליקציה ב-Docker, יוצרים משתמש חדש, רושמים הערה, מוסיפים קובץ — הכל עובד נהדר. עוצרים את ה-container (docker stop), מריצים מחדש (docker start) — הכל עדיין שם. ואז מוחקים את ה-container (docker rm) ומריצים docker run שוב — והמסד ריק, המשתמשים נעלמו, והקבצים חזרו לנקודת האפס של ה-image.
הרגע הזה הוא ה-aha moment של הפרק. זו לא באג. זו ארכיטקטורה.
ה-writable layer: הסיבה הטכנית
כל container שרץ בנוי משתי חלקים:
- Image layers (read-only): שכבות ה-image שיצרתם ב-
docker build. אלה קבועות, חסרות-שינוי, ולא משתנות בריצה. כל הוראה ב-Dockerfile יוצרת layer (FROM, COPY, RUN, וכו'). - Writable layer (ephemeral — זמני): שכבה דקה מעל ה-image layers, שבה נכתבים כל השינויים שהאפליקציה עושה בזמן ריצה. יצירת קבצים, כתיבה למסד נתונים, שינוי הגדרות — הכל הולך לכאן.
הבעיה: ה-writable layer חי בתוך ה-container. כשאתם עושים docker rm <container>, אתם מוחקים את ה-container — ואיתו את ה-writable layer. ה-image layers נשארים (הם לא שייכים ל-container), אבל כל מה שהיה בתוך ה-container — נמחק. Container הוא disposable by design (חד-פעמי בעיצובו): הוא נועד לרוץ, למות, ולהיות מוחלף באחד חדש שזהה לו.
זה הפילוסופיה של Docker: containers הם כמו תהליכים — לא משאבים. אתם לא שומרים על תהליך, אתם שומרים על מה שהתהליך יצר. הפתרון: לאחסן את "מה שנוצר" מחוץ ל-container, במקום ש-Docker לא מוחק.
שתי דרכים לשמור נתונים מחוץ ל-container
Docker נותן שתי דרכים עיקריות להוציא נתונים מה-container:
- named volume — תיקייה ש-Docker יוצר ומנהל ב-
/var/lib/docker/volumes/על המארח. אתם נותנים לה שם (pgdata,mydata), Docker מטפל בשאר. זה מה שתשתמשו בו למסדי-נתונים, ל-state של האפליקציה, ולכל מה שרץ בפרודקשן. - bind mount — מיפוי של תיקייה ספציפית מהמארח (הלפטופ שלכם) לתוך ה-container. אתם בוחרים את הנתיב, הוא תלוי במבנה המארח שלכם, ויש לו גישה ישירה לקבצים. זה מה שתשתמשו בו לפיתוח — לעריכת קוד ב-host ולראות את השינוי ב-container מיד.
ההבחנה הזו — volume מנוהל מול mount של מארח — היא אחת ההחלטות הכי מבלבלות למתחילים. בואו נפרק אותה לעומק.
named volume — האחסון המנוהל ששורד גם אחרי docker rm
named volume (בקיצור — volume) הוא אזור אחסון ש-Docker יוצר ומנהל בעצמו. אתם נותנים לו שם, Docker יודע איפה לשמור את הנתונים במארח (ב-/var/lib/docker/volumes/<name>/_data), ואתם לא צריכים לדאוג לנתיבים. כשאתם מצמידים את ה-volume ל-container, כל מה שה-container כותב לנתיב המצוין — נשמר ב-volume, לא ב-writable layer.
התחביר בפועל
אתם משתמשים ב--v (או --volume, שניהם זהים) עם הפורמט:
docker run -v volume_name:/path/inside/container image_name
לדוגמה, להריץ PostgreSQL עם volume בשם pgdata שישמור את הנתונים ב-/var/lib/postgresql/data (הנתיב ש-PostgreSQL מצפה לו):
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret123 \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
החלק החשוב: pgdata הוא השם של ה-volume, ו-/var/lib/postgresql/data הוא הנתיב בתוך ה-container שבו PostgreSQL שומר את הנתונים. שני החלקים נדרשים: בלי שם ה-volume, Docker יוצר anonymous volume עם hash אקראי; בלי הנתיב, Docker לא יודע לאן לחבר.
הוכחה — הנתונים שורדים את המחיקה
הנה הסצנה הקלאסית שתעשו היום, בשלוש דקות:
- מריצים את ה-container עם volume (הפקודה למעלה).
- מתחברים למסד, יוצרים טבלה, מכניסים שורה, יוצאים.
- עוצרים ומוחקים את ה-container:
docker stop my-postgres && docker rm my-postgres. - מריצים מחדש עם אותה פקודה (כולל
-v pgdata:/var/lib/postgresql/data). - מתחברים שוב — הטבלה והשורה שם.
הסיבה: ה-volume pgdata לא היה שייך ל-container שמחקתם. הוא חי בנפרד, במערכת הקבצים של המארח, ורק ה-container "ראה" אותו דרך ה-mount. כשמחקתם את ה-container, ה-volume נשאר. כשהרצתם container חדש והצמדתם את אותו volume — ה-container החדש ראה את אותם נתונים.
פקודות ניהול של volumes
כמה פקודות שימושיות שתחזרו עליהן:
docker volume ls— רשימת כל ה-volumes במערכת.docker volume inspect pgdata— מידע מפורט על volume ספציפי: איפה הוא נמצא במארח, מתי נוצר, ואיזה containers השתמשו בו.docker volume create mydata— יצירה ידנית של volume (לרוב לא צריך —-v name:/pathיוצר אוטומטית).docker volume rm pgdata— מחיקה של volume ספציפי. אזהרה: מוחק את כל הנתונים שלא ניתנים לשחזור.docker volume prune— מחיקה של כל ה-anonymous volumes שלא בשימוש. named volumes לא נמחקים אוטומטית.
named volume מול anonymous volume
כשאתם כותבים docker run -v /path/in/container image (בלי שם לפני הנקודתיים), Docker יוצר anonymous volume עם hash אקראי כמו a1b2c3d4e5f6. הוא עובד, אבל אין לכם שם ידידותי לזכור, וקשה לנהל אותו. תמיד תנו שם — זה עולה שתי אותיות וחוסך בלאגן אחר כך.
bind mount — הגשר בין המארח ל-container לפיתוח
bind mount עושה משהו שונה לגמרי: הוא ממפה תיקייה מהמארח שלכם (הלפטופ, המקום שבו אתם עורכים קוד עם VSCode) לתוך ה-container, בזמן אמת. כל שינוי בקבצים במארח — משתקף מיד ב-container. זה הבסיס של hot reload — עריכת קוד בלי לבנות מחדש.
תחביר ה-bind mount
הפורמט דומה ל-volume, אבל במקום שם — נתיב מוחלט במארח:
docker run -v /absolute/path/on/host:/path/in/container image_name
דוגמה קלאסית לפיתוח:
docker run -d --name myapp-dev \
-v $(pwd):/app \
-p 3000:3000 \
myapp:latest
החלק $(pwd):/app אומר: "קח את התיקייה הנוכחית במארח (שבה רץ ה-Dockerfile), ומפה אותה ל-/app בתוך ה-container." אם אתם עורכים את app.py במארח עם VSCode, ה-container רואה את השינוי מיד — בלי docker build מחדש, בלי docker run מחדש. רוב ה-frameworks המודרניים (Next.js עם Fast Refresh, Flask עם debug mode, FastAPI עם --reload) רואים את השינוי וטוענים את הקוד מחדש אוטומטית.
מתי bind mount, מתי named volume
הכלל פשוט:
- bind mount — לקוד בפיתוח. אתם רוצים לערוך ב-VSCode במארח ולראות את השינוי ב-container מיד. לא למסדי-נתונים — הוא תלוי במבנה המארח שלכם, ואם תעבירו את ה-container ל-VPS אחר, הנתיב לא יעבוד.
- named volume — לכל דבר שצריך לשרוד: מסדי-נתונים, קבצי uploads, cache, state. גם בפיתוח וגם בפרודקשן. אינדיפרנטי למבנה המארח, נייד בין מכונות.
המלכודת הקלאסית: להריץ מסד-נתונים עם bind mount של תיקייה במארח. זה עובד בפיתוח, אבל ברגע שתעלו לשרת, התיקייה הזו לא קיימת — והמסד לא עולה. מסדי-נתונים תמיד על named volume.
הפקודה המעשית — dev מול prod על אותו Dockerfile
הנה ההבדל המעשי שתרגישו ממש בתרגיל 2:
# Production: image אפוי, אין קוד במארח בכלל
docker run -d --name myapp-prod \
-v mydata:/var/lib/myapp \
-p 3000:3000 \
myapp:v1.0
# Development: bind mount לקוד, hot reload
docker run -d --name myapp-dev \
-v $(pwd):/app \
-p 3000:3000 \
-e NODE_ENV=development \
myapp:latest
אותו image (myapp:latest), שתי סביבות. בפיתוח, הקוד שלכם גלוי וניתן לעריכה. בפרודקשן, ה-image עומד בפני עצמו — אין תלות במבנה המארח.
הזהירות הקריטית: container הוא disposable
עכשיו, אחרי שאתם יודעים על volumes, הנה הכלל שיחסוך לכם שעות תסכול: אל תערכו קבצים בתוך container רץ עם docker exec -it <id> sh. גם אם זה עובד עכשיו, השינויים שלכם חיים ב-writable layer — וייעלמו ברגע שתעשו docker rm או docker pull של image חדש. אם אתם צריכים לשנות משהו — תקנו את ה-Dockerfile, תקנו את הקוד, או השתמשו ב-bind mount. לא ב-docker exec.
משתני סביבה ב-runtime — -e, --env-file, ו-ENV ב-Dockerfile
עכשיו עוברים לחצי השני של הפרק: config. כל אפליקציה אמיתית צריכה הגדרות — כתובת מסד נתונים, API key, פורט, feature flag, סיסמה. השאלה היא לא אם צריך config, אלא איך מעבירים אותו ל-container בלי לדלוף אותו למקום הלא נכון.
שלוש דרכים להעביר config
יש שלוש דרכים עיקריות, וכל אחת מתאימה למקרה אחר:
-
-e KEY=value— משתנה בודד. מועבר inline בפקודת ה-docker run. טוב לבדיקה מהירה, גרוע לפרודקשן כי הוא נראה בהיסטוריית ה-shell.docker run -e POSTGRES_PASSWORD=secret123 postgres -
--env-file .env— קובץ שלם של משתנים. Docker קורא את הקובץ ומעביר כל שורהKEY=valueכמשתנה סביבה. זו הדרך המומלצת לפרודקשן.# .env POSTGRES_PASSWORD=secret123 API_KEY=sk-abc123 DATABASE_URL=postgres://user:pass@db:5432/mydb # הרצה docker run --env-file .env myapp -
ENVב-Dockerfile — ערך שנקבע בבנייה. הערך נכנס ל-image ונשאר ב-layer. מתאים לערכים לא-רגישים שלא משתנים בין סביבות (למשל גרסת אפליקציה, תיקיית ברירת-מחדל).# Dockerfile ENV APP_VERSION=1.0.0 ENV NODE_ENV=production ENV PORT=3000
הכלל שיעזור לכם לבחור:
- סודות (API keys, סיסמאות, tokens): תמיד
--env-file .envאו-e. לעולם לאENVב-Dockerfile. - config שמשתנה בין סביבות (dev/staging/prod):
--env-file .env, אוenvironment:ב-compose (פרק 5). - config קבוע שלא רגיש (port, תיקייה, הגדרת build):
ENVב-Dockerfile.
ה-Application קורא את המשתנים
בתוך ה-container, האפליקציה קוראת את המשתנים עם המנגנון הסטנדרטי של השפה שלה. כמה דוגמאות מהירות:
# Python (Flask / FastAPI)
import os
db_url = os.environ.get("DATABASE_URL")
api_key = os.environ["API_KEY"] # KeyError אם חסר
# Node.js
const dbUrl = process.env.DATABASE_URL;
const apiKey = process.env.API_KEY;
# Shell
echo "DB host is $DATABASE_HOST"
העיקרון: ה-container לא צריך לדעת איך המשתנים הגיעו. הוא רק רואה אותם בסביבה שלו. זה מה שהופך את --env-file לכל-כך גמיש: אותו image, קבצי .env שונים, סביבות שונות.
קובץ .env — המבנה הנכון
קובץ .env הוא פשוט רשימת שורות KEY=value, בלי רווחים סביב ה-=:
# .env — local development, NEVER commit
NODE_ENV=development
DATABASE_URL=postgres://myuser:mypass@localhost:5432/mydb
API_KEY=sk-test-abc123
JWT_SECRET=super-secret-token-rotation
PORT=3000
והקובץ המקבילה, .env.example, נשמר ב-Git בלי הסודות:
# .env.example — checked into git, no real values
NODE_ENV=development
DATABASE_URL=postgres://user:pass@localhost:5432/dbname
API_KEY=your-api-key-here
JWT_SECRET=generate-a-random-string
PORT=3000
כך כל מי שעובד על הפרויקט יודע אילו משתנים נדרשים, בלי לראות את הערכים האמיתיים.
build ARG מול runtime ENV — ההבחנה שמונעת אסון
זו ההבחנה שהכי קל לבלבל בה, והבלבול הזה הוא מקור מס' 1 לדליפת סודות. שני directives נראים דומים, אבל הם חיים במקומות שונים לגמרי:
ARG— זמין רק בזמן build. מועבר עם--build-arg KEY=value. הערך נשמר ב-image layer וניתן לחילוץ עםdocker history.ENV/-e/--env-file— זמין רק בזמן run. הערך לא נכנס ל-image, הוא רק "מוזרם" לסביבת ה-container בריצה.
ההבדל בפועל
תראו את שתי הדוגמאות האלה וההבדל יהפוך ברור:
# Dockerfile — שימוש לא נכון
ARG API_KEY
ENV API_KEY=$API_KEY # ← הערך נכנס ל-image layer!
# Dockerfile — שימוש נכון
# אין ARG, אין ENV. הערך מועבר בריצה:
# docker run --env-file .env myapp
בגרסה הראשונה, מי שיריץ docker history myapp יראה את ה-ARG ואת ה-ENV, ויוכל לחלץ את ה-key מה-layers. ה-key "נאפה" לתוך ה-image. זו דליפה קבועה — גם אם תמחקו את השורה מה-Dockerfile אחר כך, ה-image שכבר נבנה מכיל את הסוד.
בגרסה השנייה, ה-image לא מכיר את ה-key. הוא מקבל אותו רק כשמריצים docker run --env-file .env. docker history לא יראה שום דבר רגיש.
טבלת ההבחנה המהירה
| מאפיין | ARG | ENV / -e / --env-file |
|---|---|---|
| מתי זמין | רק בזמן docker build |
רק בזמן docker run |
| איך מועבר | --build-arg KEY=value |
-e KEY=value / --env-file .env |
| איפה נשמר | image layer (קבוע!) | רק בסביבת ה-container הרץ |
גלוי ב-docker history |
כן | לא |
| מתאים לסודות | לעולם לא | כן (עם --env-file) |
| מתאים לגרסת build | כן (למשל --build-arg VERSION=1.2) |
לא |
הכלל: ARG לדברים שמשפיעים על הבנייה (גרסה, target platform, flag של compiler). ENV/-e/--env-file לדברים שמשפיעים על הריצה (config, סודות, endpoints). אם אתם רואים ARG API_KEY ב-Dockerfile — זה אות אזהרה אדום.
הכלל הקרדינלי: אף פעם לא לאפות סודות ל-image
הגענו ללב הפרק. זה הכלל החשוב ביותר בקורס כולו — חשוב יותר מ-cache, יותר מ-multi-stage, יותר מ-non-root USER. אם תזכרו רק דבר אחד, שזה יהיה זה.
הבעיה: סודות ש"נאפים" ל-image
"אפיית סוד" (secret baking) זה המצב שבו משתנה רגיש — API key, סיסמת מסד, token — נכנס לתוך ה-image בזמן הבנייה, ונשאר שם לנצח. הסיבות הנפוצות:
ENV API_KEY=sk-abc123ב-Dockerfile. הערך נכתב ל-layer ונשאר ב-image.ARG API_KEY=sk-abc123ואזENV API_KEY=$API_KEY. אותה בעיה בשני שלבים.COPY .env /app/.envב-Dockerfile בלי .dockerignore. הקובץ כולו נכנס.RUN curl -H "Authorization: Bearer $API_KEY" ...ב-Dockerfile — הערך מופיע בהיסטוריית ה-layer.- Private key, certificate, או credentials file שמועתקים עם
COPY.
בכל המקרים האלה, docker history myapp יראה את הערך, וכל מי שיכול למשוך את ה-image — יכול לחלץ אותו.
המספר שצריך לזכור: 48%
מחקרים על אפליקציות שפורסמו לציבור (כולל אלה שנבנו עם כלי AI) מראים שכ-48% מהן מכילות סוד שדלף לתוך ה-image — API key של OpenAI, token של GitHub, סיסמת מסד. זו לא בעיה נדירה; זו ברירת המחדל כשלא מקפידים. רוב המקרים האלה קרו למי שפשוט לא ידע ש-ENV ב-Dockerfile שומר את הערך ב-layer. הם חשבו שזה רק "הגדרה זמנית." לא. זה קבוע.
ההגנה — ארבע שכבות
ההגנה הנכונה היא בת ארבע שכבות, והן חייבות לעבוד יחד:
-
אל תכתבו סודות ב-Dockerfile. בלי
ENV, בליARG, בליCOPY .envבתוך ה-Dockerfile. אם צריך config בבנייה, תכתבו placeholder שמוחלף בריצה. -
החריגו
.envמ-.gitignore. הקובץ לא יכול להגיע ל-Git. הוסיפו.envל-.gitignoreבראש הפרויקט, וצרו.env.exampleכתבנית ציבורית. -
החריגו
.envמ-.dockerignore. גם אם הקובץ נשאר במארח (למשל במהלך פיתוח),docker buildלא יעתיק אותו לתוך ה-build context.docker initיוצר.dockerignoreעם.envכברירת-מחדל — אל תמחקו אותו. -
העבירו את הסוד בריצה.
docker run --env-file .env myapp, אוenvironment:ב-compose (פרק 5). הערך נשאר במארח, מועבר ל-container רק כשצריך, ולא נכנס ל-image.
כל ארבע השכבות נדרשות. אם תפספסו אחת — דליפה.
file-based secrets — סודות ב-/run/secrets ו-best practices
העברת סודות דרך משתני סביבה היא הסטנדרט ברוב המקרים, אבל יש לה חיסרון: משתני סביבה יכולים להופיע בלוגים, ב-stack traces, וב-dumps של ה-process. cat /proc/1/environ מציג את כולם לכל מי שיש לו גישה ל-container. לפעמים — במיוחד בפרודקשן אמיתי — זה לא מספיק.
החלופה: secrets כקבצים זמניים
Docker תומך ב-mount של סודות כקבצים בתוך ה-container, בנתיב /run/secrets/<name>. הקבצים האלה:
- לא נמצאים ב-image — הם מגיעים מ-host בלבד.
- נשמרים בזיכרון (tmpfs) במקרים מסוימים, לא נכתבים לדיסק.
- לא מופיעים ב-
docker inspectכמשתני סביבה. - נעלמים כשה-container נעצר — אין residual files.
התחביר עם Docker Compose (פרק 5):
services:
myapp:
image: myapp:latest
secrets:
- api_key
- db_password
secrets:
api_key:
file: ./secrets/api_key.txt
db_password:
file: ./secrets/db_password.txt
בתוך ה-container, הקבצים מופיעים ב-/run/secrets/api_key ו-/run/secrets/db_password. האפליקציה קוראת אותם עם open('/run/secrets/api_key').read().strip() במקום os.environ['API_KEY'].
מתי זה הכרחי?
לרוב ה-Vibe Coders, --env-file .env הוא מספיק. file-based secrets מתאימים ל:
- אפליקציות ב-production שמוגשות דרך HTTPS, עם משתמשים אמיתיים.
- רגולציה או דרישות compliance (פיננסים, בריאות).
- סביבות מרובות-שירותים שבהן secrets מנוהלים במקום אחד (HashiCorp Vault, AWS Secrets Manager, Doppler).
עבור הפרויקט הראשון שלכם, --env-file .env הוא הצעד הנכון. כשתעברו לפרודקשן אמיתי בעוד כמה חודשים, תוסיפו file-based secrets או Vault.
לאמת שאין דליפה — docker history ו-inspect
לפני שאתם דוחפים image ל-registry (פרק 6) או נותנים למישהו אחר למשוך אותו — תעצרו לבדיקה אמיתית. שתי פקודות חובה:
docker history — רואה את כל ה-layers
הפקודה docker history myapp:latest (או --no-trunc לראות את הפקודה המלאה) מציגה את כל ה-layers של ה-image, ולכל layer — את ההוראה שיצרה אותו. חפשו:
ENV API_KEY=...→ בעיה. הסוד אפוי.ARG SECRET=...→ בעיה. הסוד אפוי.COPY .env /app/→ בעיה. הקובץ כולו הועתק.RUN curl -H "Authorization: Bearer ..."→ בעיה. הסוד בהיסטוריה.ENV PORT=3000→ תקין. לא סוד.ENV NODE_ENV=production→ תקין. לא סוד.
אם אתם רואים משהו רגיש — תבנו מחדש. אין דרך "למחוק" סוד מ-image קיים. כל מה שנכנס ל-layer — נשאר.
docker inspect — רואה env vars ו-mounts
הפקודה docker inspect myapp:latest (או docker inspect <running-container-id>) מחזירה JSON עם כל המטא-דאטה של ה-image או ה-container. חפשו את השדות:
Config.Env— רשימת כל ה-ENVשנקבעו ב-Dockerfile. אם יש שם ערך רגיש — בעיה.Mounts— רשימת volumes ו-bind mounts. אםSourceמצביע על קובץ סוד במארח — שימו לב שזה לא נכנס ל-image.
בדיקה מהירה למצוא סודות שדלפו:
docker history myapp:latest --no-trunc | grep -iE "key|secret|token|password"
docker inspect myapp:latest | grep -iE "key|secret|token|password"
אם ה-grep מחזיר שורות — תבדקו כל אחת. שורה תמימה כמו ENV NODE_VERSION=20 היא תקינה; ENV STRIPE_KEY=sk_live_... — אסון.
רשת הביטחון האוטומטית — docker scout cves
בפרק 6 נרחיב על docker scout (כלי סריקת חולשות שמגיע עם Docker Desktop). הוא בונה SBOM (Software Bill of Materials — רשימת כל החבילות ב-image) ומסמן חולשות אבטחה ידועות. הוא לא יתפוס סוד שדלף — לזה אתם צריכים docker history — אבל הוא יתפוס base image ישן עם חולשות.
Capstone — האפליקציה רצה עם volume ו-env בפועל
עכשיו הכל מתחבר. בואו נריץ את התרחיש האמיתי: אפליקציה שמתחברת למסד-נתונים, סודות מועברים ב-runtime, הכל שורד restart. זו הבסיס לכל מה שיבוא בפרק 5 (compose) ובפרק 6 (deploy).
המסגרת: שני containers, שיתוף env ו-volume
התרחיש המלא: PostgreSQL עם named volume למסד, ואפליקציה שמתחברת אליו עם סיסמה מ---env-file. שני ה-containers עצמאיים; הקישור ביניהם הוא רק דרך הרשת וה-config.
# 1. הרצת PostgreSQL עם volume
docker run -d --name my-postgres \
-e POSTGRES_PASSWORD=secret123 \
-e POSTGRES_DB=mydb \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
# 2. הכנסת נתונים (psql מהמארח, או דרך container נוסף)
docker exec my-postgres psql -U postgres -d mydb \
-c "CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);"
docker exec my-postgres psql -U postgres -d mydb \
-c "INSERT INTO users (name) VALUES ('Alice'), ('Bob');"
# 3. הרצת האפליקציה עם env file
cat > .env << EOF
DATABASE_URL=postgres://postgres:secret123@host.docker.internal:5432/mydb
API_KEY=sk-test-abc123
EOF
docker run -d --name myapp \
--env-file .env \
-p 3000:3000 \
myapp:latest
# 4. הוכחת השרידות
docker stop my-postgres && docker rm my-postgres
docker run -d --name my-postgres-v2 \
-e POSTGRES_PASSWORD=secret123 \
-e POSTGRES_DB=mydb \
-v pgdata:/var/lib/postgresql/data \
-p 5432:5432 \
postgres:16
docker exec my-postgres-v2 psql -U postgres -d mydb \
-c "SELECT * FROM users;"
# Alice, Bob — שם.
הרגע הזה — ראיתם את הנתונים נשרדים אחרי מחיקת ה-container — הוא ה-aha moment של volumes. זה לא קסם; זה פשוט נתיב במערכת הקבצים של המארח ש-Docker שומר בין ריצות.
הקיצור לעתיד — bind mount בפיתוח
אותו Dockerfile, אבל בסביבת פיתוח, אתם מחליפים את ה-volume ב-bind mount של קוד המקור:
# Development — קוד מהמארח, hot reload
docker run -d --name myapp-dev \
-v $(pwd):/app \
-p 3000:3000 \
--env-file .env \
myapp:latest
# עריכה ב-VSCode → שינוי מיידי ב-container
# (בהנחה שהאפליקציה רצה עם --reload / nodemon / debug mode)
ההבדל היחיד: pgdata:/var/lib/postgresql/data (named volume) הפך ל-$(pwd):/app (bind mount). ה-image אותו image, הקונפיג אותו קונפיג, הסביבה שונה.
אימות סופי — האם הסודות באמת בחוץ?
לפני שאתם מסיימים את הפרק, שלוש בדיקות שלוקחות 30 שניות ומוכיחות שהקוד שלכם נקי:
# 1. האם .env מוחרג מ-Git?
cat .gitignore | grep "^\.env$"
# צריך להציג שורה אחת: .env
# 2. האם .env מוחרג מ-Docker build?
cat .dockerignore | grep "^\.env"
# צריך להציג שורה אחת: .env
# 3. האם ה-image נקי מסודות?
docker history myapp:latest --no-trunc | grep -iE "key|secret|token|password"
# צריך להציג רק ENV בטוחים (PORT, NODE_ENV) או רשימה ריקה
אם שלוש הבדיקות עוברות — אתם במקום הנכון. ה-volume ישמור, ה-config יזרום, הסודות יישארו במארח. אתם מוכנים לפרק 5.
המטרה: להוכיח בעצמכם ש-named volume שומר על נתונים גם אחרי מחיקת ה-container, ולהבין את ההבדל מול containers בלי volume.
מה תעשו:
- הריצו:
docker run -d --name test-ephemeral -e POSTGRES_PASSWORD=pass postgres:16(בלי volume — שימו לב!). - הכניסו נתונים:
docker exec test-ephemeral psql -U postgres -c "CREATE TABLE t (x INT); INSERT INTO t VALUES (1);" - מחקו:
docker stop test-ephemeral && docker rm test-ephemeral. - הריצו שוב בלי volume:
docker run -d --name test-ephemeral-v2 -e POSTGRES_PASSWORD=pass postgres:16. - נסו:
docker exec test-ephemeral-v2 psql -U postgres -c "SELECT * FROM t;". צפו לשגיאה:relation "t" does not exist— הנתונים אבדו. - עכשיו הריצו עם volume:
docker run -d --name test-volume -e POSTGRES_PASSWORD=pass -v pgdata:/var/lib/postgresql/data postgres:16. - הכניסו נתונים:
docker exec test-volume psql -U postgres -c "CREATE TABLE t (x INT); INSERT INTO t VALUES (42);" - מחקו:
docker stop test-volume && docker rm test-volume. - הריצו שוב עם אותו volume:
docker run -d --name test-volume-v2 -e POSTGRES_PASSWORD=pass -v pgdata:/var/lib/postgresql/data postgres:16. - בדקו:
docker exec test-volume-v2 psql -U postgres -c "SELECT * FROM t;". צפו לראות את השורה:x = 42. - נקו:
docker stop test-volume-v2 && docker rm test-volume-v2 && docker volume rm pgdata.
תוצאה צפויה: בלי volume — הנתונים אבדו, הטבלה לא קיימת. עם volume — הנתונים שרדו גם אחרי מחיקת ה-container. ראיתם את ההבדל בעיניים, לא רק קראתם עליו.
המטרה: לחוות את ה-hot reload — לערוך קוד במארח ולראות את השינוי ב-container בלי rebuild.
מה תעשו:
- צרו תיקייה ריקה:
mkdir -p /tmp/hotreload && cd /tmp/hotreload. - צרו קובץ
app.pyפשוט (לדוגמה, Flask):from flask import Flask app = Flask(__name__) @app.route("/") def hello(): return "Hello v1 — first version" if __name__ == "__main__": app.run(host="0.0.0.0", port=3000, debug=True) - צרו
requirements.txt:flask==3.0.0. - צרו Dockerfile פשוט (או השתמשו ב-זה ש-
docker initיצר). - בנו:
docker build -t hotreload:test .. - הריצו עם bind mount:
docker run -d --name hot -v $(pwd):/app -p 3000:3000 hotreload:test. - בדקו בטרמינל אחר:
curl http://localhost:3000. צפו: "Hello v1". - עכשיו, ב-VSCode (או כל עורך), פתחו את
app.pyושנו את "Hello v1" ל-"Hello v2 — live reload works". שמרו. - בלי לעצור את ה-container, הריצו
curl http://localhost:3000שוב. צפו: "Hello v2". - שנו שוב ל-"Hello v3" ובדקו. אם Flask עם debug mode רץ — השינוי אמור להיות מיידי.
- נקו:
docker stop hot && docker rm hot && docker rmi hotreload:test.
תוצאה צפויה: שינוי ב-VSCode משתקף ב-container תוך שנייה, בלי rebuild. זה ה-hot reload שמייצר cycle מהיר של "כתבתי → ראיתי → תיקנתי." בלי bind mount, הייתם צריכים docker build + docker run אחרי כל שינוי.
המטרה: להעביר API key ל-container דרך --env-file ולאמת שהוא לא נכנס ל-image.
מה תעשו:
- צרו תיקייה חדשה:
mkdir -p /tmp/secrets-test && cd /tmp/secrets-test. - צרו
.env:API_KEY=sk-test-abc123 DB_PASSWORD=hunter2 PORT=3000 - ודאו:
grep "^\.env$" ../your-project/.gitignore(או צרו.gitignoreחדש עם.envבשורה הראשונה). - ודאו:
grep "^\.env$" .dockerignore(אם אין — צרו עם.envו-.git). - צרו
app.pyשמדפיס את המשתנים:import os print("API_KEY =", os.environ.get("API_KEY")) print("DB_PASSWORD =", os.environ.get("DB_PASSWORD")) print("PORT =", os.environ.get("PORT")) - צרו Dockerfile מינימלי שמעתיק את
app.pyומריץ אותו עם Python. - בנו:
docker build -t secret-test:1.0 .. - הריצו:
docker run --rm --env-file .env secret-test:1.0. צפו לראות את שלושת הערכים. - בדקו את ה-image:
docker history secret-test:1.0 --no-trunc. חפשו "API_KEY" או "sk-test" — צריך להיות ריק. הסוד לא ב-image. - בדקו גם:
docker inspect secret-test:1.0 | grep -i "api_key\|sk-test". צריך להיות ריק. - בדקו גם:
docker run --rm secret-test:1.0(בלי--env-file). צפו לראותNoneבמקום הערכים — ה-image לא מכיר את הסודות. - נקו:
docker rmi secret-test:1.0 && rm .env.
תוצאה צפויה: --env-file .env מעביר את הסודות בריצה, והם זמינים ב-container. docker history ו-docker inspect לא רואים אותם. בלי --env-file, הערכים הם None. ארבע השכבות של ההגנה עובדות.
המטרה: להריץ תרחיש מלא — מסד-נתונים עם volume, אפליקציה שמתחברת אליו עם סיסמה מ---env-file, ולהוכיח שהכל שורד restart.
מה תעשו:
- הריצו PostgreSQL עם volume:
docker run -d --name pg -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:16. - צרו
.env:DATABASE_URL=postgres://postgres:secret@host.docker.internal:5432/postgres API_KEY=sk-test-xyz789 - צרו
app.pyשמתחברת למסד ויוצרת טבלה:import os import psycopg2 conn = psycopg2.connect(os.environ["DATABASE_URL"]) cur = conn.cursor() cur.execute("CREATE TABLE IF NOT EXISTS messages (id SERIAL PRIMARY KEY, text TEXT)") cur.execute("INSERT INTO messages (text) VALUES ('hello from container')") conn.commit() cur.execute("SELECT * FROM messages") for row in cur.fetchall(): print(row) conn.close() - צרו
requirements.txt:psycopg2-binary==2.9.9. - צרו Dockerfile שמתקין את psycopg2-binary ומריץ את app.py.
- ודאו
.gitignoreכולל.env, ו-.dockerignoreכולל.env. - בנו:
docker build -t app-with-db:1.0 .. - הריצו:
docker run --rm --env-file .env --add-host=host.docker.internal:host-gateway app-with-db:1.0. צפו לראות את השורה(1, 'hello from container')בפלט. - הריצו שוב. הפעם הטבלה כבר קיימת, אז ה-
CREATE IF NOT EXISTSידלג, ה-INSERTיוסיף שורה שנייה, וה-SELECTיחזיר שתי שורות. - עכשיו מחקו רק את ה-container של ה-DB (ה-volume חי!):
docker stop pg && docker rm pg. הריצו שוב:docker run -d --name pg -e POSTGRES_PASSWORD=secret -v pgdata:/var/lib/postgresql/data postgres:16. - הריצו את האפליקציה:
docker run --rm --env-file .env --add-host=host.docker.internal:host-gateway app-with-db:1.0. צפו לראות את שתי השורות שכבר היו שם, ועוד אחת חדשה. ה-volume שמר. - אמתו:
docker history app-with-db:1.0 --no-trunc | grep -iE "secret|sk-test|password". צריך להיות ריק. - נקו:
docker stop pg && docker rm pg && docker volume rm pgdata && docker rmi app-with-db:1.0.
תוצאה צפויה: ה-DB רץ עם volume, הסיסמה מועברת ב---env-file, הנתונים שורדים מחיקה של ה-container, ו-docker history נקי. אתם יודעים עכשיו להריץ אפליקציה אמיתית שמתחברת למסד-נתונים אמיתי, עם סודות אמיתיים, בלי לדלוף כלום.
ההחלטה בין named volume, bind mount, או בלי mount בכלל — תלויה בשלוש שאלות:
-
האם הנתונים צריכים לשרוד את ה-container?
- כן (מסד, uploads, cache, state) → named volume בהכרח. בלי זה, הכל ייעלם ב-
docker rm. - לא (חישוב זמני, build artifacts, scratch space) → בלי mount. ה-container הוא disposable; זה בסדר.
- כן (מסד, uploads, cache, state) → named volume בהכרח. בלי זה, הכל ייעלם ב-
-
האם אתם צריכים לערוך את הנתונים מהמארח בזמן אמת?
- כן (פיתוח, עריכת קוד, hot reload) → bind mount לתיקיית הקוד.
- לא (production, staging) → named volume.
-
האם ה-container ירוץ במכונה אחרת (VPS, CI, חבר)?
- כן → named volume. לעולם לא bind mount בפרודקשן — הנתיב
/Users/yourname/projectלא קיים ב-VPS. - לא (רק על הלפטופ) → bind mount בסדר לפיתוח.
- כן → named volume. לעולם לא bind mount בפרודקשן — הנתיב
הכלל המעשי: קוד בפיתוח → bind mount. נתונים בכל מקום → named volume. אם אתם זוכרים רק את המשפט הזה — אתם מסודרים.
ה-flow הנכון להעברת config ל-container, בארבעה צעדים:
-
זהה מה רגיש ומה לא.
- רגיש: API keys, סיסמאות, tokens, private keys, OAuth secrets.
- לא רגיש: ports, hostnames, feature flags, log levels, NODE_ENV.
-
בחר מנגנון העברה לפי רגישות.
- רגיש →
--env-file .env(או file-based secrets לפרודקשן מחמיר). - לא רגיש אך משתנה בין סביבות →
--env-file .envעם ערך שונה לכל סביבה. - לא רגיש וקבוע →
ENVב-Dockerfile.
- רגיש →
-
ודא שלוש שכבות הגנה.
- הסוד לא ב-Dockerfile (אין
ENV KEY=valueרגיש). - הסוד לא ב-Git (
.envב-.gitignore;.env.exampleבמקומו). - הסוד לא ב-image (
.envב-.dockerignore;docker historyנקי).
- הסוד לא ב-Dockerfile (אין
-
אמת לפני push.
לפני
docker push(פרק 6), הריצו:docker history myapp:latest --no-trunc | grep -iE "key|secret|token|password"אם יש שורות שלא צריכות להיות שם — תבנו מחדש.
הכלל: כל סוד שראה אור בתוך image — דלף. אין דרך למחוק. תפסו את זה לפני ה-push.
פתחו terminal והריצו docker volume ls. תראו את כל ה-volumes ש-Docker יצר אוטומטית בפרויקטים קודמים. עכשיו הריצו docker volume prune (אחרי שווידאתם שאתם לא צריכים אותם) כדי לנקות. תוצאה צפויה: רשימה הרבה יותר קצרה, והבנה ברורה ש-named volumes שורדים docker system prune עד שאתם מוחקים אותם ספציפית. זו הסיבה שהם בטוחים לפרודקשן — אתם צריכים להחליט למחוק, לא Docker.
פתחו את .gitignore בפרויקט שלכם. חפשו את .env — אם הוא לא שם, הוסיפו אותו עכשיו. בנוסף, חפשו .env.example — אם הוא קיים, פתחו אותו ובדקו שהוא באמת ריק מסודות (רק placeholders). אם אין, צרו אחד. תוצאה צפויה: אתם יודעים ב-100% שהסודות שלכם לא ב-Git. הצעד הזה לבדו חוסך 90% מדליפות הסודות בעולם.
פתחו את .dockerignore בפרויקט. חפשו .env, .git, node_modules (או __pycache__). אם משהו חסר — הוסיפו. תוצאה צפויה: docker build שלכם מהיר יותר (פחות קבצים ב-build context), ה-image קטן יותר, והסודות לא נכנסים. זו הגנה שלוקחת 30 שניות להגדיר וחוסכת אסון.
הריצו docker images ובחרו את ה-image הראשי של הפרויקט. הריצו docker history myapp:latest --no-trunc. עברו על כל שורה וחפשו: key, secret, token, password, credential. אם יש — תאתרו את המקור (Dockerfile? COPY? ENV?) ותקנו. תוצאה צפויה: רשימה נקייה, או רשימה ארוכה של פעולות לתיקון לפני ה-push ל-registry בפרק 6.
צרו קובץ .env עם שני משתנים: MY_NAME=YourName ו-MY_GREETING=Shalom. צרו app.py שמדפיס את שניהם. בנו image שלא מכיל את הערכים (רק את הקוד). הריצו פעמיים: פעם אחת בלי --env-file (צפו ל-None), פעם שנייה עם --env-file .env (צפו לערכים). תוצאה צפויה: אתם רואים בעיניים שה-image לא יודע את הערכים — הם מגיעים רק מהקובץ בריצה. זו ההוכחה הקונקרטית להפרדה.
הריצו container פשוט: docker run -d --name demo -e POSTGRES_PASSWORD=pass postgres:16. התחברו: docker exec -it demo sh. צרו קובץ: echo "this is ephemeral" > /tmp/test.txt. צאו (exit). מחקו: docker rm -f demo. הריצו אותו container שוב. התחברו: docker exec -it $(docker ps -aq | head -1) sh. בדקו: ls /tmp/test.txt — לא קיים. תוצאה צפויה: ראיתם בעיניים שהקובץ נעלם עם ה-container. עכשיו תבינו למה volumes קיימים.
הריצו: docker run -d --name pg1 -e POSTGRES_PASSWORD=pass -v mydata:/var/lib/postgresql/data postgres:16. צרו טבלה והכניסו שורה דרך docker exec. מחקו: docker rm -f pg1. הריצו שוב: docker run -d --name pg2 -e POSTGRES_PASSWORD=pass -v mydata:/var/lib/postgresql/data postgres:16. בדקו: docker exec pg2 psql -U postgres -c "SELECT * FROM your_table;". תוצאה צפויה: השורה שם. ה-volume שרד את המחיקה. אתם יכולים לחזור על התרגיל הזה עוד 10 פעמים — המסקנה תהיה זהה. הבנה דרך חזרה היא הבנה אמיתית.
הריצו: docker run --rm alpine sh -c "ping -c 1 host.docker.internal" (או nslookup host.docker.internal). תוצאה צפויה: הכתובת תהיה 192.168.65.2 ב-Docker Desktop או 10.0.2.2 ב-Linux. זו הכתובת שבה container רואה את המארח שלכם. תצטרכו אותה בתרגיל 4 כשאתם מחברים את האפליקציה למסד שרץ במארח.
"אני אשמור הכל בקבצים רגילים בתוך ה-container" — לא. ה-writable layer הוא ephemeral, והוא נמחק עם ה-container. אחרי docker rm, docker system prune, או אפילו docker pull של image חדש — המסד ריק. זו הסיבה המס' 1 ל"איבדתי את כל המידע של המשתמשים שלי" בעולם ה-Docker.
הסימפטום המדויק: אתם מריצים PostgreSQL בלי volume, יוצרים טבלאות ומכניסים נתונים, עושים restart — הכל בסדר. אחרי docker rm + docker run חדש — קסם, המסד ריק.
התיקון: תמיד -v pgdata:/var/lib/postgresql/data (או הנתיב של ה-DB הספציפי שלכם). ה-volume חי במארח, לא ב-container, ושורד כל מחיקה. תוך 10 שניות אתם מגינים על כל המידע של המשתמשים שלכם.
"זה רק לפיתוח, אני אשנה את זה אחר כך" — לא. ENV API_KEY=sk-abc123 ב-Dockerfile אופה את ה-key ל-layer. גם אם תמחקו את השורה מה-Dockerfile בעוד שבוע, ה-image שכבר נבנה מכיל את הסוד. וכל מי שמושך את ה-image לומד את ה-key שלכם. זו הסיבה המס' 1 לדליפת API keys ב-GitHub Container Registry.
הסימפטום המדויק: אתם מוצאים sk-... ב-docker history myapp, או גרוע — ב-docker inspect. ה-key נחשף. החלפת ה-key ב-OpenAI היא הצעד הראשון; הבנה איך זה קרה היא הצעד השני.
התיקון: אל תכתבו סודות ב-Dockerfile. בכלל. לא ENV, לא ARG, לא RUN echo $KEY. העבירו את הסוד בריצה עם --env-file .env או -e. הוסיפו .env ל-.gitignore ול-.dockerignore. זה ה-baseline. הסטטיסטיקה: כ-48% מאפליקציות ה-AI שפורסמו לציבור מכילות סוד שדלף בדרך הזו. אל תהיו חלק מהסטטיסטיקה.
אתם docker exec -it <id> sh, פותחים קובץ, משנים שורה, שומרים, בודקים — עובד. עושים restart — השינוי נעלם. עושים rebuild — השינוי נעלם. כל שינוי שעשיתם בתוך ה-container חי ב-writable layer, וזה אומר שהוא ימות עם ה-container.
הסימפטום המדויק: אתם רואים את השינוי ב-docker exec אבל לא ב-docker run חדש. או שהשינוי נעלם אחרי docker pull של image חדש. זה לא באג; זו התנהגות נכונה של containers — הם חד-פעמיים.
התיקון: אם אתם רוצים לשנות קוד — תקנו את הקוד במארח, תבנו image חדש. אם אתם רוצים לשנות config — העבירו אותו ב--e או --env-file. אם אתם רוצים לשנות state שלא קשור לקוד — שמרו אותו ב-volume. אל תערכו בתוך container רץ ותצפו שזה יישמר. זו אנטי-דפוס ב-Docker.
זה עובד נהדר בפיתוח: -v $(pwd):/app, עורכים ב-VSCode, רואים את השינוי ב-container. אז למה לא להעלות את זה לפרודקשן? כי $(pwd) במארח שלכם זה /Users/yourname/projects/coolapp, וב-VPS שלכם זה נתיב שלא קיים. ה-container פשוט לא יעלה.
הסימפטום המדויק: עובד בלפטופ, קורס ב-VPS עם Error: stat /Users/yourname/... או דומה. הפתרון המהיר של חבריכם: "תשנה את הנתיב ב-VPS." לא. זה לא פתרון — זה פתרון ad-hoc שישבור בכל deploy הבא.
התיקון: בפרודקשן, ה-image עומד בפני עצמו. אין תלות במבנה המארח. קוד נכנס ב-COPY . . בזמן build; state נשמר ב-named volume. bind mount הוא כלי פיתוח, לא כלי פרודקשן. אם אתם רואים -v /home/... ב-compose.yaml של production — תקנו.
יומי (10 דקות, 14 ימים ראשונים):
- 3 דקות — בדיקת volumes.
docker volume lsפעם ביום. אם יש volumes של פרויקטים שסגרתם —docker volume prune. - 3 דקות — בדיקת .env. פתחו את
.envבפרויקט הראשי, עברו עליו. ערכים חסרים? ערכים ישנים? סדרו. - 4 דקות — בדיקת docker history. לפני כל
docker buildשל גרסה חדשה,docker historyעל הגרסה הקודמת. אם יש סוד — תדעו.
שבועי (30 דקות, יום ראשון): סקירת הגדרות. עברו על .gitignore, .dockerignore, Dockerfile, וקובץ .env.example. האם .env בשני ה-ignore-ים? האם יש ENV KEY=value רגיש ב-Dockerfile? האם .env.example עדיין ריק מסודות? אם יש בעיה — תקנו השבוע.
לפני פרק 5 (סוף שבוע שני): ודאו שהאפליקציה שלכם רצה עם named volume למסד-נתונים, ועם --env-file .env לכל ה-config. docker history נקי. docker inspect לא מראה סודות. אם משהו נשבר — תקנו עכשיו. בפרק 5 נעבור ל-compose.yaml ונחבר הכל בקובץ אחד, ואתם צריכים image יציב שעובד עם volume ו-env.
חודשי (45 דקות, סוף חודש): audit מסודר. docker volume ls, docker ps -a, docker images. נקו מה שלא צריך. בדקו שאתם זוכרים איפה הסודות של כל פרויקט — ב-.env מקומי, לא ב-Git, לא ב-image. אם יש ספק — grep -r "API_KEY\|SECRET\|TOKEN" . (לא בתוך .env, בקבצים אחרים).
-
שאלה: אתם מריצים
docker run --name db postgres:16(בלי volume), יוצרים טבלה עם נתונים, ואז עושיםdocker rm db. מה קרה לנתונים, ולמה?תשובה: הנתונים אבדו. ה-writable layer של ה-container הכיל את הקבצים של PostgreSQL, וכשה-container נמחק — ה-layer נמחק איתו. ה-image layers נשארו (הם לא שייכים ל-container), אבל בלי volume — שום דבר מה-state של הריצה לא שרד. התיקון: הוסיפו
-v pgdata:/var/lib/postgresql/dataבפקודת ה-docker run. ה-volume חי במארח, נפרד מה-container, ושורד את המחיקה. -
שאלה: ב-Dockerfile שלכם יש
ENV API_KEY=sk-abc123. אתם מוחקים את השורה הזו ועושיםdocker buildמחדש עם תגmyapp:v2. האםmyapp:v1עדיין חושף את ה-API key?תשובה: כן.
ENV API_KEY=sk-abc123אופה את הערך לתוך layer בזמן build. ה-layer הזה הוא חלק מ-myapp:v1, ומי שיש לו את ה-image יכול לחלץ את הערך עםdocker history. העובדה שמחקתם את השורה מה-Dockerfile לא משנה אתv1— רקv2יהיה נקי. הלקח: אין דרך "למחוק" סוד מ-image. צריך לבנות מחדש, וגם אז — כל מי שמשך אתv1עדיין יכול לראות. זו הסיבה שאסור לאפות סודות מלכתחילה. -
שאלה: אתם רוצים לערוך קוד ב-VSCode ולראות את השינוי ב-container מיד, בלי rebuild. עם איזה mount תשתמשו, ולמה?
תשובה: bind mount.
docker run -v $(pwd):/app myapp:latestממפה את תיקיית הפרויקט במארח לתוך/appב-container. כל שינוי ב-VSCode משתקף מיד ב-container, ואם האפליקציה רצה במצב debug/--reload, היא טוענת את הקוד מחדש אוטומטית. named volume לא יעבוד כי הוא לא קשור למארח — רק ל-Docker. -
שאלה: איזה מהבאים נכון: (א)
ARG DB_PASSWORD=secretב-Dockerfile מאובטח כי ARG זמין רק בזמן build. (ב)ENV DB_PASSWORD=secretב-Dockerfile בטוח כי זה רק בתוך ה-container. (ג)docker run -e DB_PASSWORD=secretבטוח כי זה לא נכנס ל-image. (ד)--env-file .envבטוח בתנאי ש-.envב-.gitignoreוב-.dockerignore.תשובה: (א) לא נכון — ARG זמין בזמן build, אבל הערך נשמר ב-layer של ה-image.
docker historyרואה אותו. (ב) לא נכון — ENV נכנס ל-image גם כן, וגם זמין ב-container. (ג) נכון חלקית —-eכן מעביר בריצה בלי להיכנס ל-image, אבל הסוד גלוי בהיסטוריית ה-shell וב-docker inspect. (ד) נכון — בתנאי שכל התנאים מתקיימים:.envב-.gitignore(לא ב-Git), ב-.dockerignore(לא ב-image), ומועבר רק ב---env-fileבריצה. זו הדרך הנכונה. -
שאלה: למה named volume עדיף על bind mount למסדי-נתונים, גם בפיתוח?
תשובה: שלוש סיבות. (1) ניידות — bind mount תלוי בנתיב ספציפי במארח (
/Users/yourname/...); named volume לא תלוי בכלום ועובד בכל מכונה. (2) ניהול —docker volume ls,docker volume inspect,docker volume rmנותנים לכם שליטה ברורה; bind mount הוא סתם תיקייה במארח שאף אחד לא יודע עליה. (3) ביצועים — ב-Mac וב-Windows, bind mount יכול להיות איטי בגלל ה-translation של ה-filesystem; named volume משתמש ב-native filesystem של המארח ומהיר יותר לקריאה/כתיבה של הרבה קבצים קטנים (מה שמסדי-נתונים עושים). חריג יחיד: אם אתם רוצים לערוך את קוד המקור מהמארח — שם bind mount הוא הכלי הנכון. אבל state של האפליקציה (מסד, uploads, cache) → תמיד named volume.
- מסד-נתונים עם נתונים שורדים.
docker run -v pgdata:/var/lib/postgresql/data … postgres— אישור בעיניים שהנתונים קיימים אחריdocker rmוהרצה מחדש. זה ה-deliverable הכי חשוב של הפרק: הוכחה מעשית ש-persistence עובד. - ריצת dev עם bind mount. עריכת קוד ב-VSCode במארח, רואים את השינוי ב-container תוך שנייה. אותו Dockerfile, שני מצבים (dev עם bind, prod עם volume). ההוכחה שאתם יודעים לבחור את ה-mount הנכון לכל תרחיש.
- הרצה בטוחה עם config. API keys ב-
.envשמוחרג גם מ-Git וגם מ-.dockerignore, הרצה עם--env-file .env, ובדיקה ש-docker historyלא חושף שום סוד. ההוכחה שאתם ב-52% שלא דולפים סודות (ולא ב-48% שכן).
- ה-writable layer הוא ephemeral. כל שינוי שה-container עושה בלי volume — נעלם עם ה-container. Containers הם disposable by design. מי שרוצה persistence — צריך volume.
- named volume מיועד למסדי-נתונים ו-state, bind mount לקוד בפיתוח. הכלל: volume לנתונים, mount לקוד. בלי להתבלבל — תיקנו 90% מהבעיות של מתחילים.
- משתני סביבה ב-
--env-file .env, לא ב-ENVב-Dockerfile. הסודות לעולם לא נכנסים ל-image.--env-fileהוא הגשר בין המארח ל-container; ה-image נשאר נקי. - ARG נשמר ב-layer, ENV נשמר ב-layer. שניהם לא מתאימים לסודות.
ARGלפרמטרים של build (גרסה, target);ENVלערכים קבועים לא-רגישים (port, NODE_ENV). סודות →--env-fileתמיד. - ארבע שכבות הגנה על סודות. (1) אין סוד ב-Dockerfile; (2)
.envב-.gitignore; (3).envב-.dockerignore; (4)--env-fileבריצה. לפספס אחת — דליפה. docker historyהוא המבחן הסופי.docker history myapp --no-trunc | grep -iE "key|secret|token|password"— אם יש שורות חשודות, תבנו מחדש. זו הבדיקה שלוקחת 10 שניות וחוסכת חודשים של נזק.- container הוא disposable; תקנו את ה-Dockerfile או השתמשו ב-bind mount, לא ב-
docker exec. שינויים בתוך container רץ הם אנטי-דפוס. הם נעלמים ב-rebuild, ב-pull, או ב-docker rm. המקור האמיתי של הקוד הוא המארח; המקור האמיתי של ה-state הוא volume.
אם תוציאו רק פעולה אחת מהפרק הזה השבוע — שתהיה זאת: ודאו שה-image שלכם לא מכיל שום סוד. הריצו docker history myapp:latest --no-trunc עכשיו, ועברו על כל שורה. חפשו API_KEY, SECRET, TOKEN, PASSWORD, credential. אם אתם רואים משהו — תקנו את ה-Dockerfile, תבנו מחדש, ותעבירו את הסוד ל---env-file. ואז הוסיפו .env ל-.gitignore ול-.dockerignore אם הוא לא שם.
הסיבה שזו הפעולה הכי חשובה: 48% מאפליקציות ה-AI שפורסמו לציבור מכילות סוד שדלף ל-image. רובן לא במתכוון; המפתח פשוט לא ידע ש-ENV ב-Dockerfile נשמר ב-layer. אתם יודעים עכשיו. הבדיקה הזו לוקחת 30 שניות ומגינה עליכם מלהיות חלק מהסטטיסטיקה הזו. אם תזכרו רק את זה — הצלתם את המשתמשים שלכם מדליפת מידע, ואת עצמכם מבילוי חודשי ב-OpenAI על מישהו אחר.
בפרק 5 (אפליקציות מרובות-Containers — Docker Compose ל-Web + מסד-נתונים) נעבור מפקודות docker run בודדות לקובץ compose.yaml אחד שמגדיר את כל הסביבה: web + database + cache, עם service discovery לפי שם, healthcheck שמחכה למסד-נתונים מוכן לפני שהאפליקציה עולה, ו-depends_on עם תנאים. ה-volume וקובץ ה-.env שבניתם בפרק הזה הם הבסיס; בפרק 5 פשוט נעטוף אותם בקובץ אחד שמריץ את הכל ביחד. בפרק 6 נדחוף את ה-image ל-ghcr.io ונפרוס אותו על VPS או Coolify, וההגנה על הסודות שבניתם היום היא מה שתגן עליכם גם שם.
- ☐ אני יכול להסביר למה הנתונים בתוך container נעלמים אחרי
docker rm(writable layer ephemeral), ולמה named volume פותר את זה. - ☐ הצלחתי להריץ PostgreSQL (או כל מסד) עם named volume, ליצור טבלה עם נתונים, למחוק את ה-container, ולראות שהנתונים קיימים אחרי הרצה מחדש.
- ☐ אני יודע את ההבדל בין named volume לבין bind mount, ויכול לבחור נכון: volume למסדי-נתונים, bind mount לקוד בפיתוח.
- ☐ הצלחתי להריץ אפליקציה עם bind mount של תיקיית הקוד, לערוך קוד במארח, ולראות את השינוי ב-container בלי rebuild.
- ☐ אני מבין את ההבחנה בין
ARG(build-time, נשמר ב-layer) לביןENV/-e/--env-file(runtime, לא ב-layer), ויודע שסודות הולכים רק ל-runtime. - ☐ הקובץ
.envשלי קיים, מכיל את כל המשתנים הנדרשים, ומוחרג גם מ-.gitignoreוגם מ-.dockerignore. - ☐ קיים
.env.exampleציבורי (ב-Git) שמראה את המבנה של המשתנים בלי ערכים אמיתיים. - ☐ הרצתי
docker history myapp:latest --no-truncולא ראיתי שום סוד (key, secret, token, password) ב-layers. - ☐ הרצתי
docker inspect myapp:latest | grep -iE "key|secret|token|password"ואומת שאין סודות ב-metadata של ה-image. - ☐ הצלחתי להריץ תרחיש מלא: PostgreSQL עם volume + אפליקציה עם
--env-file .env+ אימות שהנתונים שורדים אחרי restart של ה-container. - ☐ אני מבין ש-container הוא disposable, ושעריכה ב-
docker execהיא אנטי-דפוס — תיקונים צריכים לבוא מ-Dockerfile או מ-bind mount. - ☐ אני מרגיש/ה שאני מוכן/ה לפרק 5 (Docker Compose). ה-image שלי רץ עם volume ועם env, והסודות שלי בטוחים.