התקנת Docker על שרת ענן: מדריך מעשי ל-Ubuntu 24.04

מהתקנה נקייה מהמאגר הרשמי של Docker ועד סטאק Compose שרץ בייצור - כולל לוגים שלא ממלאים את הדיסק, גיבוי של volumes והחור בחומת האש שרוב המדריכים שוכחים.

עודכן: · 9 דק׳ קריאה · מאת צוות CloudServer

בקצרה כדי להתקין Docker על שרת ענן Ubuntu 24.04 מוסיפים את מאגר ה-apt הרשמי של Docker ומתקינים את docker-ce יחד עם תוסף docker-compose-plugin. אחר כך מגדירים רוטציית לוגים ב-daemon.json, מריצים את השירותים עם docker compose ומדיניות restart, שומרים נתונים ב-volumes שמגובים מחוץ לשרת, וזוכרים ש-Docker עוקף את ufw - לכן פורטים פנימיים מפרסמים על 127.0.0.1 בלבד.

התקנת Docker על שרת ענן לוקחת חמש דקות; להפוך אותה למשהו שאפשר להשאיר לרוץ בייצור לוקח קצת יותר. המדריך מכסה את שני החלקים: התקנה מהמאגר הרשמי של Docker על Ubuntu 24.04 LTS, ואחריה ההגדרות שבדרך כלל מגלים רק אחרי תקלה - דיסק שהתמלא בלוגים, מסד נתונים שנחשף לאינטרנט, או volume שאף אחד לא גיבה. הכול רלוונטי לכל שרת VPS או שרת ענן לינוקס עם גישת root.

מה זה Docker ובמה קונטיינר שונה ממכונה וירטואלית?

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

קונטיינר (Docker)מכונה וירטואלית
ליבהמשותפת עם המארחנפרדת לכל מכונה
זמן עלייהשניות או פחותעשרות שניות עד דקות
תקורהעשרות MB לקונטיינרמערכת הפעלה שלמה
בידודטוב, אבל ליבה משותפתחזק יותר, ברמת חומרה וירטואלית
שימוש טיפוסיאריזת אפליקציה ותלויות, סביבות זהות מפיתוח לייצורהפרדה בין לקוחות, מערכות הפעלה שונות (למשל Windows)

היתרון המעשי: ה-image שבדקתם על הלפטופ הוא בדיוק מה שרץ בשרת. אין יותר "אצלי זה עבד" בגלל גרסת PHP אחרת או ספרייה חסרה. החיסרון: עוד שכבה להבין, עם רשת ואחסון שמתנהגים קצת אחרת ממה שמצפים.

איך מתקינים Docker על Ubuntu 24.04?

מתקינים מהמאגר הרשמי של Docker: מוסיפים את מפתח החתימה, מוסיפים את המאגר ומתקינים חמש חבילות. קודם מסירים חבילות ישנות שעלולות להתנגש (אם הן לא מותקנות, apt פשוט ידלג):

for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do
  sudo apt-get remove -y $pkg
done

הוספת המפתח והמאגר:

sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

sudo tee /etc/apt/sources.list.d/docker.sources > /dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/ubuntu
Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
Components: stable
Signed-By: /etc/apt/keyrings/docker.asc
EOF

התקנה ובדיקה:

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo docker run --rm hello-world
docker compose version

השירות מופעל אוטומטית ועולה עם כל אתחול. אם hello-world הדפיס הודעת ברכה - Docker עובד. סקריפט ההתקנה המהיר get.docker.com עושה פחות או יותר את אותו הדבר, אבל אני מעדיף בשרתי ייצור לראות כל שלב ולא להריץ סקריפט מרוחק כ-root.

האם להוסיף את המשתמש לקבוצת docker?

אפשר, אבל צריך להבין מה זה אומר: חבר בקבוצת docker הוא בפועל root על השרת. מי שיכול להריץ docker run -v /:/host יכול לקרוא ולשנות כל קובץ במערכת, כולל /etc/shadow ומפתחות SSH.

sudo usermod -aG docker dana
# התנתקו והתחברו מחדש כדי שהשינוי ייכנס לתוקף
docker ps

הכלל שלי: מוסיפים לקבוצה רק משתמשים שממילא יש להם sudo מלא. משתמש פריסה של CI, או עובד שצריך רק לראות לוגים, לא מקבל גישה לקבוצה. אם צריך בידוד אמיתי בין משתמשים, יש את rootless mode של Docker, שמריץ את ה-daemon תחת משתמש רגיל - במחיר של מגבלות ברשת ובפורטים נמוכים. ההקשר הרחב יותר נמצא בצ'קליסט אבטחת שרת לינוקס.

איך מונעים מלוגים של Docker למלא את הדיסק?

