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

הגדרת Traefik v3 עם 5 אפליקציות ב-Docker Compose

למדו להריץ 5 שירותים תחת IP אחד עם Traefik v3. המדריך כולל הגדרת Host rule, ניהול TLS אוטומטי מול Let's Encrypt ופתרון לבעיית הרשאות הקובץ acme.json שמונעת עליית מכולות.

כתובת IP אחת, חמישה יישומים, פורט 443 אחד

לשרת ה-VPS שלך יש כתובת IPv4 ציבורית אחת ופורט TCP יחיד מסוג 443. ברצונך להריץ עליו את Gitea, עותק staging של היישום שלך, לוח בקרה פנימי, דף סטטוס ומקבל webhook – חמישה שמות מתחם, שרת אחד. Reverse proxy הוא התהליך שמחזיק בפורטים :80 ו-:443, קורא את ה-header מסוג Host בכל בקשה ומעביר אותה למכולה הנכונה. Traefik מבצע זאת, והוא מנפיק ומחדש תעודה עבור כל שם מתחם מבלי שתצטרך להריץ את certbot באופן ידני. Nginx ו-Caddy יבצעו את ה-TLS termination עבור אותם חמישה שמות מתחם בצורה מושלמת, לכן אם הבחירה עדיין פתוחה, כדאי לשקול את שלושת ה-proxies מבחינת ניהול תעודות ועלות התצורה לכל יישום לפני שאתה מחבר הכל לאחד מהם.

מה שמבדיל את Traefik מבלוק server {} ב-nginx הוא מקור התצורה. ב-nginx אתה עורך קובץ ומבצע reload, ומחזור החיים של התעודה נשאר מטלה נפרדת, תהליך העבודה שאתה מבצע כאשר אתה מנפיק תעודות Let's Encrypt עם certbot ב-nginx, שבו טיימר החידוש חי מחוץ לשרת ה-web לחלוטין. ה-provider של Docker ב-Traefik עוקב אחר זרם האירועים של Docker וקורא labels מהמכולות שלך: הפעל מכולה עם label של כלל Host() והיא תהיה ניתנת לניתוב תוך שנייה; עצור אותה והנתיב ייעלם. זהו גם המלכוד. תצורה שחיה בתוך labels נמצאת בחמישה מקומות בו-זמנית, ו-label שגוי הוא שקט – המכולה פשוט לא מנותבת, ו-Traefik אינו מדווח על כך דבר.

ארבעת המושגים המרכזיים

  • Entrypoints הם sockets להאזנה. עליך להגדיר שניים: web בפורט :80 ו-websecure בפורט :443.
  • Routers מתאימים בקשה (Host(...)) ומקשרים אותה לשירות. תעודות מונפקות עבור כל router, באמצעות tls.certresolver.
  • Services הם ה-backend, כלומר המכולה והפורט שעליו היא מאזינה בתוך רשת ה-Docker.
  • Middlewares ממוקמים בין ה-router ל-service: הם מבצעים אימות בסיסי, רשימות היתר של IP, כתיבה מחדש של headers והפניות (redirects).

ארבעת המושגים הללו הם השמות ש-Traefik נותן לעבודה שהיית מבצע ידנית: ה-router הוא ה-server_name, ה-service הוא יעד ה-proxy_pass, וה-middlewares הם הוראות ה-header והאימות שאתה מגדיר בעצמך כאשר אתה בונה בלוק שרת של nginx reverse proxy שורה אחר שורה.

תצורה סטטית (entrypoints, providers, ACME) מועברת בשורת הפקודה של Traefik או ב-traefik.yml, ושינוי שלה מחייב אתחול של Traefik. תצורה דינמית (routers, services, middlewares) מגיעה מתוויות (labels) של מכולות ונטענת מחדש בזמן אמת (hot-reload). בלבול בין השתיים הוא הגורם השכיח ביותר למצב שבו "הדגל (flag) שלי לא משפיע על כלום".

