SSD Nodes Learn Hosting plans →
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-31

הרצת Docker על VPS: מה באמת משתנה?

גלו מה קורה כשמריצים Docker על שרת VPS. המדריך מסביר איך למנוע קריסת זיכרון, עקיפת חוקי UFW, בעיות באתחול מכולות ומילוי הדיסק בשרתים עם משאבים מוגבלים.

מה משתנה בעת הרצת Docker על גבי VPS

Docker על גבי VPS מריץ את אותו מנוע ואותן תמונות (images) כמו Docker על המחשב הנייד שלכם, לכן כל פקודה שאתם כבר מכירים תמשיך לעבוד. מה שמשתנה הוא הסביבה מסביב. למחשב נייד יש זיכרון פנוי, firewall שאף אחד לא סורק, ודיסק גדול מספיק כך שלעולם לא תצטרכו לבדוק אותו. שרת שכור כולל תקרה קבועה של זיכרון, כתובת IP ציבורית שנסרקת בתוך דקות מרגע העלייה, ומערכת קבצים ראשית ש־Docker ימלא ללא התראה.

ארבעה הבדלים גורמים לרוב התקלות בשרת קטן:

  • הזיכרון מוגבל, וה־kernel פותר מחסור בזיכרון על ידי הריגת תהליך.
  • פורט שפורסם (published port) עובר ישירות דרך UFW, כיוון ש־Docker כותב חוקי firewall משלו.
  • מכולות (containers) לא חוזרות לפעול לאחר reboot אלא אם ביקשתם זאת מראש.
  • תמונות, מכולות, volumes ו־build cache גדלים עד שהדיסק מתמלא.

כל סעיף להלן מציין את הכשל, את המחרוזת שתראו בפועל, ואת המדריך שפותר זאת לעומק. אם טרם כתבתם קובץ compose, קראו תחילה את יסודות Docker Compose על גבי VPS וחזרו לכאן. דף זה מניח שאתם כבר יודעים להעלות stack.

כמה זיכרון RAM צורך קונטיינר Docker?

פחות ממה שרוב האנשים מצפים. קונטיינר הוא תהליך בתוך cgroup (קבוצת בקרה), ולא מכונה וירטואלית, לכן אין בו kernel אורח ואין הקצאה קבועה. העלות היא כל מה שהתהליך שבפנים ניגש אליו. זו הסיבה שכל ה-stack נכנס ב-2 GB, בעוד שאותו stack שנבנה ממכונות וירטואליות לא היה נכנס.

הנתונים להלן הם מספרים טיפוסיים למצב המתנה (idle) עבור אימג'ים סטנדרטיים על Ubuntu 24.04 עם תצורה כברירת מחדל, כפי שנקראו מ-docker stats דקות ספורות לאחר ההפעלה. הם מהווים נקודת התחלה לתכנון, ולא מדד (benchmark) לעומס העבודה שלכם. הריצו את docker stats --no-stream על השרת שלכם לפני שתסתמכו על נתון כלשהו, כולל אלו.

ChartTypical idle container memory, stock images, megabytes
The data behind this chart
[
  {
    "label": "nginx",
    "idle_mb": 8,
    "budget_mb": 64
  },
  {
    "label": "Redis 7",
    "idle_mb": 12,
    "budget_mb": 128
  },
  {
    "label": "Traefik v3",
    "idle_mb": 40,
    "budget_mb": 128
  },
  {
    "label": "PostgreSQL 16",
    "idle_mb": 45,
    "budget_mb": 512
  },
  {
    "label": "Uptime Kuma",
    "idle_mb": 95,
    "budget_mb": 256
  },
  {
    "label": "MariaDB 11",
    "idle_mb": 190,
    "budget_mb": 512
  },
  {
    "label": "Nextcloud (Apache)",
    "idle_mb": 210,
    "budget_mb": 768
  }
]

