---
title: 'Caddy Docker Compose بروكسي عكسي: HTTPS لبيئة الإنتاج'
description: 'دليل عملي لعام 2026: Caddy 2.11.2 Docker Compose Reverse Proxy مع HTTPS تلقائي، و Security Headers، وإعادة تحميل Zero-downtime، و Cloudflare DNS wildcards.'
url: https://nerdleveltech.com/ar/caddy-reverse-proxy-docker-compose-production-https-tutorial
language: ar
date: '2026-05-11'
category: cloud-devops
tags:
  - caddy
  - docker
  - docker-compose
  - reverse-proxy
  - https
  - lets-encrypt
  - cloudflare
  - devops
  - tls
  - security-headers
markdown_url: https://nerdleveltech.com/ar/caddy-reverse-proxy-docker-compose-production-https-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/caddy-reverse-proxy-docker-compose-production-https-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# Caddy Docker Compose بروكسي عكسي: HTTPS لبيئة الإنتاج

## ملخص

يشرح هذا البرنامج التعليمي كيفية تشغيل [Caddy 2.11.2](https://GitHub.com/caddyserver/caddy/releases/tag/v2.11.2)[^1] كوكيل عكسي (reverse proxy) أمام خدمات Docker متعددة، باستخدام `Docker compose` وملف `Caddyfile` واحد فقط. ستحصل على HTTPS تلقائي من Let's Encrypt، وقصاصة برمجية (snippet) لرؤوس الأمان قابلة لإعادة الاستخدام، وسير عمل لإعادة التحميل بدون توقف، وبناء اختياري باستخدام `xcaddy` يضيف إضافة Cloudflare DNS لشهادات الـ wildcard. المجموعة بالكامل جاهزة للعمل بأمر واحد `Docker compose up -d` وتعمل على أي جهاز Linux مثبت عليه Docker Engine 24+.

## ما ستتعلمه

- كيفية إعداد Caddy كوكيل عكسي في Docker Compose يخدم خدمات HTTP متعددة
- كيفية عمل HTTPS التلقائي في Docker، بما في ذلك المجلدات (volumes) التي يجب الحفاظ عليها
- لماذا يؤدي ربط Caddyfile كملف واحد إلى تعطل `caddy reload`، وحل ربط المجلد
- كيفية تعريف قصاصة `(security-headers)` قابلة لإعادة الاستخدام وتطبيقها على كل موقع
- كيفية إجراء إعادة تحميل بدون توقف من الجهاز المضيف باستخدام `Docker compose exec`
- كيفية بناء صورة Caddy مخصصة مع إضافة Cloudflare DNS لشهادات الـ wildcard
- كيفية تأمين واجهة برمجة تطبيقات (API) الإدارة على المنفذ 2019 في بيئة الإنتاج

## المتطلبات الأساسية

| الأداة                   | الإصدار                                                      |
| ------------------------ | ------------------------------------------------------------ |
| Docker Engine            | 24.0+ (إضافة Compose V2 مدمجة)                               |
| Docker Compose           | v2.24+ (`Docker compose version`)                            |
| مضيف Linux مع IP عام     | المنافذ 80 و 443 و 443/udp يمكن الوصول إليها من الإنترنت     |
| نطاق (Domain) حقيقي      | سجلات DNS من نوع A/AAAA موجهة إلى المضيف قبل تشغيل Caddy[^2] |
| اختياري: حساب Cloudflare | رمز API مع صلاحية `Zone.DNS:Edit` لشهادات wildcard           |

لا تحتاج إلى Go أو `xcaddy` أو أي ملفات ثنائية لـ Caddy على المضيف. كل شيء يحدث داخل الحاويات المبنية من صورة `caddy:2.11.2-alpine` الرسمية[^3].

## كيف يعمل HTTPS التلقائي في Caddy داخل Docker

عندما يخدم Caddy اسم مضيف (وليس مجرد منفذ أو IP)، فإنه يقوم تلقائيًا بتسجيل حساب ACME، وطلب شهادة، وإكمال تحدي HTTP-01 على المنفذ 80، وتحويلك إلى HTTPS على 443، وتجديد الشهادة قبل انتهاء صلاحيتها[^2]. في Docker، يجب توفر ثلاثة شروط ليعمل ذلك بشكل كامل:

1. **توجيه DNS إلى مضيفك.** لن يقوم Caddy بإصدار شهادة لاسم لا يشير سجل A/AAAA الخاص به إلى الجهاز الذي يعمل عليه.
2. **المنافذ 80 و 443 يمكن الوصول إليها من الإنترنت.** كلاهما مطلوب لتحدي HTTP-01 وخدمة HTTPS.
3. **مجلد `/data` دائم.** هذا هو المكان الذي يخزن فيه Caddy الشهادات والمفاتيح الخاصة و OCSP staples. ملف README الرسمي على Docker Hub صريح في هذا الشأن: *"يجب عدم معاملة دليل البيانات كذاكرة مؤقتة. محتوياته ليست عابرة."*[^3] إذا قمت بمسحه، سيعيد Caddy إصدار كل شهادة من الصفر — وهو طريق سريع للوصول إلى حدود معدل Let's Encrypt.

هذه النقطة الثالثة هي الخطأ الأكثر شيوعًا في بيئات الإنتاج، وهي السبب في أن هذا البرنامج التعليمي يستخدم المجلدات المسماة (named volumes) منذ الخطوة الأولى.

## أقل إعداد ممكن لـ Caddy

قم بإنشاء دليل للمشروع وثلاثة ملفات:

```bash
mkdir -p caddy-stack/caddy && cd caddy-stack
touch compose.yaml caddy/Caddyfile .env
```

### الخطوة 1 — كتابة ملف Compose

```yaml
# compose.yaml
services:
  caddy:
    image: caddy:2.11.2-alpine
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./caddy:/etc/caddy
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN:-}
    networks: [edge]

  API:
    image: traefik/whoami:latest
    restart: unless-stopped
    networks: [edge]

  app:
    image: traefik/whoami:latest
    restart: unless-stopped
    networks: [edge]

volumes:
  caddy_data:
  caddy_config:

networks:
  edge:
    driver: bridge
```

بعض التفاصيل التي تستحق التذكر:

- **لا يوجد مفتاح `version:`.** أسقطت مواصفات Compose حقل `version:` في المستوى الأعلى، ويقوم `Docker compose` الحديث بطباعة تحذير عند تضمينه[^4]. ابدأ من `services:`.
- **`cap_add: NET_ADMIN`** يسمح لـ `quic-go` بزيادة أحجام مخزن UDP المؤقت لـ HTTP/3 دون الحاجة إلى رفع قيم `sysctl` للنواة على المضيف[^3].
- **`./caddy:/etc/caddy` هو ربط دليل، وليس ربط ملف.** هذا مقصود (انظر القسم التالي).
- **`traefik/whoami`** هو خادم Go صغير يطبع تفاصيل طلب HTTP بتنسيق JSON[^5]. نستخدم نسختين كخلفيات بديلة حتى تتمكن من رؤية توجيه حركة المرور دون الحاجة إلى إحضار تطبيقاتك الخاصة.

### الخطوة 2 — كتابة ملف Caddyfile

```caddyfile
# caddy/Caddyfile

{
    email you@example.com
}

(security-headers) {
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        X-Content-Type-Options "nosniff"
        X-Frame-Options "SAMEORIGIN"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "camera=(), microphone=(), geolocation=()"
        -Server
        -X-Powered-By
    }
}

API.example.com {
    import security-headers
    reverse_proxy API:80
}

app.example.com {
    import security-headers
    reverse_proxy app:80
}
```

استبدل `you@example.com` واسمي المضيفين بالقيم التي تملكها بالفعل. يقرأ Caddy الـ `email` من كتلة الخيارات العالمية مرة واحدة ويستخدمها لكل تسجيل ACME[^6]. تم ترك واجهة برمجة تطبيقات الإدارة على وضعها الافتراضي — مرتبطة بـ `localhost:2019` داخل الحاوية — لأننا سنستخدم `caddy reload` لاحقًا، وهذا الأمر يتحدث إلى واجهة الإدارة. سنقوم بتأمينها في قسم مخصص أدناه.

### الخطوة 3 — بدء التشغيل

```bash
Docker compose up -d
Docker compose logs -f caddy
```

في غضون ثوانٍ قليلة، يجب أن ترى Caddy يقوم بإصدار الشهادات ويبدأ في الاستماع:

```text
INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
INFO    autosaved config (load with --resume flag)
INFO    serving initial configuration
INFO    tls.obtain    acquiring lock         {"identifier": "API.example.com"}
INFO    tls.issuance.acme    waiting on internal rate limiter
INFO    tls.obtain    certificate obtained successfully  {"identifier": "API.example.com"}
```

### الخطوة 4 — التحقق من العمل

```bash
curl -sSI https://API.example.com | head -n 5
# HTTP/2 200
# alt-svc: h3=":443"; ma=2592000
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# x-content-type-options: nosniff
# x-frame-options: SAMEORIGIN

curl -s https://API.example.com/API | python3 -m json.tool
# {
#   "hostname": "abc123…",
#   "ip": ["172.18.0.3"],
#   "headers": {
#     "X-Forwarded-For": ["1.2.3.4"],
#     "X-Forwarded-Proto": ["https"]
#   },
#   "method": "GET",
#   "host": "API.example.com"
# }
```

يتم توفير `X-Forwarded-Proto: https` و `X-Forwarded-For` تلقائيًا — حيث يقوم توجيه `reverse_proxy` في Caddy بتعيين كليهما افتراضيًا[^7]. كما يتم تمرير رأس `Host` الأصلي دون تغيير، وهو ما تحتاجه معظم التطبيقات خلف الوكيل.

## لماذا لا يجب عليك ربط Caddyfile كملف واحد

تخطئ معظم الشروحات في هذا الأمر. يكتبون `./Caddyfile:/etc/caddy/Caddyfile` وينتقلون للنقطة التالية. يحتوي ملف README الرسمي على Docker Hub على تحذير `⚠️` في الأعلى مباشرة:

> *إذا تم استخدام vim أو محرر آخر يغير inode الخاص بالملف المحرر، فلن يتم تطبيق التغييرات داخل الحاوية إلا عند إعادة إنشاء الحاوية.*[^3]

ما يحدث فعليًا: عند الحفظ باستخدام Vim أو VS Code أو معظم المحررات، يقوم المحرر بالكتابة في ملف مؤقت ثم يعيد تسميته ليحل محل الملف الأصلي. يتغير الـ inode على المضيف، لكن الربط داخل الحاوية يظل مثبتًا على الـ inode القديم، لذا لا يرى Caddy الملف الجديد أبدًا. ينجح أمر `Docker compose exec caddy caddy reload`، لكنه يقرأ المحتويات غير المتغيرة.

الحل هو ربط الدليل الأب (`./caddy:/etc/caddy` في ملف Compose أعلاه) وتعديل `caddy/Caddyfile` من الخارج. تراقب الحاوية الدليل، وليس الملف، لذا تكون عمليات تبديل الـ inode غير مرئية.

## إعادة التحميل بدون إعادة تشغيل

```bash
Docker compose exec -w /etc/caddy caddy caddy reload
```

يؤدي هذا إلى تشغيل `caddy reload` في نفس الحاوية. عملية إعادة التحميل ذرية (atomic): إذا تم تحليل ملف Caddyfile الجديد ولكن فشل تطبيقه، يعود Caddy إلى التكوين السابق دون قطع الاتصال[^8]. تظل جلسات TLS الخاصة بك نشطة، وتنتهي الطلبات الجارية على التكوين القديم، بينما تستلم الطلبات الجديدة التكوين الجديد.

إذا بدأت Caddy بنقطة الدخول القياسية (`caddy run --config /etc/caddy/Caddyfile`)، يمكنك أيضًا إرسال `SIGUSR1` إلى العملية الرئيسية — هذه ميزة في الإصدار 2.11.1+ تقوم بإعادة قراءة الملف إذا تم تحميله أصلاً من ملف ولم يتم تغييره منذ ذلك الحين عبر واجهة برمجة تطبيقات (API) الإدارة API[^9]:

```bash
Docker compose kill -s SIGUSR1 caddy
```

استخدم `caddy reload` في السكربتات (فهو يعيد رمز خروج غير صفري إذا كان التكوين الجديد غير صالح). واستخدم `SIGUSR1` إذا كان لديك عملية مراقبة لملف التكوين وتحتاج إلى مشغل يعتمد على الإشارات (signal-driven trigger).

## إضافة قصاصة رؤوس أمان قابلة لإعادة الاستخدام

كتلة `(security-headers)` في ملف Caddyfile أعلاه هي ما يسميه Caddy *قصاصة (snippet)* — وهي قطعة من التكوين قابلة لإعادة الاستخدام تشير إليها باستخدام `import`[^10]. تحدد القصاصة رؤوس الاستجابة مرة واحدة، ويقوم `import security-headers` بإدراجها في كل كتلة موقع. إذا أضفت موقعًا ثالثًا، فلن تحتاج إلا لتذكر سطر الاستيراد.

قيم الرؤوس نفسها هي إعدادات افتراضية متحفظة تعمل مع معظم التطبيقات:

| الرأس (Header)              | القيمة                                         | السبب                                                                                |
| --------------------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------ |
| `Strict-Transport-Security` | `max-age=31536000; includeSubDomains; preload` | HSTS لمدة عام واحد، لجميع النطاقات الفرعية، ومؤهل لقائمة التحميل المسبق للمتصفح[^11] |
| `X-Content-Type-Options`    | `nosniff`                                      | يمنع تخمين نوع MIME (MIME sniffing)                                                  |
| `X-Frame-Options`           | `SAMEORIGIN`                                   | يمنع اختطاف النقرات (clickjacking) عبر `<iframe>`                                    |
| `Referrer-Policy`           | `strict-origin-when-cross-origin`              | يزيل المسار/الاستعلام عند مغادرة أصلك (origin)                                       |
| `Permissions-Policy`        | `camera=(), microphone=(), geolocation=()`     | يرفض الوصول إلى المستشعرات افتراضيًا؛ يتم التفعيل لكل موقع على حدة                   |
| `-Server` و `-X-Powered-By` | (إزالة)                                        | يوقف تسريب هوية الخادم للمهاجمين                                                     |

لاحظ علامة `-` البادئة *لإزالة* رأس — يقوم Caddy بتحليل `-Header-Name` كعملية حذف في كتلة `header`[^12]. بشكل افتراضي، يرسل Caddy رأس `Server: Caddy` في كل استجابة، وغالبًا ما تضيف التطبيقات الأصلية (upstream) رأس `X-Powered-By` الخاص بها؛ كلاهما يسرب هوية الخادم للمهاجمين، لذا تقوم القصاصة بإزالتهما.

## شهادات Wildcard مع إضافات Cloudflare DNS

لا يمكن لنسخة Caddy الافتراضية إصدار شهادات wildcard (`*.example.com`) لأن ذلك يتطلب تحدي ACME DNS-01، والذي يتطلب بدوره إضافة لمزود DNS. توجد وحدة Cloudflare في `GitHub.com/caddy-dns/cloudflare`[^13] وتقوم بتثبيتها عن طريق إعادة بناء Caddy باستخدام `xcaddy`. أبسط طريقة للقيام بذلك في Docker هي نمط البناء متعدد المراحل (multi-stage builder) من ملف README الرسمي لـ Docker Hub[^3]:

```dockerfile
# caddy/Dockerfile
FROM caddy:2.11.2-builder-alpine AS builder
RUN xcaddy build \
    --with GitHub.com/caddy-dns/cloudflare

FROM caddy:2.11.2-alpine
COPY --from=builder /usr/bin/caddy /usr/bin/caddy
```

قم بتحديث خدمة Caddy في `compose.yaml` للبناء من ملف Dockerfile هذا بدلاً من سحب الصورة الجاهزة:

```yaml
  caddy:
    build:
      context: ./caddy
      dockerfile: Dockerfile
    restart: unless-stopped
    # …بقية الخدمة دون تغيير
```

قم بإنشاء رمز (token) لواجهة برمجة تطبيقات Cloudflare API من *My Profile → API Tokens → Create Token → Edit zone DNS* (فقط `Zone.DNS:Edit` على النطاق الذي تتحكم فيه). ضعه في ملف `.env`:

```bash
# .env
CLOUDFLARE_API_TOKEN=cf_token_value_here
```

ثم قم بتحديث ملف Caddyfile لإصدار شهادة wildcard:

```caddyfile
*.example.com {
    import security-headers
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }

    @API host API.example.com
    handle @API {
        reverse_proxy API:80
    }

    @app host app.example.com
    handle @app {
        reverse_proxy app:80
    }
}
```

قم بتشغيل `Docker compose up -d --build` وسيقوم Caddy بطلب شهادة wildcard من Let's Encrypt عن طريق كتابة سجل TXT في نطاق Cloudflare الخاص بك، وإزالته بعد التحقق، وتخزين شهادة `*.example.com` الناتجة في `caddy_data`. العملية بالكامل تتم تلقائيًا وتعيد التشغيل قبل انتهاء الصلاحية[^2].

## تأمين واجهة برمجة تطبيقات الإدارة API

تستمع واجهة برمجة تطبيقات الإدارة في Caddy إلى `localhost:2019` افتراضيًا[^14]. داخل الحاوية، "localhost" هو الـ loopback الخاص بالحاوية، والذي لا يمكن الوصول إليه من المضيف أو الحاويات الأخرى ما لم تقم بنشر المنفذ 2019 في `compose.yaml`. لذا فأنت لا تعرض واجهة برمجة التطبيقات API للإنترنت مباشرة عن طريق الخطأ — ولكن أي شخص يمكنه عمل `Docker exec` داخل الحاوية يمكنه تشغيل واجهة برمجة تطبيقات التكوين بالكامل.

وثائق فريق Caddy صريحة: *"إذا كنت تقوم بتشغيل كود غير موثوق به على خادمك، فتأكد من حماية نقطة نهاية الإدارة الخاصة بك عن طريق عزل العمليات، وتصحيح البرامج الضعيفة، وتكوين نقطة النهاية للربط بمقبس unix (unix socket) مخصص بدلاً من ذلك."*[^14]

ثلاثة خيارات مناسبة للإنتاج، مرتبة حسب مستوى الحذر:

1. **ربط واجهة برمجة تطبيقات الإدارة API بمقبس Unix** باستخدام `admin unix//run/caddy/admin.sock` في كتلة الخيارات العالمية، ثم تركيب هذا المقبس فقط في الحاويات التي تحتاج فعليًا لإعادة التحميل (مثل حاوية جانبية للنشر). تظل عمليات إعادة التحميل تعمل — `caddy reload --address unix//run/caddy/admin.sock` — ولكن لا يتم كشف أي مستمع TCP.
2. **تعطيل واجهة برمجة تطبيقات الإدارة API تمامًا** باستخدام `admin off`. سيستمر Caddy في خدمة حركة المرور ولكن `caddy reload` سيتوقف عن العمل، لذا ستتطلب تغييرات التكوين `Docker compose restart caddy`. إعادة التشغيل تكون سلسة (تنتهي الطلبات الجارية على التكوين القديم) ولكنها تسبب توقفًا بسيطًا لعدة ثوانٍ.
3. **الاحتفاظ بمستمع `localhost:2019` الافتراضي** مع عدم نشر المنفل 2019 في `compose.yaml`. هذا ما تفعله المجموعة أعلاه. يقتصر خطر التعرض على أي شخص يمكنه عمل `Docker exec` داخل حاوية Caddy — وهو عادةً حد ثقة مقبول لمضيف مستأجر واحد، ولكنه يستحق التفكير فيه.

لا توجد إجابة صحيحة عالميًا؛ اختر بناءً على من لديه وصول إلى سطر الأوامر (shell) في مضيف Docker الخاص بك وعدد المرات التي تحتاج فيها فعليًا لإعادة التحميل.

## فحوصات الصحة النشطة لعمليات نشر أكثر أمانًا

عندما يكون لديك نسخ متعددة من الواجهة الخلفية، يمكن لتوجيه `reverse_proxy` في Caddy فحصها والتوقف عن إرسال حركة المرور إلى الحاويات غير السليمة:

```caddyfile
API.example.com {
    import security-headers
    reverse_proxy API-v1:80 API-v2:80 {
        lb_policy least_conn
        health_uri /healthz
        health_interval 10s
        health_timeout 2s
        health_status 200
        fail_duration 30s
        max_fails 3
    }
}
```

يتم استدعاء `health_uri` كل `health_interval` لكل خادم أصلي؛ ويعتبر الخادم متوقفًا عندما يفشل في إرجاع `health_status` خلال `health_timeout`[^15]. زوج `fail_duration` / `max_fails` هو فحص سلبي: إذا فشل طلب حقيقي، يتذكر Caddy ذلك لمدة 30 ثانية وينتقل إلى نظير سليم بعد 3 حالات فشل من هذا القبيل. سياسة موازنة التحميل الافتراضية في Caddy هي `random`؛ بينما تعتبر `least_conn` عادةً خيارًا افتراضيًا أفضل أمام الاتصالات طويلة الأمد مثل WebSockets أو SSE.

إذا كنت ترغب في فحص حالة الخوادم الأصلية في وقت التشغيل، فإن واجهة برمجة تطبيقات الإدارة API لديها نقطة نهاية مخصصة للقراءة فقط. تأتي صورة Caddy المبنية على Alpine مع `wget` الخاص بـ BusyBox (لا يوجد `curl`)، لذا استخدم ذلك:

```bash
Docker compose exec caddy wget -qO- http://localhost:2019/reverse_proxy/upstreams
# [{"address":"API-v1:80","num_requests":4,"fails":0},
#  {"address":"API-v2:80","num_requests":5,"fails":0}]
```

(مع ضبط `admin off`، ستختفي نقطة النهاية مع بقية الـ API.)

## قائمة التحقق من الصحة

بعد تشغيل `Docker compose up -d`، تأكد من كل مما يلي:

```bash
# 1. Caddy is listening on 80, 443, and 443/udp
Docker compose ps caddy --format "table {{.Service}}\t{{.Ports}}"
# SERVICE   PORTS
# caddy     0.0.0.0:80->80/tcp, 0.0.0.0:443->443/tcp, 0.0.0.0:443->443/udp

# 2. HTTP redirects to HTTPS
curl -sI http://API.example.com | grep -i location
# location: https://API.example.com/

# 3. The certificate is issued by Let's Encrypt, not Caddy's local CA
echo | openssl s_client -servername API.example.com -connect API.example.com:443 2>/dev/null \
    | openssl x509 -noout -issuer
# issuer=C = US, O = Let's Encrypt, CN = R11
# (CN will be one of R10–R14 for RSA leaf certs or E5–E9 for ECDSA)

# 4. Security headers are present
curl -sI https://API.example.com | grep -iE "(strict|frame|content-type-options|referrer)"

# 5. The data volume actually contains certificates
Docker compose exec caddy ls -la /data/caddy/certificates/acme-v02.API.letsencrypt.org-directory/
# API.example.com   app.example.com
```

إذا فشل أي من هذه الاختبارات، انتقل إلى جدول استكشاف الأخطاء وإصلاحها.

## استكشاف الأخطاء وإصلاحها

| العرض                                                         | السبب المحتمل                                                                                                                    | الحل                                                                                                                |
| ------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `tls.obtain could not get certificate` مع انتهاء مهلة HTTP-01 | المنفذ 80 غير قابل للوصول من الإنترنت، أو سجل DNS A/AAAA لا يشير إلى هذا المضيف                                                  | يجب أن يصل `curl -I http://yourdomain/` من جهاز خارج شبكتك إلى Caddy. تحقق من DNS باستخدام `dig +short yourdomain`. |
| الشهادة هي `Caddy Local Authority` بدلاً من Let's Encrypt     | عنوان الموقع في Caddyfile هو `localhost`، أو IP، أو اسم لا يتم حله علنًا                                                         | استخدم اسم مضيف عام حقيقي. يعود Caddy لاستخدام سلطة الشهادات الداخلية الخاصة به للعناوين غير العامة[^2].            |
| `caddy reload` يخرج بـ 0 ولكن الإعدادات لا تتغير              | لقد قمت بربط Caddyfile كملف واحد وقام المحرر بتغيير inode الخاص به عند الحفظ                                                     | قم بربط المجلد الأب بدلاً من ذلك (`./caddy:/etc/caddy`)[^3].                                                        |
| HTTP/3 يعمل في Chrome ولكن ليس في curl                        | نسخة Curl الأساسية لا تدعم QUIC؛ هذا أمر طبيعي                                                                                   | اختبر باستخدام `curl --http3` من نسخة تدعم QUIC، أو تحقق من ترويسة `Alt-Svc:` في استجابة HTTP/2.                    |
| `permission denied` على `caddy_data` بعد نقل المضيف           | تم نسخ وحدة التخزين (volume) دون الحفاظ على ملكية Linux؛ يعمل Caddy كـ UID 0 في الصورة الرسمية ولكن النسخة قد تكون غيرت الأذونات | `Docker compose exec --user root caddy chown -R 0:0 /data /config` ثم أعد التشغيل.                                  |
| إضافة Cloudflare DNS: `unknown directive: dns`                | لقد نسيت البناء متعدد المراحل (multi-stage build) وما زلت تستخدم النسخة الأساسية `caddy:2.11.2-alpine`                           | أعد البناء باستخدام `Dockerfile` الموضح أعلاه و `Docker compose up -d --build`.                                     |

## قائمة التحقق للإنتاج

قبل توجيه حركة المرور الحقيقية إلى هذا النظام:

- ثبّت وسم الصورة الدقيق (`caddy:2.11.2-alpine`)، ولا تستخدم أبدًا `:latest` — يحذر ملف README الرسمي من ذلك صراحةً[^3].
- احتفظ بـ `caddy_data` و `caddy_config` في وحدة تخزين (volume) مدعومة بنسخ احتياطي.
- اختر وضعية تأمين الـ admin-API (Unix socket، أو إيقاف، أو loopback الافتراضي) — راجع القسم المخصص أعلاه.
- أضف استراتيجية لتدوير السجلات (log rotation) (توجيه `log` في Caddyfile أو شاحن سجلات خارجي) — أضاف Caddy 2.11.2 دعم `zstd` لتدوير السجلات وألغى `roll_gzip` لصالح `roll_compression`[^1].
- استبدل حاويات `traefik/whoami` بخوادمك الخلفية الحقيقية؛ صورة whoami هي أداة لتصحيح الأخطاء وليست تطبيقًا للإنتاج.
- قم بتشغيل `Docker compose pull && Docker compose up -d` بشكل دوري للحصول على إصدارات Caddy التصحيحية (أصلح v2.11.2 ثغرة في `forward_auth` و `copy_headers` كانت تسمح لترويسات الهوية المقدمة من العميل بالوصول إلى الخادم الخلفي عندما لا تعيد خدمة المصادقة كل ترويسة منسوخة — وهذا مهم إذا كنت تستخدم `forward_auth` مع بروكسي SSO[^1]).

## قراءات إضافية

- [تجميع اتصالات Postgres في بيئة الإنتاج باستخدام PgBouncer و Supavisor](https://nerdleveltech.com/production-postgres-pooling-pgbouncer-Supabase-supavisor-tutorial) — الاقتران الطبيعي للخوادم الخلفية مع حزمة Caddy
- [دليل Cloudflare Workers + R2 image CDN](https://nerdleveltech.com/cloudflare-workers-r2-image-cdn-tutorial) — عندما تريد تحويلات للصور على الحافة (edge-side) بالإضافة إلى البروكسي العكسي
- [الانتقال من Ingress إلى Kubernetes Gateway API](https://nerdleveltech.com/migrate-ingress-to-Kubernetes-gateway-API-tutorial) — النسخة الخاصة بـ Kubernetes لنفس المشكلة

## المصادر

[^1]: ملاحظات إصدار Caddy v2.11.2 — <https://GitHub.com/caddyserver/caddy/releases/tag/v2.11.2>

[^2]: HTTPS التلقائي — <https://caddyserver.com/docs/automatic-https>

[^3]: نظرة عامة على صورة Caddy الرسمية لـ Docker — <https://hub.Docker.com/_/caddy>

[^4]: مواصفات Compose: عناصر المستوى الأعلى للإصدار والاسم — <https://docs.Docker.com/reference/compose-file/version-and-name/>

[^5]: traefik/whoami — <https://GitHub.com/traefik/whoami>

[^6]: خيارات Caddyfile العالمية (`email`) — <https://caddyserver.com/docs/caddyfile/options#email>

[^7]: توجيه `reverse_proxy` — <https://caddyserver.com/docs/caddyfile/directives/reverse_proxy>

[^8]: API: POST /load — <https://caddyserver.com/docs/API#post-load>

[^9]: إعادة تحميل SIGUSR1 (v2.11.1+) — <https://GitHub.com/caddyserver/caddy/releases/tag/v2.11.1>

[^10]: توجيه `import` والقصاصات البرمجية — <https://caddyserver.com/docs/caddyfile/directives/import>

[^11]: Strict-Transport-Security — <https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Strict-Transport-Security>

[^12]: توجيه `header` — <https://caddyserver.com/docs/caddyfile/directives/header>

[^13]: موديول caddy-dns/cloudflare — <https://GitHub.com/caddy-dns/cloudflare>

[^14]: Caddy admin API — <https://caddyserver.com/docs/API>

[^15]: فحوصات الصحة (health checks) لـ `reverse_proxy` — <https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#health-checks>