קובץ ה-compose

רשת Docker משותפת אחת בשם proxy מהווה את עמוד השדרה. Traefik מגיע למכולה רק אם שניהם מחוברים אליה.

name: edge

networks:
  proxy:
    name: proxy

services:
  traefik:
    image: traefik:v3.5
    restart: unless-stopped
    command:
      - --providers.docker=true
      - --providers.docker.exposedByDefault=false
      - --providers.docker.network=proxy
      - --entryPoints.web.address=:80
      - --entryPoints.websecure.address=:443
      - --entryPoints.web.http.redirections.entryPoint.to=websecure
      - --entryPoints.web.http.redirections.entryPoint.scheme=https
      - --certificatesresolvers.le.acme.email=you@example.com
      - --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
      - --certificatesresolvers.le.acme.tlschallenge=true
      # while you iterate, point at staging so a mistake costs nothing:
      # - --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
      - --api.dashboard=true
      - --log.level=INFO
      - --accesslog=true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)
      - traefik.http.routers.dashboard.entrypoints=websecure
      - traefik.http.routers.dashboard.tls.certresolver=le
      - traefik.http.routers.dashboard.service=api@internal
      - traefik.http.routers.dashboard.middlewares=dashboard-auth
      - traefik.http.middlewares.dashboard-auth.basicauth.users=admin:$$apr1$$REPLACE$$THIS

  gitea:
    image: gitea/gitea:1  # major-only pin keeps this demo copy-pasteable; pin an exact release in production
    restart: unless-stopped
    volumes:
      - ./gitea:/data
    networks:
      - proxy
    labels:
      - traefik.enable=true
      - traefik.http.routers.gitea.rule=Host(`git.example.com`)
      - traefik.http.routers.gitea.entrypoints=websecure
      - traefik.http.routers.gitea.tls.certresolver=le
      - traefik.http.services.gitea.loadbalancer.server.port=3000

docker compose up -d, ולאחר מכן docker compose logs -f traefik. כל יישום נוסף הוא העתק של בלוק ה-gitea עם שם נתב משלו, Host() משלו ופורט פנימי משלו. התקנת Nextcloud הרצה ב-Docker עם TLS וגיבויים משתלבת באותו אופן: הסירו את הפורטים המפורסמים שלה, חברו אותה ל-proxy, ותנו לתוויות הנתב לטפל בשם המארח ובתעודה.

חמישה פרטים שם מצדיקים את קיומם.

exposedByDefault=false הופך מכולה לבלתי נראית עבור Traefik כל עוד היא לא נושאת את traefik.enable=true. השאירו זאת בחוץ, וכל מכולה שתפעילו אי פעם, כולל ה-postgres החד-פעמית שהרצתם כדי לבדוק משהו, תקבל נתיב שנוצר עבורה.

providers.docker.network=proxy מורה ל-Traefik באיזו רשת להשתמש כאשר מכולה מחוברת למספר רשתות. השמיטו זאת, ו-Traefik עלול לבחור בכתובת ה-IP השגויה של המכולה, מה שיופיע כשגיאת 502 שנראית כמו תקלה ביישום.

loadbalancer.server.port=3000 הוא הפורט בתוך המכולה; Gitea מאזין ב-3000 שם. שימו לב שאף מכולת יישום אינה מפרסמת פורט כלל, רק Traefik עושה זאת.

ההפניה בנקודת הכניסה web הופכת בקשות בטקסט גלוי ל-308 ל-HTTPS. פורט 80 נשאר פתוח בכל מקרה: אתגר ה-ACME HTTP זקוק לו, וכך גם משתמשים המקלידים שם מארח ללא פרוטוקול.

ה-$$ הכפול ב-hash של ה-basic-auth הוא בריחה (escaping) של Compose, לא שגיאת הקלדה. צרו אותו באמצעות htpasswd -nbB admin 'your-password' (חבילה apache2-utils), ולאחר מכן הכפילו כל $.