מגדירים מגבלת גודל ללוגים בקובץ /etc/docker/daemon.json. ברירת המחדל היא מנהל הלוגים json-file בלי שום מגבלה, וקונטיינר רועש יכול לייצר עשרות GB בשבועות ספורים. ראיתי יותר שרתים נופלים מזה מאשר מכל מתקפה.

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  },
  "live-restore": true
}
sudo systemctl restart docker

שתי הערות. ראשית, ההגדרה חלה רק על קונטיינרים שנוצרים מעכשיו; קיימים צריך ליצור מחדש (docker compose up -d --force-recreate). שנית, live-restore משאיר קונטיינרים רצים בזמן שה-daemon עצמו מופעל מחדש או מתעדכן, וזה חוסך השבתות קצרות. חלופה טובה היא "log-driver": "local", שדוחס ומגביל כברירת מחדל.

איך מריצים אפליקציה עם Docker Compose?

Docker Compose מתאר את כל השירותים של האפליקציה בקובץ YAML אחד, ומרימים הכול בפקודה docker compose up -d. הגרסה הנוכחית (v2) היא תוסף של Docker שמופעל עם רווח - docker compose - ולא הכלי הישן docker-compose עם מקף, שכבר לא מתוחזק.

הדוגמה הבאה היא סטאק טיפוסי: אפליקציית ווב, PostgreSQL ו-Caddy כ-reverse proxy שמנפיק תעודות TLS לבד. מבנה התיקייה:

/srv/myapp/
  compose.yaml
  Caddyfile
  .env          # סודות - לא נכנס ל-git

הקובץ compose.yaml:

services:
  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - app

  app:
    image: ghcr.io/example/myapp:1.4.2
    restart: unless-stopped
    environment:
      DATABASE_URL: postgres://app:${DB_PASSWORD}@db:5432/app
    depends_on:
      db:
        condition: service_healthy
    # אין ports - רק Caddy מדבר עם האפליקציה, דרך הרשת הפנימית

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d app"]
      interval: 10s
      retries: 5
    # אין ports - מסד הנתונים לא נחשף החוצה בכלל

volumes:
  caddy_data:
  caddy_config:
  pgdata:

ה-Caddyfile:

app.example.co.il {
    reverse_proxy app:3000
}

ובקובץ .env שורה אחת: DB_PASSWORD=... עם סיסמה ארוכה ואקראית (openssl rand -base64 32), והרשאות chmod 600. מרימים:

cd /srv/myapp
docker compose up -d
docker compose ps
docker compose logs -f app

שימו לב למה שאין בקובץ: לאפליקציה ולמסד הנתונים אין ports. Compose יוצר רשת פנימית שבה השירותים מוצאים זה את זה לפי שם (db, app), ורק Caddy חשוף החוצה. מי שמעדיף Nginx יכול להחליף את Caddy, אבל אז צריך לטפל בתעודות בנפרד.

תגיות גרסה: אל תשתמשו ב-latest בייצור. postgres:17 ננעל על גרסה ראשית, וזה חשוב במיוחד במסד נתונים: מעבר מ-16 ל-17 דורש הגירת נתונים, ו-image שקפץ גרסה בלי שהתכוונתם פשוט לא יעלה.

איזו מדיניות restart לבחור?

ברוב המקרים unless-stopped: הקונטיינר עולה מחדש אחרי קריסה ואחרי אתחול של השרת, אבל אם עצרתם אותו ידנית הוא נשאר עצור. האפשרויות:

  • no - ברירת המחדל. מתאים למשימות חד-פעמיות.
  • on-failure - רק כשהתהליך יצא עם קוד שגיאה. טוב ל-workers שאמורים לסיים.
  • always - תמיד, גם אחרי עצירה ידנית ואתחול של Docker. קצת מבלבל בזמן תחזוקה.
  • unless-stopped - כמו always, אבל מכבד עצירה ידנית. הבחירה הסבירה לשירותים.

מדיניות restart לא מחליפה ניטור. קונטיינר שקורס ועולה בלולאה כל עשר שניות ייראה "רץ" ב-docker ps, ורק עמודת ה-STATUS תסגיר שהוא עלה לפני שש שניות.

למה ufw לא חוסם פורטים של Docker ואיך מתקנים?

כי Docker כותב חוקי iptables ישירות לשרשראות ה-NAT וה-FORWARD, ותעבורה לקונטיינר לא עוברת בכלל דרך שרשרת ה-INPUT שבה ufw עובד. התוצאה: ufw status מראה שרק 22, 80 ו-443 פתוחים, ובפועל PostgreSQL עם "5432:5432" נגיש לכל האינטרנט. זו אחת הדרכים הנפוצות שבהן מסדי נתונים נחשפים.