שתי העמודות מבצעות תפקידים שונים. idle_mb הוא מה שהקונטיינר צורך כשהוא לא עושה כלום. budget_mb הוא מה שיש להקצות בעת התכנון, כיוון ששימוש אמיתי אינו מצב המתנה. PostgreSQL במצב המתנה צורך קרוב ל-45 MB ודורש 512 MB ברגע שחיבורים, פעולות מיון ו-cache פעילים. תכננו לפי עמודת התקציב. בצעו ניפוי שגיאות (debug) לפי עמודת ההמתנה.

שימו לב למבנה של 7 השורות הללו. nginx במצב המתנה צורך 8 MB ו-Nextcloud צורך 210 MB. ה-proxy שלפני היישומים שלכם הוא כמעט בחינם. מסד הנתונים ויישום ה-PHP הם אלו שלפיהם אתם קובעים את גודל השרת.

אזהרה אחת לגבי docker stats: נתון הזיכרון כולל page cache שקריאות הקבצים של הקונטיינר עצמו משכו פנימה, לכן הוא עולה במשך זמן מה לאחר ההפעלה ואז מתייצב. עקבו אחריו במשך שעה לפני שתחליטו שמשהו דולף (leaking).

הערכת גודל ל-VPS: מה נכנס ב-2 GB, ב-4 GB וב-8 GB

ראשית, יש להפחית את המשאבים שצורך המארח. ה-kernel,‏ systemd,‏ journald,‏ sshd ו-daemon של Docker פועלים כולם על אותו ה-RAM שבו נמצאים ה-containers שלכם, ו-dockerd יחד עם containerd תופסים כ-100 MB מתוכו. אתם זקוקים גם לזיכרון פנוי עבור ה-page cache, ועבור עומסים רגעיים שנוצרים בעת בניית image או הרצת dump של מסד נתונים.

ChartRAM budget by plan size, megabytes
The data behind this chart
[
  {
    "plan": "2 GB VPS",
    "total_mb": 2048,
    "host_reserve_mb": 768,
    "container_mb": 1280
  },
  {
    "plan": "4 GB VPS",
    "total_mb": 4096,
    "host_reserve_mb": 1024,
    "container_mb": 3072
  },
  {
    "plan": "8 GB VPS",
    "total_mb": 8192,
    "host_reserve_mb": 1536,
    "container_mb": 6656
  }
]

host_reserve_mb מכסה את מערכת ההפעלה, את ה-daemon של Docker ואת מרווח הביטחון ששומר על השרת מגיב תחת עומס. מה שנותר הוא container_mb, וזהו המספר היחיד שעומד לרשותכם לשימוש. הרזרבה גדלה בהתאם לתוכנית, מ-768 MB בשרת הקטן ביותר ועד 1536 MB בשרת הגדול ביותר, כיוון ששרת גדול יותר מריץ יותר containers, כותב יותר לוגים וזקוק ליותר page cache.

תוכנית של 2 GB מותירה 1280 MB עבור ה-containers. אם תקצו 512 MB מתוכם עבור PostgreSQL ו-128 MB עבור Traefik, מחצית מהזיכרון כבר נוצלה. היתרה מספיקה לשני יישומים קטנים של כ-256 MB כל אחד. זהו שרת ממשי ושימושי. אין בו מקום ל-Nextcloud וגם לא ל-search cluster מעבר לכך.

תוכנית של 4 GB מותירה 3072 MB, שטח שבו מסד נתונים, reverse proxy, שלושה יישומים ו-container לניטור יכולים להשתלב בו-זמנית. זהו הגודל המינימלי שכדאי להשתמש בו עבור כל דבר שחשוב לכם, כיוון שהזיכרון הפנוי הוא זה שבולם השלכות של פריסה (deploy) כושלת.

