התקנת 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, אבל אז צריך לטפל בתעודות בנפרד.
איזו מדיניות 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" נגיש לכל האינטרנט. זו אחת הדרכים הנפוצות שבהן מסדי נתונים נחשפים.
שלוש דרכים לטפל, מהפשוטה למורכבת:
- לא לפרסם מה שלא צריך. מסד נתונים, Redis ושירותים פנימיים מדברים ברשת של Compose ולא צריכים ports בכלל.
- לפרסם על localhost בלבד. אם צריך גישה מהשרת עצמו (לכלי ניהול, או ל-Nginx שרץ מחוץ ל-Docker), כתבו "127.0.0.1:5432:5432". הפורט זמין מהשרת ולא מבחוץ. מבחוץ ניגשים דרך מנהרת SSH:
ssh -L 5432:127.0.0.1:5432 dana@server. - חוקים בשרשרת 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 ולוגים. הטבלה מבוססת על הסטאקים שאני נתקל בהם בדרך כלל; העומס האמיתי תלוי באפליקציה ובכמות הגולשים.
| סטאק | vCPU | RAM | דיסק | הערות |
|---|---|---|---|---|
| שירות בודד קטן (בוט, API פנימי, Uptime Kuma) | 1 | 1-2GB | 20-40GB | מספיק לרוב כלי הניהול הקטנים |
| אפליקציה + PostgreSQL + Caddy (הדוגמה למעלה) | 2 | 2-4GB | 40-80GB | הגדירו shared_buffers לפי הזיכרון |
| וורדפרס + MariaDB + Redis + reverse proxy | 2 | 4GB | 60-80GB | הדיסק תלוי בעיקר בספריית המדיה |
| כמה אפליקציות, worker, תור, Redis ו-DB משותף | 4 | 8GB | 100-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 ואת הגיבויים, ואתם מתמקדים בקוד.