התעודה והמלכודת של acme.json

tlschallenge=true בוחר ב-TLS-ALPN-01: Let's Encrypt מתחבר לשרת שלכם בפורט 443 ו-Traefik עונה לאתגר בתוך ה-TLS handshake. החלופה היא HTTP-01, בפורט 80; החליפו את השורה tlschallenge ברשימת ה-command: של Traefik בשתי השורות הבאות:

      - --certificatesresolvers.le.acme.httpchallenge=true
      - --certificatesresolvers.le.acme.httpchallenge.entrypoint=web

שתי השיטות עובדות. שתיהן דורשות שרשומת ה-DNS הציבורית עבור שם המארח כבר תצביע על ה-VPS שלכם, כדי שרשות התעודות תוכל לתרגם את השם ולהתחבר מבחוץ. צרו תחילה רשומת A (ו-AAAA), ודאו זאת באמצעות dig +short git.example.com, ורק אז הריצו את Traefik.

כעת, המלכודת שגורמת לאנשים לאבד ערב שלם. Traefik שומר את מפתח חשבון ה-ACME שלו ואת כל התעודות שהונפקו בתוך קובץ acme.json. אם הקובץ הזה ניתן לקריאה על ידי הקבוצה או על ידי כל משתמש אחר, Traefik ידפיס שורה הדומה מאוד לזו הבאה ויעצור:

error: unable to get ACME account: permissions 644 for /letsencrypt/acme.json are too open, please use 600

הפתרון הנקי הוא זה שצוין לעיל: בצעו bind-mount לתיקייה ואפשרו ל-Traefik ליצור את הקובץ בעצמו עם ההרשאות הנכונות. אם יצרתם את acme.json באמצעות touch, ה-umask שלכם כנראה הפך אותו ל-644. תקנו זאת במארח (host):

chmod 600 ./letsencrypt/acme.json
docker compose restart traefik

גבו את התיקייה הזו יחד עם ה-volumes של היישומים שלכם. אובדן שלה הוא מצב שניתן להתאושש ממנו, שכן התעודות יונפקו מחדש, אך הנפקה מחדש של חמישה שמות מארח בבת אחת תגרור אתכם למגבלות הקצב (rate limits).

השתמשו ב-staging CA בזמן שאתם מבצעים ניסויים. בטלו את ההערה בשורה caserver, ודאו שכל הניתובים עובדים, ולאחר מכן החזירו את ההערה ומחקו את acme.json כדי שתעודות הייצור (production) יונפקו מחדש. שרת הייצור של Let's Encrypt מאפשר חמש תעודות זהות בשבוע עבור אותה קבוצת שמות מארח, ומגביל אימותים כושלים חוזרים עבור אותו שם. ה-staging מנפיק תעודות שאינן מהימנות, הדפדפן שלכם יציג אזהרה, והאזהרה הזו היא הסימן לכך שהתהליך עבד, עם מגבלות הרבה יותר מקלות.

לוח הבקרה הוא ממשק ניהול, לא הדגמה

רוב המדריכים המהירים מגדירים את --api.insecure=true, שמציג את לוח הבקרה בפורט 8080 ללא אימות. בשרת בעל כתובת IP ציבורית, הגדרה זו חושפת את טופולוגיית הניתוב, שמות המארחים, שמות ה-middleware ופורטים של שירותי ה-backend לכל מי שסורק את השרת.

התוויות (labels) על שירות ה-traefik לעיל הן החלופה: לוח הבקרה מנותב כמו כל יישום אחר, תחת שם מתחם אמיתי, מעל TLS, ומאחורי basicauth. ה-service=api@internal הוא הרכיב שמחבר את הנתב ל-API המובנה של Traefik. ניתן להדק את האבטחה עוד יותר על ידי שרשור רשימת כתובות IP מורשות, המיושמת משמאל לימין. אם כתובת ה-IP של משרדכם דינמית, הגדירו את הטווח כ-subnet שניתן על ידי שרת WireGuard VPN שאתם מארחים על אותו ה-VPS וגשו ללוח הבקרה רק דרך המנהרה:

