الخطوات
-
1
اكتب Dockerfile متعدد المراحل
المرحلة الأولى (
builder) تثبّت تبعيات Composer. المرحلة الثانية (runtime) تنسخ فقط مجلد vendor — لا Composer نفسه. الصورة النهائية تبقى صغيرة وخالية من أدوات البناء.dockerfile# ── Stage 1: Composer dependencies ────────────────────────────── FROM composer:2 AS builder WORKDIR /app COPY composer.json composer.lock ./ RUN composer install \ --no-dev \ --no-scripts \ --optimize-autoloader \ --no-interaction \ --prefer-dist COPY . . RUN composer run-script post-autoload-dump # ── Stage 2: PHP-FPM runtime ────────────────────────────────── FROM php:8.3-fpm-alpine AS runtime # تثبيت تبعيات النظام وامتدادات PHP RUN apk add --no-cache \ libpng-dev libjpeg-turbo-dev libwebp-dev \ libzip-dev oniguruma-dev icu-dev && \ docker-php-ext-install \ pdo_mysql mbstring zip bcmath intl gd opcache WORKDIR /var/www # نسخ التطبيق وvendor من builder COPY --from=builder /app . # مستخدم بدون صلاحيات root RUN addgroup -g 1000 laravel && \ adduser -u 1000 -G laravel -s /bin/sh -D laravel && \ chown -R laravel:laravel /var/www USER laravel EXPOSE 9000 CMD ["php-fpm"] -
2
أنشئ إعداد Nginx لـ PHP-FPM
يُحيل Nginx طلبات PHP إلى
php:9000— وهذا اسم الخدمة في Compose، لا منفذ محلي. الملفات الثابتة تُقدَّم مباشرة دون المرور بـ PHP.nginx# docker/nginx/default.conf server { listen 80; root /var/www/public; index index.php; # تقديم الملفات الثابتة مباشرة location / { try_files $uri $uri/ /index.php?$query_string; } # إحالة طلبات PHP إلى php-fpm location ~ \.php$ { fastcgi_pass php:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; fastcgi_param DOCUMENT_ROOT $realpath_root; } location ~ /\.ht { deny all; } } -
3
اكتب ملف docker-compose.yml
أربع خدمات. كلها على شبكة داخلية واحدة
laravel. المجلدات تُبقي بيانات قاعدة البيانات ومجلد storage محفوظة عبر إعادة تشغيل الحاويات.yamlservices: php: build: context: . target: runtime volumes: - ./storage:/var/www/storage env_file: .env networks: - laravel nginx: image: nginx:1.27-alpine ports: - "80:80" volumes: - ./public:/var/www/public:ro - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro depends_on: - php networks: - laravel mysql: image: mysql:8.4 environment: MYSQL_ROOT_PASSWORD: "${DB_PASSWORD}" MYSQL_DATABASE: "${DB_DATABASE}" volumes: - mysql_data:/var/lib/mysql networks: - laravel redis: image: redis:7.4-alpine networks: - laravel volumes: mysql_data: networks: laravel: -
4
أنشئ ملف .dockerignore
بدون هذا الملف، يتضمن سياق بناء Docker مجلدَي
vendor/وnode_modules/— مئات الميجابايت تُرسل إلى الـ daemon عند كل بناء، حتى لو لم يتغير شيء.bashvendor/ node_modules/ .git/ .env .env.* storage/logs/* storage/framework/cache/* storage/framework/sessions/* storage/framework/views/* tests/ docker/ *.md -
5
شغّل الـ migrations عبر سكريبت entrypoint
يجب تشغيل الـ migrations بعد أن تصبح
mysqlجاهزة فعلاً، لا مجرد مشغّلة. استخدم entrypoint صغيراً ينتظر قاعدة البيانات قبل تشغيل artisan.bash#!/bin/sh # docker/php/entrypoint.sh set -e echo "Waiting for MySQL..." until php -r "new PDO('mysql:host=${DB_HOST};dbname=${DB_DATABASE}', '${DB_USERNAME}', '${DB_PASSWORD}');" 2>/dev/null; do sleep 1 done php artisan migrate --force php artisan optimize php artisan storage:link --force exec "$@" -
6
اربط الـ entrypoint بالـ Dockerfile
أضف سطرين إلى مرحلة
runtime، قبيلCMDالنهائي مباشرة.ENTRYPOINTيشغّل سكريبت الـ migration؛CMDيمررphp-fpmكوسيط إلى"$@".dockerfile# أضف إلى مرحلة runtime في Dockerfile (كـ root، قبل USER laravel) COPY docker/php/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh USER laravel ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] CMD ["php-fpm"] -
7
عيّن متغيرات البيئة للحاويات
ملف
.envالعادي يعمل — فقط غيّر أسماء المضيفين لتطابق أسماء خدمات Compose. MySQL هيmysql، وRedis هوredis، لا127.0.0.1.bashAPP_ENV=production APP_KEY=base64:your_key_here APP_DEBUG=false APP_URL=https://example.com DB_CONNECTION=mysql DB_HOST=mysql DB_PORT=3306 DB_DATABASE=laravel DB_USERNAME=root DB_PASSWORD=secret REDIS_HOST=redis REDIS_PORT=6379 CACHE_STORE=redis SESSION_DRIVER=redis QUEUE_CONNECTION=redis -
8
ابنِ البيئة وشغّلها
ابنِ صورة PHP وشغّل جميع الحاويات الأربع. راقب السجلات للتأكد من أن PHP-FPM وNginx يعملان بنظافة.
bash# البناء والتشغيل (في الخلفية) docker compose up -d --build # مراقبة جميع السجلات docker compose logs -f # تأكد من تشغيل جميع الحاويات docker compose ps # تشغيل أوامر artisan داخل الحاوية docker compose exec php php artisan tinker # إيقاف كل شيء (يحتفظ بالمجلدات) docker compose down # إيقاف ومسح قاعدة البيانات (خطر: يحذف البيانات) docker compose down -v -
9
قائمة تدقيق تصليب الإنتاج
قبل النشر للإنتاج، طبّق هذه التغييرات:
- مستخدم بدون root — منجز بالفعل في Dockerfile أعلاه (المستخدم
laravel) - بدون تبعيات تطوير — منجز بـ
composer install --no-dev - مجلد storage للقراءة فقط — شارك
storage/وحدها، لا التطبيق بأكمله - فحوصات الصحة — أضف كتل
healthcheckفي Compose حتى تنتظر الخدمات التابعة بشكل صحيح - إدارة الأسرار — استخدم Docker secrets أو مدير أسرار (Vault أو AWS SSM) بدلاً من ملف
.envعادي في الإنتاج - فحص الصور — شغّل
docker scout cvesأو Trivy على صورتك النهائية قبل الرفع
yaml# أضف health check لخدمة mysql في docker-compose.yml healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s timeout: 5s retries: 5 # فحص الثغرات الأمنية docker scout cves local://your-image:latest - مستخدم بدون root — منجز بالفعل في Dockerfile أعلاه (المستخدم
نصائح ومحاذير
- استخدم <code>docker compose watch</code> (Compose v2.22+) للتطوير المحلي — يُزامن تغييرات الملفات داخل الحاوية دون إعادة البناء.
- ثبّت إصدارات الصور بدقة في الإنتاج (<code>php:8.3.9-fpm-alpine</code> لا <code>php:8.3-fpm-alpine</code>) لمنع التحديثات المفاجئة من كسر بنائك.
- إعداد PHP <code>opcache.validate_timestamps=0</code> ضروري للإنتاج — يمنع OPcache من إجراء stat() لكل ملف عند كل طلب. عيّنه في <code>docker/php/opcache.ini</code>.
- لا تدمج الأسرار في الصورة أبداً. وسائط بناء Docker تصبح جزءاً من تاريخ طبقة الصورة — أي شخص لديه صلاحية الوصول للصورة يستطيع قراءتها بـ <code>docker history</code>.
- شغّل <code>docker compose exec php php artisan queue:work</code> أو أضف خدمة <code>worker</code> مخصصة في Compose للمهام الخلفية.
خاتمة
لديك الآن بيئة Laravel من أربع حاويات تعكس الإنتاج، تعمل بأمر واحد docker compose up، وتنتج صورة خفيفة عبر البناء متعدد المراحل. يمكن تسليم نفس docker-compose.yml لأي مطور في الفريق وسيحصل على بيئة متطابقة في دقائق — لا مزيد من نقاشات "يعمل على جهازي".