תוכנית של 8 GB מותירה 6656 MB מתוך ה-8192 MB הזמינים, ובשלב זה המגבלה עוברת בדרך כלל מהזיכרון ל-CPU או לקצב העברת הנתונים בדיסק. סוג מסוים של container קובע את גודלו לפי התצורה שלו ולא לפי העומס: שרת מודלים מקומי משריין KV cache ביחס לחלון ההקשר (context window) שלו, לכן הגדלת num_ctx ב-Ollama יכולה להוסיף גיגה-בייטים לתקציב עוד לפני שהגיעה בקשה אחת. אם החישוב מראה שה-stack שלכם לא נכנס, רכשו תוכנית גדולה יותר במקום לנסות לבצע אופטימיזציות מורכבות: מה העלות האמיתית של VPS מפרט מה הערך של גיגה-בייטים נוספים בחודש.

שני כללים שומרים על החישובים מדויקים. הגדירו מגבלת זיכרון לכל שירות, כדי שתהליך שיוצא משליטה לא יפיל את השרת כולו. והשאירו חלק מהתקציב לא מנוצל, כיוון ש-docker compose build ו-pg_dump זקוקים לזיכרון בדיוק ברגעים הקריטיים ביותר. מגבלות זיכרון ב-Docker Compose מפרט את התחביר ואת המלכודות הנפוצות.

מדוע המכולה שלי מסתיימת עם קוד 137?

הסיבה היא שהליבה (kernel) סגרה אותה. הקוד 137 הוא 128 ועוד 9, כאשר אות 9 הוא SIGKILL. המכולה דרשה יותר זיכרון ממה שהוקצה לה, ומנגנון ה-OOM (Out of Memory) killer סיים את פעולתה.

CONTAINER ID   IMAGE         STATUS
9f2c1a4d7b3e   postgres:16   Exited (137) 4 minutes ago

אמתו את הסיבה במקום לנחש:

docker inspect my-db | grep -i oomkilled
sudo journalctl -k | grep -i "out of memory"

הערך "OOMKilled": true מציין שהמכולה הגיעה למגבלת ה-cgroup שלה, ויומן הליבה מציין את התהליך שנבחר לסגירה:

Memory cgroup out of memory: Killed process 2417 (postgres) total-vm:1284416kB

זהו המקרה התקין, כיוון שהנזק נעצר במכולה אחת בלבד. המקרה הגרוע הוא מכולה ללא כל הגבלה. ללא הגבלה, התקרה שלה היא כל המכונה, ולכן דליפת זיכרון בשירות אחד מרעיבה את המארח, והליבה בוחרת קורבן לפי גודל מכלל המערכת. שורת היומן מאבדת את הקידומת Memory cgroup ומופיעה כ-Out of memory: Killed process 2417 (postgres). התהליך שנבחר הוא לעיתים קרובות מסד הנתונים שלכם, בעוד המכולה שדלפה ממשיכה לרוץ. זו הסיבה שהגבלה על כל שירות חשובה יותר מהערך המדויק של כל הגבלה בודדת.

שימוש ב-swap משנה את התזמון, לא את החישוב. רוב תמונות ה-VPS מגיעות ללא swap. בדקו זאת באמצעות swapon --show, שלא ידפיס דבר אם אין swap מוגדר. קובץ swap נותן לליבה מקום לאחסן דפים לא פעילים, מה שקונה לכם דקות יקרות להבחין בבעיה.

sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
free -h

הפקודה free -h אמורה כעת להציג ערך שאינו אפס בשורת ה-Swap. ה-swap אינו מוסיף זיכרון RAM. שרת שנמצא תחת עומס זיכרון מתמיד הופך לאיטי עד כדי כך שלא ניתן להתחבר אליו ב-SSH כדי לתקן את הבעיה, לכן התייחסו ל-swap כאל חוצץ התרעה ותקנו את הקצאת המשאבים.

מדוע UFW לא חוסם את הפורט שפרסמתי ב-Docker?

התעבורה אינה מגיעה לשרשרת (chain) ש-UFW מגן עליה. כאשר מפרסמים פורט באמצעות -p 5432:5432 או הגדרת ports: ב-compose, ה-daemon כותב חוק DNAT (תרגום כתובת רשת יעד) לטבלת ה-nat וחוק accept לשרשרת ה-DOCKER הייעודית שלו. חבילה המיועדת למכולה מועברת ישירות למכולה במקום להימסר למארח (host), לכן היא מטופלת בנתיב ה-FORWARD ולעולם אינה עוברת דרך חוקי ה-INPUT ש-UFW כותב.