שלוש דרכים לטפל, מהפשוטה למורכבת:

  1. לא לפרסם מה שלא צריך. מסד נתונים, Redis ושירותים פנימיים מדברים ברשת של Compose ולא צריכים ports בכלל.
  2. לפרסם על localhost בלבד. אם צריך גישה מהשרת עצמו (לכלי ניהול, או ל-Nginx שרץ מחוץ ל-Docker), כתבו "127.0.0.1:5432:5432". הפורט זמין מהשרת ולא מבחוץ. מבחוץ ניגשים דרך מנהרת SSH: ssh -L 5432:127.0.0.1:5432 dana@server.
  3. חוקים בשרשרת DOCKER-USER. Docker משאיר שרשרת אחת שהוא לא נוגע בה ושנבדקת לפני החוקים שלו. דוגמה שמאפשרת לפורט שפורסם רק מכתובת המשרד:
sudo iptables -I DOCKER-USER -i eth0 -p tcp -m conntrack \
  --ctorigdstport 5432 --ctdir ORIGINAL ! -s 198.51.100.25 -j DROP

החוק הזה לא שורד אתחול. כדי שיישמר צריך להכניס אותו לקובץ /etc/ufw/after.rules או להשתמש בכלי כמו iptables-persistent, ולבדוק את שם ממשק הרשת (ip -br a) - הוא לא תמיד eth0. בפועל, ברוב הפרויקטים שלי שני הצעדים הראשונים מספיקים, ואני מגיע ל-DOCKER-USER רק כשבאמת צריך פורט פתוח לכתובות מסוימות. שכבה נוספת וטובה היא חומת אש ברמת הרשת של ספק הענן, שיושבת לפני השרת ולא מושפעת ממה ש-Docker עושה ב-iptables.

אחרי כל שינוי - בדקו מבחוץ, ממחשב אחר: nmap -p 1-65535 <server-ip>. מה שאתם רואים שם הוא האמת, לא מה ש-ufw מדווח.

איך מעדכנים את Docker ואת הקונטיינרים?

יש שני דברים לעדכן בנפרד: את Docker Engine, שמתעדכן דרך apt יחד עם שאר המערכת, ואת ה-images של האפליקציות, שמעדכנים ידנית לפי גרסה.

# עדכון Docker Engine (עם live-restore הקונטיינרים ממשיכים לרוץ)
sudo apt-get update && sudo apt-get install --only-upgrade docker-ce docker-ce-cli containerd.io docker-compose-plugin

# עדכון images של הסטאק: משנים תגית ב-compose.yaml, ואז
docker compose pull
docker compose up -d

# ניקוי images ישנים שתופסים דיסק
docker image prune -a --filter "until=168h"

אני לא אוהב עדכון אוטומטי של images בייצור. עדכון של PostgreSQL או של האפליקציה הוא שינוי שצריך מישהו שמסתכל, וגיבוי טרי לפניו. לעומת זאת עדכוני אבטחה של מערכת ההפעלה עצמה - כן אוטומטיים, דרך unattended-upgrades. כדאי גם להוסיף את docker system df לבדיקה השבועית: הוא מראה כמה מקום תופסים images, volumes ו-build cache.

איך מגבים volumes של Docker?

מגבים את הנתונים, לא את הקונטיינרים: קונטיינר אפשר ליצור מחדש מה-image בכל רגע, נתונים ב-volume לא. למסד נתונים מגבים dump לוגי; לקבצים רגילים מגבים את תוכן ה-volume.

# PostgreSQL - dump עקבי בלי לעצור את השירות
cd /srv/myapp
docker compose exec -T db pg_dump -U app -Fc app > /backup/app-$(date +%F).dump

# volume של קבצים (למשל העלאות משתמשים) - tar דרך קונטיינר זמני
docker run --rm -v myapp_uploads:/data:ro -v /backup:/backup alpine \
  tar czf /backup/uploads-$(date +%F).tgz -C /data .

שם ה-volume בפועל כולל קידומת של שם הפרויקט (myapp_pgdata); docker volume ls יראה את השמות המדויקים. העתקה של תיקיית /var/lib/docker/volumes בזמן ש-PostgreSQL כותב אליה עלולה לתת גיבוי לא עקבי - לכן dump. מכאן הקבצים צריכים לצאת מהשרת, למשל עם restic ליעד חיצוני, לפי שיטת הגיבוי 3-2-1. אל תשכחו לגבות גם את compose.yaml, ה-Caddyfile ואת .env (מוצפן) - בלעדיהם השחזור לוקח הרבה יותר זמן.

כמה זיכרון ודיסק צריך שרת Docker?