- traefik.http.middlewares.office.ipallowlist.sourcerange=10.0.0.7/32
- traefik.http.routers.dashboard.middlewares=office,dashboard-auth

סיסמת basicauth משותפת אחת מפסיקה להיות הגיונית ברגע שחמישה יישומים זקוקים לחשבונות נפרדים. באותו מיקום של ה-middleware ניתן להגדיר forwardauth שמעביר את ההחלטה ל-Authentik, שרת Single Sign-On בניהול עצמי, כך שלוח הבקרה וכל נתיב לצדו נמצאים מאחורי התחברות אחת שניתן לבטל מנקודה אחת בלבד.

ה-socket של Docker הוא הרשאת root

/var/run/docker.sock הוא API שיכול ליצור מכולה המבצעת mount ל-/ מהמארח. גישה אליו שקולה להרשאות root על המכונה, ו-Traefik זקוק לו כדי לקרוא תוויות (labels).

שמרו על :ro ב-mount, אך היו ברורים לגבי המשמעות: הוא הופך את קובץ ה-socket לקריאה בלבד. הוא אינו עוצר בקשות POST ל-Docker API העוברות דרכו. ההגנה האמיתית היא לעולם לא לתת ל-Traefik גישה ישירה ל-socket, אלא להציב ביניהם proxy מסנן:

  dockerproxy:
    image: tecnativa/docker-socket-proxy   # pin the current tag
    restart: unless-stopped
    environment:
      CONTAINERS: 1
      NETWORKS: 1
      POST: 0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
    networks:
      - proxy

הסירו את ה-volume של ה-socket מ-Traefik והפנו את ה-provider אל ה-proxy:

--providers.docker.endpoint=tcp://dockerproxy:2375

Traefik שומר על גישת קריאה למכולות ולרשתות, ומאבד את היכולת ליצור אובייקטים חדשים.

חומת אש, פורטים, והכלל שכולם טועים בו

שני פורטים פתוחים, בנוסף ל-SSH:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

פורטים שפורסמו על ידי Docker עוקפים את ufw.‏ Docker מחדיר חוקי iptables משלו, אשר מוערכים לפני ה-chains של ufw. לכן, מכולה שהופעלה עם ports: ["3000:3000"] תהיה נגישה מהאינטרנט גם אם מוגדר חוק deny ב-ufw. ההגנה היא מבנית ולא תלויה בתצורת חומת האש: פרסמו פורטים רק מ-Traefik, והגדירו לכל שאר המכולות networks: [proxy] בלבד. אם שירות כלשהו חייב להגיע למארח, בצעו לו bind ל-loopback, באמצעות "127.0.0.1:3000:3000".

פתרון בעיות: שגיאות נפוצות

404 page not found, מוגש על ידי Traefik. לא נמצא router תואם. לפי סדר הסבירות: למכולה חסר traefik.enable=true (כאשר exposedByDefault=false מוגדר); כלל ה-Host() אינו תואם לשם שהקלדת; שם ה-router בתווית אחת שונה מהשם בתווית אחרת (routers.gitea.rule ו-routers.gitea.entrypoints חייבים להיות אותה מילה); או שהקפת את שם המארח במירכאות במקום בגרשיים הפוכים (backticks). גרסה Traefik v3 מחייבת שימוש בגרשיים הפוכים בתוך ה-matchers.