ניתן לצפות בתהליך זה בשרת:

sudo ufw status | grep 5432
sudo iptables -t nat -L DOCKER -n | grep 5432

UFW עשוי להציג 5432 DENY IN Anywhere בזמן שטבלת ה-nat מחזיקה חוק DNAT tcp ... to:172.18.0.2:5432 עבור אותו פורט. ממכונה אחרת, nc -vz your.server.ip 5432 עדיין יתחבר. מסד הנתונים חשוף לאינטרנט הציבורי, בעוד חומת האש מדווחת שהוא אינו חשוף.

הפתרון הוא לפרסם פחות. מכולות באותו פרויקט compose חולקות רשת ומגיעות זו לזו לפי שם השירות, לכן מסד נתונים שמשרת רק את היישום שלידו אינו זקוק כלל להגדרת ports:. כאשר נדרשת גישה מקומית, יש לקשור את הפרסום ל-loopback:

services:
  db:
    image: postgres:16
    ports:
      - "127.0.0.1:5432:5432"

לאחר docker compose up -d, ה-nc -vz your.server.ip 5432 מבחוץ ייכשל, ו-psql -h 127.0.0.1 -p 5432 על גבי השרת עצמו עדיין יעבוד. בסטאק קטן ותקין, רק ה-reverse proxy מפרסם שירותים, בפורטים 80 ו-443. המאמר מדוע פורטים שפורסמו ב-Docker עוקפים את UFW מסביר על שרשרת ה-DOCKER-USER למקרים שבהם חייבים לפרסם פורט ובכל זאת לסנן אותו, והמאמר יסודות חומת האש UFW מכסה את חוקי המארח הבסיסיים.

מדוע המכולות שלי נעלמות לאחר אתחול?

מכיוון ששום דבר לא הורה להן לחזור. מכולה נוצרת עם מדיניות הפעלה מחדש של no אלא אם הגדרת אחרת, לכן אתחול מותיר אותן במצב עצור וה־daemon לא מתייחס אליהן. אתחולים אינם אירוע נדיר ב־VPS: עדכוני ליבה מ־unattended upgrades, תחזוקה של ספק התשתית, ורצף ה־OOM שצוין לעיל, כולם מסתיימים באתחול.

שני תנאים חייבים להתקיים. ה־daemon חייב לעלות בזמן ה־boot:

systemctl is-enabled docker

פקודה זו מדפיסה enabled בהתקנת Ubuntu סטנדרטית. לאחר מכן, כל שירות זקוק למדיניות:

services:
  app:
    image: ghcr.io/example/app:1.4
    restart: unless-stopped

הערך unless-stopped מחזיר את המכולה לאחר אתחול ומכבד מכולה שעצרת בכוונה. הערך always יפעיל מחדש גם מכולות שעצרת באופן יזום בכל פעם שה־daemon עולה, מה שעלול להפתיע במהלך ניפוי שגיאות. עריכת הקובץ לבדה אינה מספיקה, כיוון שמדיניות ההפעלה מחדש נקבעת בעת יצירת המכולה. הרץ את docker compose up -d כדי שהיא תיווצר מחדש, ולאחר מכן בדוק את הערך החי:

docker inspect my-app | grep -A3 RestartPolicy

לאחר מכן בצע אתחול יזום לשרת והרץ את docker compose ps בתיקיית הפרויקט. Stack ששורד אתחול מתוכנן ישרוד גם אתחול לא מתוכנן. אם ה־stack שלך דורש הבטחת סדר פעולות או הרצה חד-פעמית בזמן ה־boot, יחידת systemd היא הכלי המתאים יותר: הפעלת Docker Compose בזמן ה-boot מכיל את קובץ היחידה. כדי לדעת אם מכולה שחזרה אכן מספקת שירות, הוסף בדיקות תקינות (healthchecks) ב-Compose.

