مشروع التخرج (الجزء 2): أتمتة النشر عبر GitHub Actions وتغليف المشروع بـ Docker
مقدمة: من بناء السيرفر إلى أتمتة التسليم والنشر
في مشروع التخرج (الجزء 1): استخدام Terraform لبناء سيرفرات المشروع الآلية قمنا ببناء طبقة البنية التحتية برمجياً، أي أن الخادم لم يعد يُنشأ يدوياً، بل أصبح مورداً قابلاً للتكرار والتحكم. الخطوة المنطقية التالية هي نقل المشروع من مرحلة “السيرفر جاهز” إلى مرحلة “الكود يُختبر ويُغلف ويُنشر تلقائياً” عبر GitHub Actions و Docker.
هذه المرحلة تمثل قلب منهج ما هو الـ CI/CD؟ ولماذا نؤتمت عمليات اختبار ونشر الأكواد؟، لأنها تربط بين المستودع البرمجي، الاختبارات، بناء الصورة، ثم تسليم نسخة موحدة قابلة للتشغيل على أي بيئة. وبهذا نتجاوز جذرياً المشكلة الكلاسيكية التي تناولناها في مشكلة “الكود يعمل على جهازي فقط” وكيف يحلها Docker نهائياً؟.
لماذا نغلف المشروع داخل حاوية قبل النشر؟
عند نشر تطبيق مباشرة على الخادم باستخدام تثبيتات محلية، فإنك تربط سلوك التطبيق بإصدار النظام، الحزم، المتغيرات البيئية، وحتى ترتيب الخطوات اليدوية. أما عند استخدام Containerization فأنت تحوّل التطبيق إلى وحدة تشغيل ثابتة تتضمن الاعتماديات اللازمة وتقلل الانحراف بين بيئة التطوير والإنتاج.
هذه الفكرة تتكامل مع ما شرحناه سابقاً في كتابة أول Dockerfile: تحويل سكربت Python إلى صورة (Image) معزولة وتحويل سيرفر Node.js متكامل إلى حاوية Docker قابلة للنقل. في مشروع التخرج، الهدف ليس فقط تشغيل الحاوية، بل إدخالها إلى خط نشر آلي يضمن أن كل Push على الفرع الرئيسي يمر عبر نفس المعايير الهندسية.
الهيكلية المقترحة لخط النشر
خط الأنابيب المثالي لهذا المشروع يمكن تقسيمه إلى أربع طبقات واضحة، بحيث تكون كل طبقة مسؤولة عن جزء مستقل ويمكن التحقق منه:
- سحب الكود من مستودع
GitHub. - تنفيذ الاختبارات البرمجية وفحص سلامة البناء.
- بناء صورة
Dockerورفعها إلى السجل. - الاتصال بالسيرفر وتشغيل النسخة الجديدة بأقل توقف ممكن.
هذا التصميم ينسجم مع مبدأ الفصل بين Build وDeploy، ويمنع وصول كود غير صالح إلى الإنتاج، تماماً كما شرحنا في إنشاء خط أنابيب (Pipeline) يرفض الأكواد التي تفشل في الاختبارات.
إعداد ملف Dockerfile للمشروع
لنفترض أن المشروع عبارة عن تطبيق Node.js. يجب أن يكون ملف Dockerfile واضحاً، صغير الحجم، ويستفيد من مبدأ الطبقات لتقليل وقت البناء.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
استخدام صورة أساس خفيفة مثل node:20-alpine يقلل الحجم النهائي ويُسرّع النقل، وهي نفس الفلسفة التي تناولناها في تقليل حجم الحاويات: بناء صور دوكر خفيفة جداً وسريعة (Alpine Linux). كما أن فصل نسخ ملفات الاعتماديات عن بقية المشروع يساعد على إعادة استخدام طبقة التثبيت عند عدم تغير الحزم.
بناء ملف Workflow في GitHub Actions
بعد تجهيز التغليف، ننتقل إلى ملف الأتمتة داخل المسار .github/workflows/deploy.yml. هذا الملف يمثل المحرك التنفيذي الذي يربط الحدث البرمجي بالإجراءات الفعلية، وهو امتداد عملي لما شرحناه في مقدمة في GitHub Actions: كتابة أول مسار عمل (Workflow).
name: Build and Deploy
on:
push:
branches:
- main
jobs:
build-test-push:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push image
run: |
docker build -t myuser/myapp:latest .
docker push myuser/myapp:latest
deploy:
needs: build-test-push
runs-on: ubuntu-latest
steps:
- name: Deploy on server via SSH
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SERVER_USER }}
key: ${{ secrets.SERVER_SSH_KEY }}
script: |
docker pull myuser/myapp:latest
docker stop myapp || true
docker rm myapp || true
docker run -d --name myapp -p 3000:3000 myuser/myapp:latest
هذا المسار يطبق تسلسلاً منضبطاً: اختبار أولاً، ثم بناء الصورة، ثم النشر. لو فشلت الاختبارات سيتوقف العمل فوراً، وهي قاعدة أساسية في الأنظمة الاحترافية لضمان عدم ترحيل كود غير مستقر.
ما الذي يحدث داخل هذا المسار فعلياً؟
- مرحلة
checkoutتسحب الكود من المستودع. - مرحلة تثبيت
Node.jsتوحد بيئة البناء داخل المشغل السحابي. - مرحلة الاختبار تمنع ترقية نسخة معطوبة.
- مرحلة رفع الصورة تجعل الخادم يسحب نسخة جاهزة بدلاً من البناء داخله.
- مرحلة النشر عبر
SSHتستبدل الحاوية الجارية بنسخة أحدث.
إدارة الأسرار والصلاحيات بأمان
أي مشروع يستخدم CI/CD بدون سياسة صارمة للأسرار ينقل الخطر من الخادم إلى المستودع. لذلك يجب تخزين بيانات الدخول، مفاتيح API، وبيانات النشر داخل إدارة الأسرار (GitHub Secrets) لحماية كلمات المرور ومفاتيح الـ API في الأتمتة وليس داخل الملفات البرمجية.
لا تستخدم حساب
rootللنشر المباشر، ولا تحفظ مفاتيح الخادم داخل المستودع حتى لو كان خاصاً. أنشئ مستخدماً محدود الصلاحيات، وفعّل مفاتيحSSHكما شرحنا في إعداد الاتصال الآمن (SSH Keys) بدون كلمات مرور بين جهازك والخوادم، ثم قيد أوامر النشر بوضوح لتقليل أثر أي اختراق.
تشغيل المشروع على الخادم بطريقة أكثر استقراراً
الأمر docker run مفيد كبداية، لكن المشاريع الحقيقية غالباً تحتاج إدارة منافذ، متغيرات بيئية، وسياسات إعادة تشغيل. هنا يصبح استخدام ما هو Docker Compose؟ ولماذا نحتاجه لتشغيل المشاريع المعقدة؟ أكثر منطقية.
version: '3.8'
services:
app:
image: myuser/myapp:latest
container_name: myapp
ports:
- "3000:3000"
restart: always
environment:
NODE_ENV: production
عندها يمكن أن تصبح أوامر النشر على الخادم أبسط وأكثر قابلية للصيانة، لأنك تستبدل التعليمات الطويلة بملف تعريف ثابت ومفهوم. كما يمكن مستقبلاً توصيل قاعدة بيانات أو خدمة عكسية بسهولة، بالاستفادة من مفاهيم ربط حاوية خادم (Backend) بحاوية قاعدة بيانات (MySQL/PostgreSQL) آلياً وإدارة الشبكات (Docker Networks): كيف تتحدث الحاويات مع بعضها بأمان؟.
تقليل التوقف وتحسين موثوقية النشر
أحد الأخطاء الشائعة هو إيقاف الحاوية القديمة قبل التأكد من جاهزية الجديدة. صحيح أن المثال السابق مبسط ومناسب تعليمياً، لكن في البيئات الإنتاجية يفضل اعتماد نمط أقرب إلى rolling update أو استخدام وكيل عكسي مثل Nginx لتبديل المسار بعد نجاح الفحص الصحي.
إذا كان مشروع التخرج سيُعرض أمام لجنة أو عملاء، فلا تجعل النشر يعتمد على حذف الحاوية القديمة مباشرة. أضف فحص صحة
health check، وسجلًا واضحاً للإصدارات، وآليةrollbackعند الفشل. هذه التفاصيل الصغيرة هي التي ترفع جودة المشروع من تجربة أكاديمية إلى بنية تشغيل شبه احترافية.
كيف يبدو المسار الهندسي الكامل للمشروع الآن؟
بعد إنهاء هذا الجزء، تصبح بنية المشروع أكثر نضجاً من الناحية التشغيلية:
- إنشاء الخادم والبنية الأساسية تم عبر
Terraform. - تغليف التطبيق تم عبر
Docker. - الأتمتة والتنفيذ السحابي تمّا عبر
GitHub Actions. - النشر إلى الخادم تم عبر
SSHوصور جاهزة قابلة للتكرار.
بهذا تكون قد جمعت بين مفاهيم ما هو DevOps؟ ولماذا تدفع الشركات ثروات لمهندسي الأتمتة السحابية؟ بشكل تطبيقي: بنية تحتية ككود، تغليف، اختبارات، ونشر مستمر. وفي المراحل القادمة يمكن توسيع هذا المشروع بإضافة المراقبة، التنبيهات، أو حتى نقله إلى ما هو Kubernetes (K8s)؟ ومتى ننتقل من Docker Compose إليه؟ إذا زادت متطلبات التوسع والاعتمادية.
خاتمة
أتمتة النشر ليست رفاهية، بل وسيلة هندسية لتقليل الخطأ البشري، تسريع التسليم، وبناء ثقة أعلى في كل تحديث. عندما يصبح كل commit قادراً على المرور عبر الاختبار، البناء، التغليف، ثم النشر تلقائياً، فأنت لا تسرّع العمل فقط، بل تبني نظاماً يمكن صيانته وتطويره بوضوح. وهذا بالضبط ما يجعل مشروع التخرج أقرب إلى بيئة الشركات الحقيقية منه إلى مجرد تطبيق يعمل محلياً.
2 comments