> ## Content Index
> Fetch the complete content index at: https://arabroot.io/llms.txt
> Use this file to discover other available public pages before exploring further.

# تثبيت WordPress مع MariaDB وDocker Compose
- URL: https://arabroot.io/articles/تثبيت-wordpress-مع-docker-compose/
- Published: 2026-09-26T19:30:00.000Z
- Updated: 2026-10-04T20:05:55.000Z
- Description: شغل موقع WordPress على خادمك بإعداد يصلح للاستخدام الفعلي، لا للتجربة فقط. نثبته مع MariaDB عبر Docker Compose، ونفعل HTTPS، ونجهز نسخاً احتياطياً يمكنك استعادته عند الحاجة.
- Author: فريق عرب رووت
- Tags: الأدلة التقنية, تطبيقات لفريقك, الاستضافة الذاتية, WordPress

إذا أردت أن تطلق مدونة أو موقعاً لشركتك على [WordPress](https://wordpress.org/?ref=arabroot.io)، فالحل الأول الذي يخطر على البال هو أن تشترك في استضافة مشتركة Shared Hosting وتضغط زر التثبيت بنقرة واحدة One-Click Install من لوحة التحكم، وهذا الحل يعمل في اليوم الأول، ولكنك سوف تجد مع الوقت أنه يعيش على سيرفر Server يتقاسمه معك عشرات العملاء، فلا تختار فيه إصدار PHP ولا حدود الرفع ولا موعد التحديث، وإذا أبطأ موقع جارك أبطأ موقعك معه، والنسخ الاحتياطي Backup فيه عادة نسخة تحتفظ بها الشركة لأيام قليلة ولم تجرب أنت استعادتها في أي يوم. والحل الثاني هو الاستضافة المدارة Managed Hosting المخصصة لـ WordPress، وهي حل جيد ومريح، ولكنك تدفع فيها عن كل موقع، وتقيدك أحياناً في الإضافات Plugins التي تسمح بها، وتبقى بياناتك على سيرفرات شركة أخرى.

لذلك فالحل الأنسب إذا كان عندك سيرفر تشغل عليه خدماتك الأخرى هو أن تستضيف WordPress بنفسك عبر Docker Compose، فتتحكم في الإضافات والبيانات والتكلفة، وتشغل الموقع بجانب بقية خدماتك على السيرفر نفسه، ولكن تذكر أن ثمن ذلك هو أن التحديثات Updates والأمان Security والنسخ الاحتياطي تصبح مسؤوليتك أنت، فإذا كان الموقع مصدر دخل أساسياً ولم يكن في فريقك من يتابعه، فقد تكون الاستضافة المدارة أجدى لك من الناحية الاقتصادية.

والتثبيت بـ Docker نفسه يستغرق دقائق، ولكن كثيراً من الأمثلة المنشورة لا يصلح لبيئة الإنتاج Production، فسوف تجد فيها كلمة مرور Password مثل «TheDbPassword»، وImage بالوسم Tag `latest` يتغير محتواها دون علمك، وحد رفع Upload Limit لا يتجاوز 2 ميجابايت، ثم يدخل HTTPS خلف الـ Reverse Proxy في حلقة تحويل Redirect Loop لا تنتهي، والسبب أن هذه الأمثلة كتبت لتعمل على جهاز المطور وليس لتبقى سنوات على سيرفر حقيقي.

وقد يتساءل البعض: لماذا لا أكتفي بالمثال المنشور في Docker Hub وأصلح ما يظهر لي من مشكلات لاحقاً؟ والإجابة أن أغلب هذه المشكلات لا تظهر في اليوم الأول، وإنما تظهر حين يرفع أحد المحررين أول فيديو، أو حين تحتاج إلى نسخة احتياطية فتكتشف أنك لم تأخذ إلا نصف الموقع، وإصلاحها بعد أن يمتلئ الموقع بالمحتوى أصعب بكثير من بنائها صحيحة من البداية.

وسوف نناقش في هذا المقال ما يلي:

- تثبيت WordPress مع MariaDB عبر Docker Compose بكلمات مرور مولدة وإصدارات محددة بأرقامها وفحوصات صحة Health Checks، وشرح كل سطر مهم في الملف.
- رفع حدود الرفع في PHP، وتشغيل HTTPS خلف الـ Reverse Proxy دون حلقة تحويل.
- معالج التثبيت والروابط الدائمة وصحة الموقع، ثم الإدارة من الطرفية بـ wp-cli.
- النسخ الاحتياطي لقاعدة البيانات وwp-content والاستعادة على سيرفر جديد، ثم التحديث وأشهر المشكلات وحلولها.

## ما تحتاجه قبل أن تبدأ Requirements

- سيرفر مثبت عليه Docker وCompose، وإذا لم تثبته بعد فابدأ بدليل [تثبيت Docker على Ubuntu](https://arabroot.io/articles/%D8%AA%D8%AB%D8%A8%D9%8A%D8%AA-docker-%D8%B9%D9%84%D9%89-ubuntu/).
- الموارد Resources: دون أي زيارات يستهلك الـ Container الخاص بـ WordPress قرابة 160 ميجابايت من الذاكرة RAM، وتستهلك MariaDB قرابة 145 ميجابايت، أما الموقع الفعلي فخطط له بذاكرة من 1 إلى 2 GB، وبمساحة تتسع للوسائط Media والنسخ الاحتياطية، والسبب أن الوسائط هي التي تكبر مع الوقت وليس قاعدة البيانات.
- نطاق Domain مثل `blog.example.com`، وReverse Proxy يعمل بـ HTTPS مثل [Nginx Proxy Manager](https://arabroot.io/articles/nginx-proxy-manager-%D9%86%D8%B7%D8%A7%D9%82%D8%A7%D8%AA-%D9%88%D8%B4%D9%87%D8%A7%D8%AF%D8%A7%D8%AA-tls/) ومتصل بشبكة Docker باسم `proxy`.
- لا تحتاج إلى فتح أي منفذ Port على السيرفر سوى 80 و443 للـ Reverse Proxy.

## تثبيت WordPress بـ Docker Compose

### مجلد التثبيت والأسرار Secrets

نبدأ بإنشاء مجلد للخدمة يحفظ فيه ملف Compose وملف الأسرار وملف إعدادات PHP:

```bash
sudo mkdir -p /opt/wordpress
sudo chown $USER: /opt/wordpress
cd /opt/wordpress
```

بعد ذلك نولد كلمتي مرور قاعدة البيانات Database عشوائياً في ملف `.env` لا يقرؤه أحد غيرك، والسبب أننا لا نريد أن نكتب كلمة المرور داخل ملف Compose نفسه فتظهر في كل نسخة منه:

```bash
cat > .env <<EOF
MARIADB_ROOT_PASSWORD=$(openssl rand -hex 32)
MARIADB_PASSWORD=$(openssl rand -hex 32)
EOF
chmod 600 .env
```

الآن سوف ننشئ [ملف إعدادات PHP](https://www.php.net/manual/en/ini.core.php?ref=arabroot.io) الخاص بالرفع، وهو `/opt/wordpress/uploads.ini`، والسبب أن القيم الافتراضية في الـ Image تسمح بـ 2 ميجابايت فقط للملف الواحد، فتظهر للمحرر رسالة «الملف المراد رفعه تجاوز الحد الأقصى» مع أول صورة كبيرة يرفعها:

```ini
upload_max_filesize = 256M
post_max_size = 260M
memory_limit = 512M
max_execution_time = 300
max_input_time = 300
```

في الإعداد أعلاه لاحظ أن `post_max_size` أكبر قليلاً من `upload_max_filesize`، والسبب أن الأول حد للطلب كله، والطلب يحمل مع الملف حقول النموذج، فإذا تساوى الحدان رفض PHP الملف الذي يقترب من الحد دون أي رسالة واضحة، وأن `memory_limit` أكبر من الاثنين كما يوصي التوثيق. وتجد الفرق بين هذه الحدود وكيف تعرف أي طبقة رفضت الملف في دليل [حدود حجم رفع الملفات](https://arabroot.io/articles/%D8%AD%D8%AF%D9%88%D8%AF-%D8%AD%D8%AC%D9%85-%D8%B1%D9%81%D8%B9-%D8%A7%D9%84%D9%85%D9%84%D9%81%D8%A7%D8%AA/). وهذه الطريقة أنظف من تعديل `.htaccess` داخل مجلد الموقع، فالملف يبقى خارج البيانات، ويبقى في Git مع بقية الإعداد، ولا يمحوه أي تحديث.

### ملف Compose وشرح ما فيه

الملف `/opt/wordpress/compose.yaml` سوف يكون كما يلي:

```yaml
services:
  db:
    image: mariadb:11.8.9
    container_name: wordpress-db
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${MARIADB_PASSWORD}
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      MARIADB_AUTO_UPGRADE: "1"
    volumes:
      - db_data:/var/lib/mysql
    networks:
      - internal
    healthcheck:
      test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:7.1.2-php8.4-apache
    container_name: wordpress
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${MARIADB_PASSWORD}
      WORDPRESS_CONFIG_EXTRA: |
        define('WP_HOME', 'https://blog.example.com');
        define('WP_SITEURL', 'https://blog.example.com');
        define('FORCE_SSL_ADMIN', true);
        define('FS_METHOD', 'direct');
        define('WP_MEMORY_LIMIT', '256M');
        define('DISALLOW_FILE_EDIT', true);
    volumes:
      - wp_data:/var/www/html
      - ./uploads.ini:/usr/local/etc/php/conf.d/uploads.ini:ro
    networks:
      - internal
      - proxy
    healthcheck:
      test: ["CMD-SHELL", "curl -fsS -o /dev/null http://localhost/wp-includes/images/blank.gif || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3

  wpcli:
    image: wordpress:cli-2.12.0-php8.4
    profiles: ["cli"]
    user: "33:33"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_NAME: wordpress
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${MARIADB_PASSWORD}
      HOME: /tmp
    volumes:
      - wp_data:/var/www/html
    networks:
      - internal
    depends_on:
      - db

volumes:
  db_data:
    name: wordpress_db
  wp_data:
    name: wordpress_html

networks:
  internal: {}
  proxy:
    external: true
```

في الإعداد أعلاه لاحظ التالي:

- **MariaDB 11.8** [إصدار دعم طويل LTS](https://mariadb.org/about/?ref=arabroot.io#maintenance-policy) تصله الإصلاحات حتى يونيو 2028، وقد صدر بعده الإصدار 12.3 LTS في مايو 2026، ونبقى هنا على 11.8 لأنه مستقر منذ أكثر من سنة وأمامه قرابة سنتين من الدعم، والانتقال بين الفرعين لاحقاً خطوة مستقلة تأخذ لها نسخة احتياطية. والمتغير [MARIADB\_AUTO\_UPGRADE](https://hub.docker.com/%5F/mariadb?ref=arabroot.io) يرقي جداول النظام System Tables تلقائياً عندما تغير الوسم إلى إصدار أحدث.
- **فحص صحة القاعدة Health Check** يستخدم السكربت المدمج في الـ Image، والشرط `condition: service_healthy` يؤخر تشغيل WordPress حتى تبدأ القاعدة بقبول الاتصالات، وبالتالي لا تظهر لك رسالة «Error establishing a database connection» عند الإقلاع Startup. وفحص WordPress نفسه يطلب ملفاً ثابتاً صغيراً بـ curl الموجود في الـ Image، فيعرف أن Apache يرد دون أن يحمل صفحة كاملة كل 30 ثانية.
- **لا توجد منافذ منشورة** على السيرفر، فالـ Container الخاص بـ WordPress على شبكة `proxy` ويصل إليه الـ Reverse Proxy بالاسم `wordpress`، والقاعدة على شبكة `internal` وحدها، فلا يصل إليها أحد من خارج الخدمة.
- **WORDPRESS\_CONFIG\_EXTRA** [تضيف الـ Image محتواه](https://github.com/docker-library/wordpress/blob/master/wp-config-docker.php?ref=arabroot.io) إلى ملف [wp-config.php](https://developer.wordpress.org/advanced-administration/wordpress/wp-config/?ref=arabroot.io)، والثابتان `WP_HOME` و`WP_SITEURL` يثبتان الرابط العام للموقع حتى لا يسجل WordPress في القاعدة عنواناً خاطئاً، والقيمة `direct` في `FS_METHOD` تمنعه من طلب بيانات FTP عند تثبيت الإضافات وتحديثها، أما `DISALLOW_FILE_EDIT` فيعطل محرر ملفات PHP في لوحة التحكم Dashboard، والسبب أن هذا المحرر هو أول ما يستغله من يستولي على حساب مدير Administrator ليزرع كوده في الموقع.
- **wpcli** خدمة مقيدة بـ [profiles](https://docs.docker.com/compose/how-tos/profiles/?ref=arabroot.io)، فلا تعمل مع `up` وإنما تستدعيها عند الحاجة، ولاحظ أن الـ Image الخاصة بها مبنية على Alpine والمستخدم `www-data` فيها رقمه 82، بينما مالك ملفات الموقع في Image الـ Apache هو المستخدم 33، لذلك نشغلها بالمستخدم 33 حتى لا تفسد الصلاحيات Permissions، ولأن هذا المستخدم ليس له مجلد خاص فيها نضع `HOME: /tmp`، وإلا ظهر لك مع أوامر التحميل التحذير `Failed to create directory '/.wp-cli/cache/'`.

💡

إذا أردت أن تجرب محلياً قبل ضبط النطاق، فاحذف أسطر `WP_HOME` و`WP_SITEURL` و`FORCE_SSL_ADMIN`، وأضف إلى خدمة `wordpress` السطر `ports: ["127.0.0.1:8080:80"]`، ثم افتح نفق SSH Tunnel بالأمر `ssh -L 8080:127.0.0.1:8080 deploy@203.0.113.10` وافتح `http://localhost:8080`. ولكن الأفضل أن تكمل معالج التثبيت Installation Wizard على النطاق النهائي إن أمكن، والسبب أن WordPress يحفظ العنوان الذي ثبت عليه في إعداداته، فيبقى `localhost` في القاعدة حتى تستبدله.

بعد ذلك أنشئ شبكة `proxy` إذا لم تكن موجودة، وشغل الخدمات:

```bash
docker network create proxy
docker compose up -d
docker compose ps
```

والمخرج سوف يكون كما يلي:

```bash
NAME           IMAGE                           STATUS
wordpress      wordpress:7.1.2-php8.4-apache   Up 50 seconds (healthy)
wordpress-db   mariadb:11.8.9                  Up About a minute (healthy)
```

## ربط النطاق وتشغيل HTTPS خلف الـ Reverse Proxy

في [Nginx Proxy Manager](https://nginxproxymanager.com/?ref=arabroot.io) أنشئ مضيفاً من نوع Proxy Host للنطاق `blog.example.com` يوجه الطلبات إلى `http://wordpress:80`، واطلب له شهادة Let's Encrypt Certificate، وفعل Block Common Exploits وForce SSL وHTTP/2.

والآن لاحظ ما يحدث للطلب: الـ Reverse Proxy ينهي اتصال TLS، ثم يمرر الطلب إلى WordPress عبر HTTP عادي، وبالتالي يرى WordPress طلباً غير مشفر، ولو صدق ذلك لحول الزائر إلى HTTPS، فيعيده الـ Reverse Proxy إليه عبر HTTP مرة أخرى، وهكذا تبدأ حلقة التحويل. والحل أن يعرف WordPress أن الزائر يستخدم HTTPS من [الترويسة Header](https://developer.wordpress.org/advanced-administration/security/https/?ref=arabroot.io) `X-Forwarded-Proto: https` التي يضيفها الـ Reverse Proxy، و[ملف wp-config.php في الـ Image الرسمية](https://github.com/docker-library/wordpress/blob/master/wp-config-docker.php?ref=arabroot.io) يعالج هذه الحالة مسبقاً، فيضبط `$_SERVER['HTTPS'] = 'on'` متى وجد الترويسة. ويمكنك أن تتحقق من ذلك بعد إكمال معالج التثبيت في القسم التالي، بأن تطلب صفحة الدخول من داخل الـ Container مرة دون الترويسة ومرة معها:

```bash
docker compose exec wordpress curl -sI -H 'Host: blog.example.com' http://localhost/wp-login.php | grep -iE '^(HTTP|Location)'
docker compose exec wordpress curl -sI -H 'Host: blog.example.com' -H 'X-Forwarded-Proto: https' http://localhost/wp-login.php | grep -iE '^(HTTP|Location)'
```

والمخرج سوف يكون كما يلي:

```bash
HTTP/1.1 302 Found
Location: https://blog.example.com/wp-login.php
HTTP/1.1 200 OK
```

دون الترويسة يحول WordPress الطلب إلى `https://` بسبب `FORCE_SSL_ADMIN`، وهذا بالضبط ما يحدث في كل دورة من حلقة التحويل، ومع الترويسة يعرض الصفحة مباشرة بالرمز `200`، وهذا ما يحدث للزائر الحقيقي خلف الـ Reverse Proxy.

⚠️

هذا الإعداد يصدق الترويسة أياً كان مصدرها، ولا يكون آمناً إلا لأن الـ Container الخاص بـ WordPress غير منشور والـ Reverse Proxy هو الطريق الوحيد إليه، لذلك لا تنشر المنفذ 80 الخاص بالـ Container على عنوان عام.

أما عنوان الزائر الحقيقي Client IP فيصل في `X-Forwarded-For` و`X-Real-IP`، ولكن WordPress لا يقرؤه تلقائياً، لذلك سوف تجد التعليقات وسجلات الدخول تحمل عنوان الـ Reverse Proxy، والحل أن تحدد الترويسة الموثوقة في إعدادات إضافة الأمان أو التخزين المؤقت Cache التي تستخدمها، وتجد شرح هذه الترويسات ومتى تثق بها في دليل [تطبيقك خلف الـ Reverse Proxy](https://arabroot.io/articles/%D8%AA%D8%B7%D8%A8%D9%8A%D9%82%D9%83-%D8%AE%D9%84%D9%81-reverse-proxy/).

## إكمال معالج التثبيت

افتح `https://blog.example.com`، وسوف تجد أن الشاشة الأولى لاختيار لغة الموقع، فإذا اخترت العربية فسوف يحمل WordPress ملفات الترجمة، ويعمل الموقع ولوحة التحكم من اليمين إلى اليسار RTL:

![شاشة اختيار لغة التثبيت في WordPress مع تحديد العربية](https://arabroot.io/content/images/2026/09/wordpress-01-language.webp)

اختيار لغة الموقع

بعد ذلك يطلب منك معلومات الموقع وحساب المدير، ولا تستخدم اسم المستخدم Username `admin`، والسبب أنه أول اسم تجربه الهجمات الآلية Bots على أي موقع WordPress، واختر كلمة مرور طويلة يولدها مدير كلمات المرور Password Manager، وبريداً حقيقياً تصلك عليه رسائل استعادة كلمة المرور:

![نموذج معلومات الموقع وحساب المدير في معالج تثبيت WordPress بالعربية](https://arabroot.io/content/images/2026/09/wordpress-02-install-form.webp)

اسم الموقع وحساب المدير (أخفينا كلمة المرور)

اضغط «تنصيب ووردبريس» ثم سجل الدخول، وسوف تظهر لك لوحة التحكم:

![لوحة تحكم WordPress 7.1.2 بالعربية](https://arabroot.io/content/images/2026/09/wordpress-03-dashboard.webp)

لوحة التحكم بعد التثبيت

## إعدادات ما بعد التثبيت

### الروابط الدائمة Permalinks

تأتي الروابط افتراضياً بصيغة `/?p=123`، وهي صيغة لا تقول للزائر ولا لمحركات البحث شيئاً عن المقالة، ولتغييرها افتح الإعدادات ثم [روابط دائمة](https://wordpress.org/documentation/article/customize-permalinks/?ref=arabroot.io)، واختر «عنوان المقالة» (`/%postname%/`) واحفظ. و[الـ Image الرسمية بنسخة Apache](https://hub.docker.com/%5F/wordpress?ref=arabroot.io) تأتي مع `mod_rewrite` مفعلاً، وتكتب قواعد إعادة الكتابة Rewrite Rules القياسية في `.htaccess` عند التشغيل الأول، وحفظ الصفحة يحدث هذه القواعد، لذلك تعمل الروابط الجديدة مباشرة:

![صفحة إعدادات الرابط الدائم في WordPress مع تحديد عنوان المقالة](https://arabroot.io/content/images/2026/09/wordpress-04-permalinks.webp)

بنية الروابط: عنوان المقالة

### التحقق من حدود الرفع

افتح وسائط ثم إضافة ملف وسائط، وسوف ترى الحد الفعلي للرفع، ويجب أن يطابق ما كتبته في `uploads.ini`:

![صفحة رفع الوسائط في WordPress تعرض الحد الأقصى 256 ميجابايت](https://arabroot.io/content/images/2026/09/wordpress-05-upload-limit.webp)

الحد الأقصى للرفع: 256 ميجابايت كما ضبطناه

ولاحظ أن هذا الحد هو حد PHP وحده، فإذا رفعته فوق 100 ميجابايت تقريباً فراجع أيضاً حد الـ Reverse Proxy وحد أي Proxy آخر أمامه مثل Cloudflare، والسبب أن الملف يمر بكل هذه الطبقات، والحد الأصغر بينها هو الذي يقرر.

### صفحة صحة الموقع Site Health

صفحة أدوات ثم [صحة الموقع](https://wordpress.org/documentation/article/site-health-screen/?ref=arabroot.io) تفحص الإعداد كله، وإذا فتحتها في تجربة محلية قبل ضبط النطاق فسوف ترى ملاحظتين حرجتين عن REST API وloopback، والسبب أن الـ Container يحاول الاتصال بعنوان الموقع المحلي من داخله فلا يجده، وسوف ترى أيضاً ملاحظة «موقعك لا يستخدم HTTPS». أما على السيرفر الحقيقي حيث يشير النطاق إلى الـ Reverse Proxy فتختفي هذه الملاحظات، وإذا بقيت فراجع قسم المشكلات في آخر الدليل.

![صفحة صحة الموقع في WordPress مع مشكلتين حرجتين وخمسة تحسينات موصى بها](https://arabroot.io/content/images/2026/09/wordpress-06-site-health.webp)

صحة الموقع قبل ضبط النطاق، وينبغي مراجعتها بعد النقل إليه

بعد ذلك [أغلق بقية الثغرات](https://developer.wordpress.org/advanced-administration/security/hardening/?ref=arabroot.io)، فاحذف الإضافات والقوالب Themes التي لا تستخدمها (`hello` و`akismet` إن لم تحتج إليهما)، والسبب أن الإضافة المعطلة تبقى ملفاتها على السيرفر وثغراتها معها، وفعل التحديث التلقائي Auto Updates للإضافات الموثوقة، وأضف إضافة للتحقق بخطوتين Two-Factor Authentication لحسابات المدراء، وإضافة SMTP ترسل البريد عبر خدمة بريد حقيقية، لأن الـ Container لا يرسل البريد بنفسه.

![واجهة موقع WordPress بالقالب الافتراضي تعرض مقالتين](https://arabroot.io/content/images/2026/09/wordpress-07-front-page.webp)

الموقع بالقالب الافتراضي، ومعه مقالة مضافة عبر wp-cli

## الإدارة من الطرفية Terminal بـ wp-cli

الأداة [wp-cli](https://wp-cli.org/?ref=arabroot.io) تدير بها موقع WordPress دون متصفح، فتحدث النواة والإضافات وتدير المستخدمين وتبحث وتستبدل Search and Replace في القاعدة، ونشغلها عبر خدمة `wpcli` مؤقتة تشارك مجلد الموقع وشبكة القاعدة، ويحذف Compose الـ Container بعد انتهاء الأمر بسبب الخيار `--rm`:

```bash
cd /opt/wordpress
docker compose run --rm wpcli wp core version
docker compose run --rm wpcli wp --info
```

والمخرج سوف يكون كما يلي:

```bash
7.1.2
PHP version:	8.4.26
WP-CLI version:	2.12.0
```

وفيما يلي [أوامر من الاستخدام اليومي](https://developer.wordpress.org/cli/commands/?ref=arabroot.io):

```bash
docker compose run --rm wpcli wp plugin list
docker compose run --rm wpcli wp plugin delete hello
docker compose run --rm wpcli wp core check-update
docker compose run --rm wpcli wp user list --fields=user_login,roles
docker compose run --rm wpcli wp post create --post_title='مرحبا من wp-cli' --post_status=publish
```

**انتبه عند إعادة توليد قواعد الروابط من wp-cli:** الـ Container الخاص بـ wp-cli لا يعرف أن Apache فيه `mod_rewrite`، لذلك إذا نفذت `wp rewrite flush --hard` فسوف يظهر لك التحذير `Regenerating a .htaccess file requires special configuration` ولن يكتب شيئاً في `.htaccess`. وفي الحالة العادية لا يضرك هذا لأن القواعد مكتوبة من التشغيل الأول، ولكن إذا حذف الملف أو أفرغته إضافة أو تعديل يدوي فسوف تعيد كل الروابط `404` عدا الصفحة الرئيسية، والحل أن تخبر wp-cli بوجود `mod_rewrite` في [ملف wp-cli.yml](https://make.wordpress.org/cli/handbook/references/config/?ref=arabroot.io) داخل مجلد الموقع، ثم تعيد توليد القواعد:

```bash
docker compose run --rm wpcli sh -c 'printf "apache_modules:\n  - mod_rewrite\n" > /var/www/html/wp-cli.yml && wp rewrite flush --hard'
```

وبعدها تفتح المقالات بالرمز `200` من جديد.

## كيف تتأكد أن كل شيء يعمل

- الأمر `docker compose ps` يعرض كلا الـ Containers بحالة `healthy`.
- الأمر `curl -sI https://blog.example.com` يعيد الرمز `200`، والأمر `curl -sI http://blog.example.com` يعيد الرمز `301` مع تحويل إلى `https://`.
- مصدر الصفحة خال من روابط `http://blog.example.com`، أي لا يوجد محتوى مختلط Mixed Content يجعل المتصفح يحذر الزائر.
- رابط مقالة بصيغة `/اسم-المقالة/` يفتح، وصفحة الرفع تعرض الحد الذي ضبطته.
- الأمر `docker compose run --rm wpcli wp core check-update` يعيد الرسالة `Success: WordPress is at the latest version.`
- تثبيت إضافة من لوحة التحكم يكتمل دون أن يطلب منك بيانات FTP.

## النسخ الاحتياطي Backup والاستعادة Restore

موقع WordPress يتكون من جزأين: **قاعدة البيانات** وفيها المقالات والإعدادات والمستخدمون، و**wp-content** وفيه الوسائط والإضافات والقوالب. أما ملفات النواة Core فلا تحتاج إلى نسخها، والسبب أن الـ Image تعيدها وتولد الملف `wp-config.php` من متغيرات البيئة Environment Variables، ولكن لا تنس أن تنسخ أيضاً `compose.yaml` و`.env` و`uploads.ini`، فبدون `.env` لن تعرف كلمة مرور القاعدة التي استعدتها.

ولتفريغ Dump القاعدة دون إيقاف الموقع نستخدم الأمر التالي، والخيار [\--single-transaction](https://mariadb.com/docs/server/clients-and-utilities/backup-restore-and-import-clients/mariadb-dump?ref=arabroot.io) يعطيك Snapshot متسقة لجداول InnoDB حتى لو كتب أحد المحررين مقالة أثناء التفريغ:

```bash
cd /opt/wordpress
mkdir -p backups
docker compose exec -T db sh -c 'mariadb-dump --single-transaction -uwordpress -p"$MARIADB_PASSWORD" wordpress' | gzip > backups/db-$(date +%F).sql.gz
```

ولأرشفة `wp-content` نستخدم Container مؤقتاً يقرأ الـ Volume للقراءة فقط:

```bash
docker run --rm -v wordpress_html:/data:ro -v "$PWD/backups":/backup alpine:3.24 tar czf /backup/wp-content-$(date +%F).tar.gz -C /data wp-content
```

جدول الأمرين ليعملا يومياً عبر cron، وانقل مجلد `backups` إلى خارج السيرفر، واحذف منه النسخ القديمة. وللاستعادة على سيرفر جديد انسخ ملفات الإعداد، وشغل `docker compose up -d` لتحصل على قاعدة فارغة، ثم نفذ الأوامر التالية:

```bash
gunzip -c backups/db-2026-09-26.sql.gz | docker compose exec -T db sh -c 'mariadb -uwordpress -p"$MARIADB_PASSWORD" wordpress'
docker run --rm -v wordpress_html:/data -v "$PWD/backups":/backup alpine:3.24 sh -c 'rm -rf /data/wp-content && tar xzf /backup/wp-content-2026-09-26.tar.gz -C /data && chown -R 33:33 /data/wp-content'
docker compose restart wordpress
```

بعد الاستعادة تأكد أن عدد السجلات Rows في `wp_posts` يطابق عددها في القاعدة الأصلية، وأن صور المقالات تظهر، وجرب الاستعادة على سيرفر اختبار كل بضعة أشهر، والسبب أن النسخة الاحتياطية التي لم تجرب استعادتها لا يمكنك الاعتماد عليها يوم تحتاجها. وإذا تغير النطاق فاستبدله في القاعدة بالأمر [docker compose run --rm wpcli wp search-replace 'https://old.example.com' 'https://blog.example.com' --skip-columns=guid](https://developer.wordpress.org/cli/commands/search-replace/?ref=arabroot.io)، بعد أن تجربه مع `--dry-run`.

## تحديث WordPress إلى إصدار أحدث Upgrade

هناك مشكلة رئيسية يقع فيها كثير ممن يشغلون WordPress في Docker، وهي أن نواة WordPress داخل Docker Image لا تتحدث بالطريقة التي يتوقعها، والسبب أن [الـ Image تنسخ ملفات النواة](https://github.com/docker-library/wordpress/blob/master/docker-entrypoint.sh?ref=arabroot.io) إلى الـ Volume `wordpress_html` **عند التشغيل الأول فقط** إذا لم تجدها فيه، وبعد ذلك فتغيير وسم الـ Image يحدث إصدار PHP وApache، ولكنه **لا يغير** ملفات WordPress الموجودة في الـ Volume.

لذلك [تحدث النواة](https://developer.wordpress.org/advanced-administration/upgrade/upgrading/?ref=arabroot.io) من داخل WordPress نفسه، من لوحة التحكم أو بـ wp-cli أو بالتحديث التلقائي، ويجدر الانتباه هنا إلى أن [التحديث التلقائي للنواة](https://wordpress.org/documentation/article/configuring-automatic-background-updates/?ref=arabroot.io) يكون مفعلاً في التثبيت الجديد للإصدارات الأمنية والرئيسية معاً، وهو يعمل في إعدادنا لأن الملفات مملوكة للمستخدم `www-data` و`FS_METHOD` مضبوط على `direct`، فلا تعطله ظناً منك أن وسم الـ Image هو الذي يحدد الإصدار.

📌

في 20 يناير 2017 أبلغت شركة Sucuri فريق WordPress بثغرة في REST API في الإصدارين 4.7 و4.7.1 تسمح لأي زائر دون تسجيل دخول بتعديل محتوى المقالات، فأصدر الفريق الإصدار 4.7.2 في 26 يناير وأخر الإعلان عن الثغرة أسبوعاً كاملاً، وكما [شرح الفريق في إعلانه](https://make.wordpress.org/core/2017/02/01/disclosure-of-additional-security-fix-in-wordpress-4-7-2/?ref=arabroot.io) فقد وصل الإصلاح عبر نظام التحديث التلقائي إلى ملايين المواقع خلال ساعات دون أن يفعل أصحابها شيئاً. وبعد الإعلان بأيام رصدت Sucuri [أربع حملات تشويه Defacement](https://blog.sucuri.net/2017/02/wordpress-rest-api-vulnerability-abused-in-defacement-campaigns.html?ref=arabroot.io) تمسح الإنترنت بحثاً عن المواقع التي لم تتحدث، وكان في نتائج Google لحملة واحدة منها أكثر من 66 ألف صفحة مخترقة خلال 48 ساعة، والدرس أن المواقع التي وصلها التحديث التلقائي نجت، وأن الموقع الذي عطل صاحبه التحديث بقي مكشوفاً لهذه الحملات.

وإذا أردت أن تحدث يدوياً فالخطوات كما يلي:

1. خذ نسخة احتياطية من القاعدة وwp-content كما في القسم السابق.
2. حدث النواة والإضافات والقوالب والترجمات بالأوامر التالية:

```bash
docker compose run --rm wpcli wp core update
docker compose run --rm wpcli wp core update-db
docker compose run --rm wpcli wp plugin update --all
docker compose run --rm wpcli wp theme update --all
docker compose run --rm wpcli wp language core update
```

1. ولتحديث PHP أو Apache أو MariaDB غير الوسوم في `compose.yaml`، مثل وسم WordPress الجديد بأرقامه الكاملة وإصدار MariaDB 11.8 التالي، ثم نفذ:

```bash
docker compose pull
docker compose up -d
docker compose logs --tail 30 db
```

ابق على فرع MariaDB 11.8 LTS للتحديثات الإصلاحية Patch Releases، ولا تنتقل بين الإصدارات الرئيسية Major Versions، مثل الانتقال من 11.8 إلى 12.3، دون نسخة احتياطية وتجربة على سيرفر اختبار، وقبل أن ترفع إصدار PHP تأكد أن إضافاتك تدعمه، والسبب أن الإضافة القديمة التي لا تدعم الإصدار الجديد قد توقف الموقع كله بخطأ واحد.

## مشكلات شائعة وحلولها

### رسالة Error establishing a database connection

السبب غالباً واحد من اثنين: الأول أن القاعدة لم تبدأ بعد، والثاني أن كلمة المرور في `.env` تغيرت بعد إنشاء القاعدة، والـ Image لا تقرأ متغيرات `MARIADB_*` إلا عند التهيئة الأولى Initialization، فتغييرها لاحقاً لا يغير كلمة المرور داخل القاعدة. راجع السجلات Logs بالأمر `docker compose logs db`، وإذا كنت غيرت كلمة المرور فأعدها إلى قيمتها السابقة، أو غيرها داخل القاعدة بالأمر `ALTER USER`.

### الملف تجاوز upload\_max\_filesize

تأكد أولاً أن `uploads.ini` مركب داخل الـ Container بالأمر `docker compose exec wordpress php -i | grep upload_max_filesize`، فإذا كانت القيمة صحيحة وفشل الرفع بالرمز `413` فالحد مفروض في الـ Reverse Proxy أو في Cloudflare وليس في PHP.

### WordPress يطلب بيانات FTP عند التحديث

السبب أن `FS_METHOD` غير مضبوط، أو أن الملفات ليست مملوكة للمستخدم `www-data`، وهذا يحدث عادة بعد نسخ الملفات بمستخدم آخر، ولإصلاح الملكية Ownership نفذ الأمر `docker compose exec wordpress chown -R www-data:www-data /var/www/html/wp-content`.

### ERR\_TOO\_MANY\_REDIRECTS بعد تفعيل HTTPS

WordPress لا يعرف أن الطلب وصل عبر HTTPS فيحوله، ثم يعيده الـ Reverse Proxy إليه، وتتكرر الحلقة، لذلك تأكد أن الـ Reverse Proxy يرسل `X-Forwarded-Proto`، وNginx Proxy Manager يفعل ذلك افتراضياً. وإذا كان Cloudflare أمام الـ Reverse Proxy فاجعل وضع SSL فيه [Full (strict)](https://developers.cloudflare.com/ssl/origin-configuration/ssl-modes/full-strict/?ref=arabroot.io)، وإذا تعذر عليك دخول لوحة التحكم تماماً فعدل `WP_HOME` و`WP_SITEURL` في `compose.yaml` ثم أعد إنشاء الـ Container.

### الروابط كلها تعيد 404 عدا الصفحة الرئيسية

قواعد `.htaccess` محذوفة أو فارغة، وهذا يحدث إذا حذف الملف أو أفرغته إضافة أو تعديل يدوي، والحل أن تحفظ صفحة الروابط الدائمة من لوحة التحكم مرة واحدة، أو تستخدم الأمر [wp rewrite flush --hard](https://developer.wordpress.org/cli/commands/rewrite/flush/?ref=arabroot.io) مع `wp-cli.yml` كما في قسم wp-cli.

### صفحة صحة الموقع تعرض خطأ في loopback أو REST API

الـ Container لا يصل إلى رابط الموقع العام من داخله، فتفشل المهام المجدولة Cron Jobs وبعض وظائف المحرر، وتتحقق من ذلك بالأمر `docker compose exec wordpress curl -sI https://blog.example.com`، فإذا فشل فالسبب غالباً DNS داخلي أو Router لا يدعم NAT loopback، والحل أن تضيف إلى خدمة `wordpress` السطر `extra_hosts: ["blog.example.com:host-gateway"]` حتى يصل الـ Container إلى الـ Reverse Proxy على السيرفر نفسه.

### المقالات المجدولة Scheduled Posts لا تنشر في موعدها

WP-Cron لا يعمل إلا حين يزور أحد الموقع، لذلك في المواقع قليلة الزيارات أضف `define('DISABLE_WP_CRON', true);` إلى `WORDPRESS_CONFIG_EXTRA`، ثم [شغل المهام من cron السيرفر](https://developer.wordpress.org/plugins/cron/hooking-wp-cron-into-the-system-task-scheduler/?ref=arabroot.io) كل خمس دقائق بالأمر `cd /opt/wordpress && docker compose run --rm wpcli wp cron event run --due-now`.

## الخلاصة

وصلنا لنهاية الموضوع، وأهم ما فيه:

- الاستضافة المشتركة والتثبيت بنقرة واحدة لا يعطيانك تحكماً في PHP والحدود والنسخ الاحتياطي، والاستضافة المدارة مريحة ولكنها مكلفة ومقيدة، وملف Compose خاص بك على سيرفرك يجمع التحكم الكامل مع إعداد تعرف كل سطر فيه.
- ولد كلمات المرور في `.env`، وحدد وسوم الـ Images بأرقامها الكاملة، واجعل `post_max_size` أكبر قليلاً من `upload_max_filesize`.
- لا تنشر منفذ WordPress، فالـ Image تصدق الترويسة `X-Forwarded-Proto`، وهذا آمن فقط حين يكون الـ Reverse Proxy هو الطريق الوحيد إلى الـ Container.
- شغل wp-cli بالمستخدم 33 ومعه `HOME: /tmp`، وأخبره بوجود `mod_rewrite` في `wp-cli.yml` قبل أن تعيد توليد قواعد `.htaccess`.
- انسخ القاعدة وwp-content ومعهما `.env`، وجرب الاستعادة على سيرفر اختبار، واترك التحديث التلقائي للنواة يعمل، لأن تغيير وسم الـ Image لا يحدث ملفات WordPress.

## سجل التحديثات Changelog

- سبتمبر 2026: كتابة الدليل واختباره على [WordPress 7.1.2](https://wordpress.org/news/category/releases/?ref=arabroot.io) مع MariaDB 11.8.9.
- أكتوبر 2026: مراجعة الدليل وإعادة اختباره، ورفع `post_max_size` فوق حد الملف، وإضافة `HOME: /tmp` إلى خدمة wp-cli، وتصحيح طريقة التحقق من HTTPS خلف الـ Reverse Proxy وشرح سلوك wp-cli مع `.htaccess`، وتوضيح موقع MariaDB 11.8 بعد صدور 12.3 LTS والتحديث التلقائي للنواة.