502 Bad Gateway. נמצא router תואם אך לא ניתן היה להגיע ל-backend. כמעט תמיד המכולה אינה מחוברת לרשת proxy, בדוק זאת באמצעות docker inspect -f '{{json .NetworkSettings.Networks}}' gitea. אפשרות נוספת היא loadbalancer.server.port שגוי: ציינת פורט שפורסם, או שהיישום מאזין בפורט אחר. הלוג מציין את הניסיון: dial tcp 172.18.0.5:8080: connect: connection refused.

הדפדפן מציג אזהרה, והתעודה מונפקת עבור TRAEFIK DEFAULT CERT. לא קיימת תעודה עבור שם המארח הזה ו-Traefik הגיש את תעודת ברירת המחדל שלו (self-signed). קרא את שורות ה-ACME:

unable to obtain ACME certificate for domains "git.example.com" ...
acme: error: 400 ... DNS problem: NXDOMAIN looking up A for git.example.com

ה-DNS עדיין לא מצביע על השרת. תקן את ה-record, המתן לסיום ה-TTL, והפעל מחדש את Traefik.

Invalid response from http://git.example.com/.well-known/acme-challenge/... באתגר ה-HTTP: פורט 80 אינו מגיע ל-Traefik מבחוץ, בדרך כלל מדובר ב-firewall ברמת ספק התשתית של ה-VPS, ולא ב-ufw.

תעודות אינן מונפקות, וה-DNS שלך ב-Cloudflare עם "ענן כתום" פעיל. Cloudflare מבצע TLS termination בקצה שלו ו-TLS-ALPN-01 לא יכול להסתיים דרכו. הגדר את ה-record למצב DNS-only בזמן ההנפקה, או עבור לאתגר DNS-01 באמצעות API token. אתגר DNS-01 הוא גם הדרך היחידה להנפיק תעודות wildcards.

לולאת הפניות (Redirect loop). רכיב כלשהו לפני Traefik כבר מבצע TLS termination ומעביר תעבורת plaintext לפורט 80; ה-redirect ב-entrypoint שולח אותה חזרה ל-HTTPS. הסר את אחת משתי ההפניות.

שמירה על פעילות תקינה

יש להגדיר את ה-unit של Docker כך שיעלה עם המערכת (systemctl is-enabled docker), ופקודת restart: unless-stopped תחזיר את ה-stack לפעולה לאחר אתחול. לשליטה מפורשת, יחידת systemd קטנה המריצה את docker compose -f /srv/edge/compose.yml up -d עם RemainAfterExit=yes תעניק לכם systemctl status edge ושליטה על סדר העלייה.

קבעו גרסה ספציפית ל-tag של Traefik (למשל traefik:v3.5, לעולם לא latest). השדרוג מגרסה v2 ל-v3 שינה את תחביר הכללים ואת שמות ה-providers, ופקודת latest אוטומטית תטען מחדש תצורה שהיא כבר לא מבינה. בצעו שדרוג מתוכנן: קראו את הערות המעבר, עדכנו את ה-tag, הריצו docker compose up -d traefik, ועקבו אחר הלוגים. אם אתם עדיין משתמשים ב-tag של v2, מדריך המעבר של Traefik מגרסה v2 ל-v3 מפרט כל שינוי שם, את מצב התאימות, ואת תהליך ה-rollback שישמור על התעודות שלכם.

גבו את ./letsencrypt ואת כרכי הנתונים (data volumes) של כל יישום. Traefik אינו מחזיק מצב (state) אחר שלא ניתן לשחזר מתוך קובץ ה-compose.

מה משתבש בקנה מידה גדול

המחסום הראשון אינו קצב העברת הנתונים, אלא השרת הבודד: מופע Traefik אחד על שרת VPS אחד מהווה נקודת כשל יחידה עבור חמישה יישומים, ומכיוון ש-acme.json מבוסס על אחסון קבצים שטוח, כתיבה אליו משני מופעי Traefik במקביל תוביל להשחתת הנתונים. הרחבת המערכת (scaling out) מחייבת העברת אחסון התעודות אל מחוץ לקובץ מקומי, או ביצוע TLS termination בנקודה אחרת.

