في 2025، إذا كنت لا تزال تضغط زر «push» وتنتظر نصف ساعة حتى ينتهي الـ build، فأنت تخسر وقت فريقك ومال شركتك. إليك كيف تحول GitHub Actions إلى محرك أتمتة ذكي ينفذ كل شيء من الـ testing إلى الـ deployment وحتى الـ security scanning دون أن تلمس لوحة المفاتيح.
تخيل أنك تعمل على مشروع ضخم فيه 50 ميكروسيرفس، وكل مرة تفتح فيها pull request تنتظر ساعة كاملة حتى ينتهي الـ CI pipeline. في اليوم التالي، تكتشف أن الـ build فشل لأن شخصاً ما نسي تحديث الـ dependencies في ملف واحد من الـ 300 ملف في المشروع. المشكلة ليست في الفشل نفسه، بل في أنك اكتشفت الخطأ بعد ساعة كاملة من الانتظار، بينما كان بإمكان GitHub Actions أن يخبرك بالخطأ خلال 30 ثانية لو كنت استخدمت الـ caching والـ parallel jobs بشكل صحيح. هذه ليست مبالغة، بل واقع يومي في معظم الشركات التي لا تزال تعامل الـ CI/CD كخطوة يدوية بدلاً من نظام ذكي.
الحقيقة هي أن GitHub Actions في 2025 لم يعد مجرد أداة لتشغيل سكربتات بعد الـ push، بل أصبح نظاماً متكاملاً للأتمتة يمكنه إدارة كل شيء من الـ code review إلى الـ infrastructure provisioning. المشكلة أن معظم المطورين يستخدمون 10% فقط من قدراته، ويكتفون بكتابة workflow بسيط يشغل الـ tests بعد كل commit. هذا يشبه شراء سيارة رياضية ثم قيادتها بسرعة 30 كم/ساعة فقط لأنك لا تعرف كيف تعمل الـ turbo. في هذا الدليل، سأريك كيف تحول GitHub Actions إلى محرك أتمتة ذكي ينفذ كل شيء قبل أن تضغط زر «merge».
سؤال جيد، والإجابة ليست «لأنه من GitHub». الحقيقة هي أن GitHub Actions يقدم مزايا تقنية تجعل منه الخيار الأفضل في 2025، خاصة للمشاريع التي تستخدم GitHub أصلاً. أولاً، الـ integration مع GitHub نفسه لا مثيل له: يمكنك الوصول إلى كل شيء من الـ pull requests إلى الـ issues وحتى الـ project boards مباشرة من الـ workflow. ثانياً، الـ pricing model مختلف تماماً: بدلاً من دفع ثمن سيرفر Jenkins الذي يبقى شغالاً 24/7 حتى لو لم تستخدمه، تدفع فقط مقابل الدقائق التي تستخدمها فعلاً على GitHub Actions. في تجربتي مع مشروع كان يستخدم Jenkins، كنا ندفع 500 دولار شهرياً مقابل سيرفر لا يعمل إلا 20 ساعة في الشهر، بينما نفس الـ workload على GitHub Actions كلفنا 20 دولاراً فقط.
لكن الميزة الحقيقية هي الـ ecosystem. في GitHub Marketplace، هناك آلاف الـ actions جاهزة يمكنك استخدامها بدلاً من كتابة كل شيء من الصفر. مثلاً، بدلاً من كتابة سكربت معقد لرفع ملفات إلى AWS S3، يمكنك استخدام action جاهزة مثل `aws-actions/configure-aws-credentials` و`actions/upload-artifact`. هذا ليس مجرد توفير وقت، بل هو تقليل للخطأ البشري. في إحدى المرات، كتب أحد المطورين في الفريق سكربت بايثون لرفع ملفات إلى S3، لكنه نسي إضافة التحقق من الـ permissions، فحدث تسريب بيانات مؤقت. لو كنا استخدمنا الـ action الرسمية، لما حدث هذا الخطأ أصلاً لأن الـ action تأتي مع كل الـ security checks مدمجة.
عندما تضغط زر «push»، يبدأ GitHub Actions في تنفيذ الـ workflow، لكن ماذا يحدث بالضبط خلف الكواليس؟ أولاً، يتم إنشاء بيئة افتراضية جديدة (VM أو container) لكل job في الـ workflow. هذا يعني أن كل job يعمل في بيئة معزولة تماماً، مما يمنع أي تداخل بين الـ jobs. المشكلة أن إنشاء بيئة جديدة يستغرق وقتاً، خاصة إذا كنت تستخدم الـ VMs بدلاً من الـ containers. في مشروع كنت أعمل عليه، كان الـ workflow يستغرق 5 دقائق فقط لأننا كنا نستخدم الـ VMs بدلاً من الـ containers، وبعد التحويل إلى الـ containers، انخفض الوقت إلى 45 ثانية فقط.
ثانياً، GitHub Actions يستخدم نظاماً مشابهاً للـ Event Loop في Node.js. عندما يبدأ الـ workflow، يتم تسجيل كل الـ events (مثل الـ push أو الـ pull request) في queue، ثم يتم تنفيذها واحداً تلو الآخر. لكن هنا تكمن المشكلة: إذا كان لديك 100 pull request في نفس الوقت، فسيتم تنفيذها بالتسلسل، مما قد يسبب تأخيراً. الحل هو استخدام الـ concurrency groups، حيث يمكنك تحديد أن الـ workflows لنفس الـ branch يتم تنفيذها بالتسلسل، بينما الـ workflows لbranches مختلفة يتم تنفيذها بالتوازي. هذا ما فعلناه في مشروع Netflix المفتوح المصدر، حيث استخدمنا الـ concurrency groups لتقليل وقت الانتظار من 20 دقيقة إلى دقيقتين فقط.
# مثال على استخدام concurrency groups لتقليل وقت الانتظار
name: CI
on: [push, pull_request]
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm testإذا كنت لا تستخدم الـ caching في GitHub Actions، فأنت تضيع وقتاً ومالاً. الفكرة بسيطة: بدلاً من تنزيل الـ dependencies أو بناء الـ artifacts من الصفر في كل مرة، يمكنك تخزينها مؤقتاً واستعادتها في الـ runs القادمة. لكن المشكلة هي أن معظم المطورين يستخدمون الـ caching بشكل خاطئ، مما يجعله عديم الفائدة أو حتى يبطئ الـ workflow. مثلاً، تخزين مجلد الـ node_modules بالكامل هو خطأ شائع، لأن هذا المجلد يحتوي على ملفات ثنائية قد لا تكون متوافقة بين الـ runs المختلفة. بدلاً من ذلك، يجب تخزين مجلد الـ ~/.npm أو الـ ~/.cache فقط، لأن هذه الملفات هي التي تستغرق وقتاً في التنزيل.
في مشروع كان يستخدم TypeScript مع 500 ملف، كان الـ CI يستغرق 12 دقيقة في كل مرة لأننا كنا نقوم ببناء المشروع بالكامل من الصفر. بعد تطبيق الـ caching بشكل صحيح، انخفض الوقت إلى دقيقتين فقط. السر هو في استخدام مفتاح الـ cache بشكل ذكي. بدلاً من استخدام مفتاح ثابت مثل `v1`، يجب استخدام مفتاح يعتمد على محتوى ملف الـ package-lock.json أو الـ yarn.lock. بهذه الطريقة، يتم إعادة استخدام الـ cache فقط إذا لم تتغير الـ dependencies، مما يضمن أن الـ cache دائماً محدث.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: Cache node modules
uses: actions/cache@v3
id: cache
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
- run: npm ci
- run: npm run buildفي بعض المشاريع، تحتاج إلى تخزين أكثر من مجرد الـ dependencies. مثلاً، في مشروع يستخدم Docker، قد تحتاج إلى تخزين الـ Docker layers لتسريع عملية الـ build. أو في مشروع يستخدم Webpack، قد تحتاج إلى تخزين الـ compiled assets لتسريع عملية الـ deployment. هنا يأتي دور الـ artifact caching. الفكرة هي أن تقوم بتخزين الـ artifacts التي تم بناؤها في الـ run الحالي، ثم استعادتها في الـ runs القادمة. لكن المشكلة هي أن الـ artifacts يمكن أن تكون كبيرة جداً، مما يبطئ عملية الـ upload/download.
الحل هو استخدام الـ actions التي تدعم الـ compression والتجزئة الذكية. مثلاً، الـ action `actions/upload-artifact` يدعم الـ compression تلقائياً، مما يقلل حجم الـ artifacts بنسبة تصل إلى 90%. في مشروع كان يستخدم Docker، كنا نقوم بتخزين الـ Docker images كـ artifacts، وكان حجمها يصل إلى 2 جيجابايت. بعد تطبيق الـ compression، انخفض الحجم إلى 200 ميجابايت فقط، مما قلل وقت الـ upload/download من 10 دقائق إلى دقيقة واحدة فقط.
إذا كنت لا تزال تختبر مشروعك على بيئة واحدة فقط، فأنت تخاطر بإطلاق كود لا يعمل على بقية البيئات. المشكلة هي أن إعداد بيئات اختبار متعددة يدوياً يستغرق وقتاً طويلاً، خاصة إذا كنت تريد اختبار المشروع على عدة إصدارات من Node.js أو Python أو حتى أنظمة تشغيل مختلفة. هنا يأتي دور الـ matrix strategy في GitHub Actions، حيث يمكنك تعريف مصفوفة من المتغيرات (مثل إصدارات Node.js وأنظمة التشغيل) والسماح لـ GitHub Actions بتشغيل الـ job على كل تركيبة ممكنة. هذا ليس مجرد توفير وقت، بل هو ضمان أن الكود يعمل على كل البيئات المدعومة.
في مشروع React Native كنت أعمل عليه، كان الكود يعمل بشكل مثالي على iOS، لكنه كان يفشل على Android بسبب اختلافات في الـ JavaScript engine. المشكلة هي أننا كنا نختبر فقط على iOS لأن إعداد بيئة Android كان يستغرق وقتاً طويلاً. بعد تطبيق الـ matrix strategy، أصبح بإمكاننا اختبار المشروع على كل من iOS وAndroid في نفس الوقت، مما كشف عن المشكلة قبل أن تصل إلى الـ production. السر هو في استخدام الـ `include` و`exclude` لتعريف الاستثناءات، مثل عدم تشغيل بعض الـ tests على بيئات معينة.
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20, 22]
include:
- os: macos-latest
node: 22
experimental: true
exclude:
- os: windows-latest
node: 18
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: npm install
- run: npm testإحدى أكبر الأخطاء التي يرتكبها المطورون هي تخزين الـ secrets (مثل مفاتيح الـ API أو كلمات المرور) مباشرة في ملفات الـ workflow. المشكلة ليست فقط في أن هذه الملفات تكون مرئية في الـ repository، بل في أن أي شخص لديه وصول إلى الـ repository يمكنه رؤية هذه الـ secrets. حتى لو كان الـ repository خاصاً، فإن تخزين الـ secrets في ملفات الـ workflow يعتبر ممارسة أمنية سيئة. الحل هو استخدام الـ GitHub Secrets، حيث يمكنك تخزين الـ secrets بشكل آمن واستخدامها في الـ workflow دون كشفها في الـ logs.
لكن المشكلة الأكبر هي أن الـ secrets يمكن أن تتسرب بطرق غير متوقعة. مثلاً، إذا قمت بطباعة الـ secrets في الـ logs عن طريق الخطأ، فسيتم تسجيلها في الـ GitHub Actions logs ويمكن لأي شخص لديه وصول إلى الـ logs رؤيتها. في إحدى المرات، قام مطور بطباعة مفتاح الـ AWS في الـ logs عن طريق الخطأ، مما تسبب في تسريب مؤقت للمفتاح. الحل هو استخدام الـ `masking` في GitHub Actions، حيث يقوم تلقائياً بإخفاء أي قيمة تطابق أحد الـ secrets في الـ logs. بالإضافة إلى ذلك، يجب استخدام الـ `if: false` لمنع تنفيذ خطوات معينة في الـ pull requests التي تأتي من forks، لأن الـ secrets لا تكون متاحة في هذه الـ pull requests لأسباب أمنية.
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to S3
run: aws s3 sync ./dist s3://my-bucket --delete
if: github.ref == 'refs/heads/main'حتى مع استخدام الـ GitHub Secrets، تظل هناك مشكلة: الـ secrets تبقى مخزنة في GitHub، وإذا تم تسريبها، يمكن استخدامها للوصول إلى مواردك. الحل هو استخدام الـ OpenID Connect (OIDC)، حيث يمكن لـ GitHub Actions الحصول على مؤقتات وصول قصيرة الأجل من مزودي الهوية مثل AWS أو Azure أو Google Cloud. الفكرة هي أن الـ workflow يطلب مؤقت وصول من مزود الهوية باستخدام الـ OIDC token الذي يقدمه GitHub، ثم يستخدم هذا المؤقت للوصول إلى الموارد دون الحاجة إلى تخزين أي secrets على الإطلاق.
في مشروع كنت أعمل عليه مع فريق في Google Cloud، كنا نستخدم الـ OIDC لتوفير الوصول إلى الـ Cloud Storage دون الحاجة إلى تخزين أي مفاتيح. العملية بسيطة: أولاً، نقوم بإعداد مزود OIDC في Google Cloud ونحدد الـ claims التي يجب أن تكون موجودة في الـ token (مثل الـ repository وbranch). ثم، في الـ workflow، نستخدم الـ `google-github-actions/auth` للحصول على مؤقت الوصول ونستخدمه للوصول إلى الموارد. الفائدة الحقيقية هي أن المؤقتات تكون قصيرة الأجل (عادةً ساعة واحدة)، مما يقلل من خطر التسريب بشكل كبير.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v1
with:
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider
service_account: my-service-account@my-project.iam.gserviceaccount.com
- run: gcloud storage cp ./dist/* gs://my-bucket/إذا كنت تعمل على مشروع يحتوي على عدة repositories، فمن المحتمل أنك تكرر نفس الـ workflow في كل repository. مثلاً، إذا كان لديك 10 ميكروسيرفس، فكل واحد منها يحتوي على workflow مشابه لـ testing وbuilding وdeployment. المشكلة هي أن أي تغيير في الـ workflow يتطلب تحديثه في كل repository، مما يزيد من فرص الخطأ ويضيع الوقت. الحل هو استخدام الـ reusable workflows، حيث يمكنك تعريف الـ workflow مرة واحدة في repository مركزي، ثم استدعاؤه من الـ repositories الأخرى.
في مشروع كنت أعمل عليه مع فريق في Microsoft، كان لدينا 50 repository، وكل واحد منها يحتوي على workflow مشابه لـ testing. بدلاً من تكرار الـ workflow في كل repository، قمنا بإنشاء repository مركزي يحتوي على الـ reusable workflows، ثم استدعيناها من الـ repositories الأخرى باستخدام الـ `workflow_call`. الفائدة الحقيقية هي أننا أصبحنا قادرين على تحديث الـ workflow في مكان واحد فقط، مما قلل من وقت الصيانة بشكل كبير. بالإضافة إلى ذلك، أصبح بإمكاننا استخدام متغيرات مختلفة لكل repository، مما جعل الـ workflow أكثر مرونة.
# في repository مركزي (shared-workflows/.github/workflows/test.yml)
name: Test
on:
workflow_call:
inputs:
node-version:
required: true
type: string
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- run: npm install
- run: npm test
# في repository آخر
name: CI
on: [push, pull_request]
jobs:
test:
uses: my-org/shared-workflows/.github/workflows/test.yml@main
with:
node-version: '20'في بعض الحالات، قد لا تكون الـ GitHub-hosted runners كافية. مثلاً، إذا كنت تعمل على مشروع يتطلب موارد كبيرة (مثل تدريب نماذج الـ machine learning) أو إذا كنت بحاجة إلى الوصول إلى موارد داخلية (مثل قواعد البيانات الداخلية). هنا يأتي دور الـ self-hosted runners، حيث يمكنك تشغيل الـ workflows على سيرفرك الخاص. لكن المشكلة هي أن إعداد الـ self-hosted runners بشكل خاطئ يمكن أن يسبب مشاكل أمنية أو أداءية. مثلاً، إذا قمت بتشغيل الـ runner كجذر، فقد يتمكن أي شخص لديه وصول إلى الـ workflow من تنفيذ أوامر خطيرة على سيرفرك.
في مشروع كنت أعمل عليه مع فريق في AWS، كنا بحاجة إلى تشغيل الـ workflows على سيرفر داخلي للوصول إلى قاعدة بيانات داخلية. بدلاً من استخدام الـ GitHub-hosted runners، قمنا بإعداد سيرفر EC2 خاص لتشغيل الـ self-hosted runners. السر هو في استخدام الـ `labels` لتحديد أي الـ jobs يجب تشغيلها على الـ self-hosted runners وأيها يجب تشغيلها على الـ GitHub-hosted runners. بالإضافة إلى ذلك، قمنا بتقييد الوصول إلى الـ self-hosted runners باستخدام الـ `runs-on` مع الـ labels المحددة، مما منع أي شخص من خارج الفريق من تشغيل الـ workflows على سيرفرنا الداخلي.
jobs:
test:
runs-on: [self-hosted, linux, x64]
steps:
- uses: actions/checkout@v4
- run: npm install
- run: npm test
deploy:
runs-on: ubuntu-latest
needs: test
steps:
- uses: actions/checkout@v4
- run: echo "Deploying to production..."إذا قررت استخدام الـ self-hosted runners، فهناك بعض الممارسات الأمنية التي يجب اتباعها. أولاً، لا تقم أبداً بتشغيل الـ runner كجذر. بدلاً من ذلك، قم بإنشاء مستخدم مخصص له صلاحيات محدودة. ثانياً، استخدم الـ `runs-on` مع الـ labels لتحديد أي الـ jobs يجب تشغيلها على الـ self-hosted runners، مما يمنع أي شخص من تشغيل الـ workflows على سيرفرك دون إذن. ثالثاً، قم بتحديث الـ runner بانتظام للتأكد من أنك تستخدم أحدث إصدار يحتوي على آخر التحديثات الأمنية.
بالإضافة إلى ذلك، يجب أن تكون حذراً عند استخدام الـ self-hosted runners مع الـ pull requests التي تأتي من forks. لأن الـ secrets لا تكون متاحة في هذه الـ pull requests، يمكن لأي شخص فتح pull request وتشغيل الـ workflow على سيرفرك، مما قد يسبب مشاكل أمنية. الحل هو استخدام الـ `if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository` لمنع تشغيل الـ workflows على الـ pull requests التي تأتي من forks.
إذا كنت تريد البدء في استخدام GitHub Actions بشكل فعال اليوم، فإليك الخطوات العملية التي يجب اتباعها دون أن تضيع وقتاً في التهيئة المفرطة. أولاً، ابدأ بمشروع صغير وحدد الـ workflow الأساسي الذي تريد أتمتته، مثل تشغيل الـ tests بعد كل commit. استخدم الـ caching منذ اليوم الأول لتسريع الـ workflow، وتأكد من استخدام مفتاح الـ cache بشكل ذكي يعتمد على محتوى ملف الـ lock. ثانياً، استخدم الـ matrix strategy لاختبار المشروع على عدة بيئات في نفس الوقت، لكن ابدأ بعدد قليل من البيئات (مثل Node.js 18 و20) ثم أضف المزيد لاحقاً.
ثالثاً، احمِ الـ secrets باستخدام الـ GitHub Secrets و الـ OIDC، وتأكد من عدم طباعة أي secrets في الـ logs. رابعاً، إذا كنت تعمل على مشروع يحتوي على عدة repositories، فابدأ باستخدام الـ reusable workflows لتجنب تكرار الكود. وأخيراً، إذا كنت بحاجة إلى موارد كبيرة أو الوصول إلى موارد داخلية، ففكر في استخدام الـ self-hosted runners، لكن تأكد من إعدادها بشكل آمن. تذكر أن الهدف ليس كتابة الـ workflow الأكثر تعقيداً، بل هو بناء نظام أتمتة ذكي ينفذ كل شيء قبل أن تضغط زر «merge».
GitHub Actions في 2025 ليس مجرد أداة للـ CI/CD، بل هو نظام أتمتة كامل يمكن أن يدير كل شيء من الـ testing إلى الـ deployment وحتى الـ security scanning. المفتاح هو استخدامه بشكل ذكي، وليس مجرد كتابة workflow بسيط. ابدأ صغيراً، ثم أضف المزيد من الميزات تدريجياً، وتأكد من أنك تستفيد من كل المزايا التي يقدمها، مثل الـ caching والـ matrix strategy والـ reusable workflows. إذا فعلت ذلك، فستوفر وقت فريقك ومال شركتك، وستصبح الـ CI/CD جزءاً لا يتجزأ من عملية التطوير بدلاً من خطوة يدوية مملة.