Docker עצמו צורך בערך 100-200MB זיכרון. מה שקובע את הגודל הוא מה שרץ בקונטיינרים, ועוד מקום לדיסק בשביל images ולוגים. הטבלה מבוססת על הסטאקים שאני נתקל בהם בדרך כלל; העומס האמיתי תלוי באפליקציה ובכמות הגולשים.

סטאקvCPURAMדיסקהערות
שירות בודד קטן (בוט, API פנימי, Uptime Kuma)11-2GB20-40GBמספיק לרוב כלי הניהול הקטנים
אפליקציה + PostgreSQL + Caddy (הדוגמה למעלה)22-4GB40-80GBהגדירו shared_buffers לפי הזיכרון
וורדפרס + MariaDB + Redis + reverse proxy24GB60-80GBהדיסק תלוי בעיקר בספריית המדיה
כמה אפליקציות, worker, תור, Redis ו-DB משותף48GB100-160GBשקלו להעביר את ה-DB לשרת נפרד
בניית images על השרת (CI)+2+2-4GB+30GBה-build cache גדל מהר; נקו עם docker builder prune

שני כללי אצבע: השאירו לפחות 20% דיסק פנוי, כי Docker מתנהג רע מאוד כשהדיסק מתמלא; והגבילו זיכרון לקונטיינרים רעבים (mem_limit ב-Compose), כדי שדליפת זיכרון באפליקציה אחת לא תגרום ל-OOM killer להרוג את מסד הנתונים. המפרטים והמחירים של התוכניות נמצאים בעמוד המחירים, ואם אתם לבטים בין סוגי שרתים, ההשוואה בין VPS, שרת ענן ושרת ייעודי עוזרת.

מה הצ'קליסט לשרת Docker בייצור?

  • Docker מותקן מהמאגר הרשמי, docker compose version מחזיר v2.
  • רק משתמשים עם sudo בקבוצת docker.
  • daemon.json עם מגבלת לוגים ו-live-restore.
  • בכל שירות: תגית גרסה קבועה ו-restart: unless-stopped.
  • אף מסד נתונים לא מפורסם החוצה; בדיקת nmap מבחוץ עברה.
  • dump יומי של מסד הנתונים וגיבוי volumes שיוצא מהשרת, ושחזור שנבדק לפחות פעם אחת.
  • ניטור זמינות חיצוני והתראה על דיסק מעל 80%.

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

שאלות נפוצות

האם להתקין Docker מ-apt של Ubuntu או מהמאגר של Docker?
עדיף מהמאגר הרשמי של Docker. החבילה docker.io של Ubuntu עובדת, אבל לרוב מפגרת בגרסאות, והתוסף docker compose מגיע שם בחבילה נפרדת. המאגר הרשמי נותן את Docker Engine, Buildx ו-Compose v2 בגרסה עדכנית ומתעדכן דרך apt כרגיל.
האם Docker עוקף את ufw?
כן. כשמפרסמים פורט עם ports או -p, Docker מוסיף חוקי iptables משלו שנבדקים לפני החוקים של ufw, כך שהפורט נגיש מהאינטרנט גם אם ufw לא פתח אותו. הפתרון הוא לפרסם פורטים פנימיים על 127.0.0.1 בלבד, לא לפרסם בכלל פורטים של מסדי נתונים, או להוסיף חוקים בשרשרת DOCKER-USER.
כמה זיכרון צריך שרת שמריץ Docker?
Docker עצמו צורך מעט, בסביבות 100-200MB. מה שקובע הוא מה שרץ בתוך הקונטיינרים: אפליקציה קטנה עם PostgreSQL ו-reverse proxy מסתדרת עם 2GB, וסטאק עם כמה שירותים, Redis ותור עבודות צריך 4-8GB. שימו לב שבניית images על השרת עצמו צורכת זיכרון ודיסק נוספים.
האם נתונים בקונטיינר נמחקים כשמעדכנים אותו?
כל מה שנכתב לשכבת הקבצים של הקונטיינר נמחק כשהקונטיינר נוצר מחדש, וזה קורה בכל עדכון image. לכן נתונים קבועים חייבים לשבת ב-volume או ב-bind mount. בדקו ב-docker inspect שכל תיקיית נתונים של מסד הנתונים מופה ל-volume לפני שאתם סומכים על הסטאק.
האם כדאי להריץ Docker ב-Kubernetes במקום על שרת בודד?
לעסק קטן-בינוני עם כמה שירותים, Docker Compose על שרת ענן אחד או שניים הוא כמעט תמיד הבחירה הנכונה: פשוט לתחזק, פשוט לגבות ופשוט להבין. Kubernetes מצדיק את עצמו כשיש הרבה שירותים, צוות שמכיר אותו וצורך אמיתי בהתאוששות אוטומטית בין שרתים.