המחסום השני הוא חיבורים ארוכי טווח. אירועים שנשלחים מהשרת (Server-sent events), העלאות קבצים גדולות ולקוחות איטיים עלולים להיתקל בזמני הקצוב (timeouts) של נקודת הכניסה; --entryPoints.websecure.transport.respondingTimeouts.readTimeout והפרמטרים writeTimeout ו-idleTimeout הם הכפתורים לשליטה בכך. חיבורי WebSocket עוברים ללא צורך בתצורה נוספת.

המחסום השלישי הוא הדיסק. --accesslog=true כותב ל-stdout, ומנהל ה-json-file של Docker שומר זאת לנצח אלא אם כן הוגדרה הגבלה. הגדירו logging.options.max-size בשירות Traefik, או כתבו את לוג הגישה לקובץ ובצעו לו רוטציה (rotate).

שום דבר מזה אינו מחייב שימוש ב-orchestrator. הוא כן מחייב שרת בשליטתכם, עם כתובת IP ציבורית ופורטים 80 ו-443 פתוחים לעולם; שרת VPS קטן אחד הוא כל רשימת התלויות הנדרשת.

FAQ

האם אני עדיין זקוק ל-certbot אם אני מריץ את Traefik?

לא. ה-ACME resolver של Traefik מבקש ומחדש את התעודה עבור כל שם מתחם שהוא מנתב, ושומר את כולן ב-acme.json. Certbot נותר הכלי המתאים כאשר Nginx או שרת אחר מבצעים TLS termination בעצמם; הרצת שניהם מול אותם שמות מתחם רק תנצל את מכסות ה-rate limits של Let's Encrypt.

מדוע המכולה שלי מחזירה 404 דרך Traefik?

שגיאת 404 שמוגשת על ידי Traefik משמעותה שאף router לא התאים לבקשה. בדקו שהמכולה נושאת את ה-traefik.enable=true (חובה ברגע ש-exposedByDefault=false מוגדר), שהערך של Host() תואם לשם שהקלדתם, וששם ה-router זהה בכל ה-labels של אותו יישום. Traefik v3 דורש גם שימוש ב-backticks בתוך ה-matcher, ולא במירכאות.

מה ההבדל בין 404 ל-502 במקרה זה?

שגיאת 404 משמעותה שהניתוב לא התבצע כלל; שגיאת 502 משמעותה ש-router התאים לבקשה אך ה-backend סירב לחיבור. הגורמים הנפוצים ל-502 הם מכולה שאינה מחוברת לרשת proxy, או loadbalancer.server.port שמצביע על פורט שפורסם (published) במקום על הפורט שבו היישום מאזין בתוך המכולה. ה-access log מציין את הכתובת המדויקת שאליה Traefik ניסה להתחבר.

האם מיפוי ה-Docker socket במצב לקריאה בלבד (read-only) מספיק?

הדגל :ro הופך את קובץ ה-socket לקריאה בלבד, אך לא את ה-API שמאחוריו; בקשות POST עדיין עוברות דרכו, וגישה ל-Docker API שקולה להרשאות root על המארח. הסידור החזק יותר הוא מכולת docker-socket-proxy המוצגת לעיל, אשר חושפת בפני Traefik רק קריאות של מכולות ורשת וחוסמת כתיבה באופן מוחלט.

האם Traefik יכול להנפיק תעודת wildcard?

רק באמצעות ה-DNS-01 challenge, עם API token עבור ספק ה-DNS שלכם. ה-TLS-ALPN-01 וה-HTTP-01 מאמתים כל אחד שם מתחם בודד ואינם יכולים להפיק wildcard. ה-DNS-01 הוא גם הפתרון כאשר CDN כמו Cloudflare מבצע TLS termination לפני ה-VPS שלכם, ושני האתגרים האחרים אינם מושלמים לעולם.