מדוע הדיסק ב-VPS שלי מלא?

מכיוון ש-Docker שומר את כל המידע עד שתורה לו אחרת. כל תגית image שמשכת אי פעם, כל container שעצרת, כל volume אנונימי שנותר מאחור לאחר יצירה מחדש, וכל שכבה של build cache נשארים על הדיסק. במערכת קבצים ראשית של 40 GB או 80 GB, גודל סטנדרטי בתוכניות אלו, הדבר מוביל להשבתה בתוך חודשים ספורים במקום שנים.

דיסק מלא אינו נראה כמו קריסה. תקבל no space left on device מ-container, מ-apt, מ-journald ומ-docker pull באותה שעה. PostgreSQL יפסיק לקבל כתיבות. השרת עדיין פעיל, מה שהופך את הבעיה לקשה יותר לזיהוי מאשר לולאת אתחול.

בדוק לפני שתמחק:

docker system df
df -h /

docker system df מחלק את הסך הכולל ל-images, ל-containers, ל-volumes מקומיים ול-build cache, עם עמודת RECLAIMABLE לצד כל אחד. בשרת שבנה בעצמו את ה-images שלו, ה-build cache הוא בדרך כלל השורה הגדולה ביותר.

docker image prune -a
docker builder prune
docker system df

docker image prune -a מסיר כל image ששום container לא משתמש בו. docker builder prune מנקה את ה-build cache. שתי הפעולות בטוחות בזמן שהשירותים רצים, כיוון שכל מה שנמצא בשימוש נשמר. מה שאינו בטוח הוא docker system prune --volumes, שמוחק כל volume ששום container לא מפנה אליו כרגע. stack שעצרת לסוף השבוע נראה בדיוק כך, ו-volume מסד הנתונים שלו יימחק יחד איתו. קרא על bind mounts לעומת named volumes לפני שתקליד דגל זה, ובצע גיבוי תחילה.

לוגים של containers הם גורם צמיחה שקט יותר. מנהל ה-json-file המוגדר כברירת מחדל אינו כולל הגבלת גודל, לכן container "פטפטן" עלול לכתוב גיגה-בייטים לתוך /var/lib/docker/containers. הגבל זאת עבור כל container ב-/etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}

החל זאת באמצעות sudo systemctl restart docker, פעולה שמפעילה מחדש את ה-containers שלך, לכן בחר את העיתוי המתאים. ההגבלה חלה על containers שנוצרו לאחר השינוי, לכן צור מחדש את אלו שרצים באמצעות docker compose up -d --force-recreate ואמת זאת:

docker inspect my-app | grep -A5 LogConfig
sudo du -sh /var/lib/docker/containers

פלט ה-inspect אמור להראות ש-max-size מוגדר. אם הוא ריק, ה-container נוצר לפני השינוי והוא עדיין כותב ללא הגבלה.

הרגלים לשמירה על תקינות שרת Docker קטן

אין צורך בלוח בקרה או בכלי מורכב כדי ליישם זאת.

  • הריצו את docker system df ואת df -h / בראשון לכל חודש. שתי פקודות, שלושים שניות, ואתם תראו את המגמה הרבה לפני שהיא הופכת להשבתה.
  • הגדירו מגבלת זיכרון לכל שירות, גם לאלו שנראים לכם קטנים. המגבלה הופכת קריסה של השרת כולו לאתחול של מכולה בודדת בלבד.
  • נטרו את השרת ממקור חיצוני, כך שתקבלו התראה על עומס זיכרון או דיסק לפני שה־kernel יתערב. Uptime Kuma רץ בתוך מכולה וצורך במצב המתנה כ-95 MB.
  • בצעו גיבוי ל-volumes, לא למכולות. המכולה היא זמנית, ה-volume אינו כזה. גיבויי restic ב-VPS מכסים תזמון ובדיקת שחזור.
  • קבעו גרסאות (tags) של תמונות בקובץ ה-compose ועדכנו אותן ביום שתבחרו. עם latest, הגרסה שתקבלו ב-docker compose pull הבא תהיה זו ששוחררה באותו בוקר.

