لماذا Traefik؟
بمجرد أن يستضيف خادمٌ عدة تطبيقات داخل حاويات، تحتاج إلى وكيل عكسي (reverse proxy) أمامها: نقطة دخول واحدة على المنفذين 80 و443، توجّه كل نطاق إلى الحاوية المناسبة وتتولى HTTPS. يؤدي Nginx هذه المهمة جيداً، لكن كل تطبيق جديد يتطلب كتابة مضيف افتراضي وإعادة تحميل الإعدادات وتوليد شهادة.
أما Traefik فيعكس المنطق: يقرأ واجهة Docker البرمجية، ويكتشف الحاويات، ويضبطها انطلاقاً من الوسوم (labels) الخاصة بها. يكفي تشغيل حاوية لنشرها عبر HTTPS بشهادة Let's Encrypt يُحصل عليها وتُجدَّد تلقائياً.
الشبكة المشتركة
يجب أن يتشارك Traefik والتطبيقات التي يعرضها شبكة Docker واحدة. أنشئها مرة واحدة:
docker network create proxy
Traefik نفسه
ملف /opt/traefik/compose.yaml:
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.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --certificatesresolvers.le.acme.email=contact@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
- --api.dashboard=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$$Hq3vN2kS$$exempleDeHashAremplacer
networks:
proxy:
external: true
بعض النقاط المهمة:
exposedbydefault=false: لا تُنشر أي حاوية دون الوسمtraefik.enable=true. بدون هذا الخيار قد تُكشف قاعدة بيانات شُغّلت عن طريق الخطأ.- إعادة التوجيه من HTTP إلى HTTPS مصرَّح بها مرة واحدة على مستوى نقطة الدخول
web. - مقبس Docker مركَّب للقراءة فقط، لكنه يمنح مع ذلك صلاحيات واسعة على المضيف: لتأمين إضافي، ضع وكيلاً للمقبس (مثل
tecnativa/docker-socket-proxy) بين Traefik وDocker. - في ملف Compose يجب مضاعفة علامات
$في التجزئة ($$). ولّد التجزئة بالأمرhtpasswd -nb admin password(من الحزمةapache2-utils).
cd /opt/traefik && docker compose up -d
docker compose logs -f traefik
نشر تطبيق
من جهة التطبيق، يكفي الانضمام إلى الشبكة proxy ووصف التوجيه بالوسوم. مثال بحاوية Symfony (PHP-FPM وNginx داخل الصورة، على المنفذ 8080):
services:
app:
image: ghcr.io/moi/app:latest
restart: unless-stopped
env_file: .env.prod
networks:
- proxy
- internal
labels:
- traefik.enable=true
- traefik.http.routers.app.rule=Host(`app.example.com`) || Host(`www.app.example.com`)
- traefik.http.routers.app.entrypoints=websecure
- traefik.http.routers.app.tls.certresolver=le
- traefik.http.routers.app.middlewares=secure-headers,rate-limit
- traefik.http.services.app.loadbalancer.server.port=8080
- traefik.http.middlewares.secure-headers.headers.stsSeconds=31536000
- traefik.http.middlewares.secure-headers.headers.stsIncludeSubdomains=true
- traefik.http.middlewares.secure-headers.headers.contentTypeNosniff=true
- traefik.http.middlewares.secure-headers.headers.frameDeny=true
- traefik.http.middlewares.rate-limit.ratelimit.average=50
- traefik.http.middlewares.rate-limit.ratelimit.burst=100
database:
image: mysql:8.4
restart: unless-stopped
environment:
MYSQL_DATABASE: app
MYSQL_USER: app
MYSQL_PASSWORD_FILE: /run/secrets/db_password
MYSQL_RANDOM_ROOT_PASSWORD: "yes"
secrets:
- db_password
volumes:
- db-data:/var/lib/mysql
networks:
- internal
networks:
proxy:
external: true
internal:
volumes:
db-data:
secrets:
db_password:
file: ./secrets/db_password.txt
قاعدة البيانات مرتبطة بالشبكة internal فقط: لا يستطيع Traefik الوصول إليها، ولا تنشر أي منفذ. عند أول تنفيذ للأمر docker compose up -d يكتشف Traefik الحاوية، ويحصل على الشهادة للنطاقين، ويبدأ توجيه الحركة خلال ثوانٍ ودون أي إعادة تشغيل.
قراءة وسم توجيه
تتبع كل الوسوم القواعد نفسها: traefik.http.<type>.<name>.<option>.
- routers: أي طلب (قاعدة
HostأوPathPrefix…) يصل إلى أي نقطة دخول، مع أي إعدادات TLS وأي طبقات وسيطة - services: إلى أي منفذ في الحاوية تُرسَل الحركة (ضروري إذا كانت الصورة تعرض عدة منافذ)
- middlewares: التحويلات المطبَّقة في الطريق (الترويسات، المصادقة، تحديد معدل الطلبات، إعادة التوجيه، الضغط)
يمكن إعادة استخدام طبقة وسيطة معرَّفة على حاوية ما في الحاويات الأخرى: عرّف ترويسات الأمان مرة واحدة على حاوية Traefik، ثم أشر إليها باللاحقة @docker، مثل secure-headers@docker.
شهادات wildcard عبر تحدي DNS
يتطلب تحدي HTTP أن يكون الخادم متاحاً على المنفذ 80. وللحصول على شهادة *.example.com، أو لخادم غير مكشوف، استخدم تحدي DNS. مع Cloudflare مثلاً:
- --certificatesresolvers.le.acme.dnschallenge=true
- --certificatesresolvers.le.acme.dnschallenge.provider=cloudflare
environment:
CF_DNS_API_TOKEN_FILE: /run/secrets/cf_token
لا يحتاج رمز Cloudflare إلا إلى صلاحية Zone ← DNS ← Edit على النطاق المعني.
استكشاف الأخطاء
- الخطأ 404 "page not found": الموجِّه غير موجود. تحقق من
traefik.enable=trueومن قاعدةHost(علامات backtick إلزامية). - الخطأ 502 Bad Gateway: يجد Traefik الحاوية لكنه لا يصل إليها. هل الحاوية على الشبكة
proxy؟ وهل منفذloadbalancerصحيح؟ - شهادة موقَّعة ذاتياً "TRAEFIK DEFAULT CERT": فشل تحدي ACME. راجع سجلات Traefik: هل يشير DNS إلى الخادم، وهل المنفذ 80 مفتوح؟
- حدود Let's Encrypt: أثناء التجارب استخدم خادم الاختبار (
--certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory) حتى لا تستنفد الحصص.
بعد الإعداد، تصبح إضافة تطبيق جديد مسألة ثلاثة أو أربعة وسوم. وهذا ما يجعل Traefik مريحاً جداً لخادم شخصي أو بنية تحتية صغيرة تستضيف خدمات كثيرة.