שרת VPS קטן שמריץ Docker נשאר תקין לאורך שנים כאשר ארבעה נתונים נשמרים בטווח הרצוי: תקציב הזיכרון, רשימת הפורטים החשופים, מדיניות האתחול (restart policy) של כל שירות, ושטח הדיסק הפנוי. כל השאר הוא ה-Docker הרגיל שאתם כבר מריצים.

FAQ

כמה זיכרון RAM דרוש להרצת Docker על שרת VPS?

Docker עצמו צורך מעט משאבים. ה-daemon וה-containerd צורכים יחד כ-100 MB, ושאר הדרישות תלויות במכולות שלכם. הקצו תחילה את חלקו של המארח: 768 MB מתוך 2048 MB עבור מערכת ההפעלה, ה-daemon ומרווח ביטחון, מה שמותיר 1280 MB לשימושכם. מסד נתונים ב-512 MB, שרת reverse proxy ב-128 MB ושני יישומים קטנים יכנסו בנפח זה. מדדו את צריכת הזיכרון של ה-stack שלכם באמצעות docker stats --no-stream במקום להסתמך על נתונים מפורסמים.

האם ניתן להריץ Docker על שרת VPS עם 1 GB RAM?

כן, עבור מכולה אחת או שתיים קלות, אך הוסיפו קובץ swap לפני שתתחילו. בערך מחצית מהזיכרון בשרת של 1 GB נצרך על ידי מערכת ההפעלה וה-Docker daemon, מה שמותיר מקום ליישום קטן ול-reverse proxy, אך לא למסד נתונים תחת עומס אמיתי. בניית images על שרת בגודל כזה עלולה להיכשל או לגרום לקריסת תהליכים אחרים; לכן, בצעו את הבנייה במקום אחר ומשכו את ה-image המוכן.

האם UFW מגן על מכולת Docker?

לא עבור פורטים שחשפתם. Docker כותב חוקי DNAT ו-forward משלו, לכן חבילת מידע המיועדת לפורט של מכולה חשופה מועברת ישירות למכולה במקום להימסר למארח, וחוקי ה-INPUT שמנהל UFW כלל לא רואים אותה. ufw deny 5432 יכול להיות פעיל בזמן שהפורט מגיב לבקשות מהאינטרנט. בצעו חשיפה ל-loopback באמצעות 127.0.0.1:5432:5432, השאירו שירותים פנימיים ללא חשיפה, או בצעו סינון בשרשרת ה-DOCKER-USER.

האם המכולות שלי יופעלו מחדש לאחר אתחול של ה-VPS?

רק אם הן נוצרו עם מדיניות הפעלה מחדש (restart policy). הגדירו restart: unless-stopped לכל שירות, הריצו docker compose up -d כדי שהמכולות ייווצרו מחדש עם הגדרה זו, וודאו ש-systemctl is-enabled docker מדפיס enabled. לאחר מכן בצעו אתחול יזום ובדקו את docker compose ps. מדיניות הפעלה מחדש שלא בדקתם אינה נחשבת למדיניות פעילה.

באיזו תדירות כדאי לבצע ניקוי (prune) ל-Docker images?

פעם בחודש זה מספיק עבור רוב השרתים הקטנים, או בכל פעם ש-docker system df מדווח על שטח שניתן לפנות ואתם זקוקים לו. docker image prune -a ו-docker builder prune בטוחים לשימוש בזמן שהשירותים רצים, כיוון ש-images וקבצי cache שנמצאים בשימוש אינם נמחקים. הימנעו מ-docker system prune --volumes אלא אם אתם יודעים בדיוק אילו volumes אינם בשימוש, כיוון שפקודה זו מוחקת את הנתונים של כל stack שאינו פעיל באותו רגע.