Preview Environments: جرّب كل Pull Request قبل الدمج
هتكسب من المقال ده طريقة عملية تشغّل نسخة مؤقتة لكل Pull Request بدل ما تدمج وبعدين تكتشف إن الصفحة مكسورة. مستوى القارئ: متوسط، ومناسب لأي فريق عنده CI بسيط وبيستخدم Docker.
المشكلة باختصار
الطريقة الشائعة إنك تراجع الكود، تشوف سكرينشوت من المطور، وبعدين تعتمد على staging واحد مشترك. الطريقة دي بتفشل لما 3 فروع يتراجعوا في نفس الوقت. آخر واحد يعمل deploy على staging يكسر تجربة الباقي.
الـ Preview Environment هو بيئة مؤقتة مربوطة بـ Pull Request واحد. كأنك بتفتح شقة صغيرة لكل ضيف بدل ما كل الضيوف يدخلوا نفس الأوضة. المثال بسيط: PR رقم 42 يأخذ URL خاص، قاعدة بيانات مؤقتة، ونسخة app بنفس إعدادات الإنتاج تقريبًا. بعد إغلاق الـ PR، البيئة تتمسح.
اللي بيحصل فعلاً في فرق صغيرة
افترض إن عندك SaaS صغير بـ 8 مطورين و20 Pull Request في الأسبوع. لو كل مراجعة UI بتاخد 15 دقيقة setup محلي، فأنت بتصرف حوالي 5 ساعات أسبوعيًا على تشغيل الفروع يدويًا. Preview Environments تقلل الرقم ده غالبًا إلى دقيقة أو دقيقتين لفتح الرابط وتجربة السيناريو.
الافتراض هنا إن التطبيق يشتغل بـ Docker Compose، وعدد الخدمات أقل من 6، والـ traffic أثناء المراجعة ضعيف. لو عندك Kubernetes بالفعل، نفس الفكرة تنفع، لكن الأدوات هتختلف.
أفضل طريقة تبدأ بها بدون منصة كبيرة
ابدأ بـ GitHub Actions وDocker Compose على سيرفر مراجعة واحد. مش لازم تبدأ بـ ArgoCD أو Terraform لو الفريق لسه بيتعلم. المكسب: إعداد أسرع وتكلفة أقل. الخسارة: isolation أضعف من Kubernetes، ومحتاج تنظف البيئات القديمة بصرامة.
ركز في الفكرة: اسم المشروع يتبني من رقم الـ PR. كل PR يعمل stack منفصل، مثل app-pr-42. Docker Compose يحط containers وnetwork وvolumes تحت نفس الاسم، فالمراجعة تبقى معزولة بالقدر الكافي لفريق صغير.
مثال تنفيذي بـ GitHub Actions
ضع الملف ده في .github/workflows/preview.yml. المثال مبني على إن عندك سيرفر Linux عليه Docker، وGitHub Actions يقدر يدخل عليه بـ SSH key محفوظ في secrets.
name: preview-environment
on:
pull_request:
types: [opened, synchronize, reopened, closed]
jobs:
deploy-preview:
runs-on: ubuntu-latest
if: github.event.action != 'closed'
steps:
- uses: actions/checkout@v4
- name: Copy files to preview server
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ secrets.PREVIEW_HOST }}
username: ${{ secrets.PREVIEW_USER }}
key: ${{ secrets.PREVIEW_SSH_KEY }}
source: "docker-compose.yml,src,package.json,package-lock.json"
target: "/srv/previews/pr-${{ github.event.pull_request.number }}"
- name: Start preview stack
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PREVIEW_HOST }}
username: ${{ secrets.PREVIEW_USER }}
key: ${{ secrets.PREVIEW_SSH_KEY }}
script: |
cd /srv/previews/pr-${{ github.event.pull_request.number }}
export COMPOSE_PROJECT_NAME=app_pr_${{ github.event.pull_request.number }}
export APP_PORT=$((5000 + ${{ github.event.pull_request.number }}))
docker compose up -d --build
echo "Preview URL: http://${{ secrets.PREVIEW_HOST }}:${APP_PORT}"
cleanup-preview:
runs-on: ubuntu-latest
if: github.event.action == 'closed'
steps:
- name: Remove preview stack
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.PREVIEW_HOST }}
username: ${{ secrets.PREVIEW_USER }}
key: ${{ secrets.PREVIEW_SSH_KEY }}
script: |
cd /srv/previews/pr-${{ github.event.pull_request.number }} || exit 0
export COMPOSE_PROJECT_NAME=app_pr_${{ github.event.pull_request.number }}
docker compose down -v --remove-orphans
rm -rf /srv/previews/pr-${{ github.event.pull_request.number }}وفي docker-compose.yml خلي البورت متغيرًا بدل رقم ثابت:
services:
web:
build: .
ports:
- "${APP_PORT:-5000}:3000"
environment:
NODE_ENV: preview
DATABASE_URL: postgres://app:app@db:5432/app
depends_on:
- db
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: app
POSTGRES_DB: app
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:الأرقام والـ trade-off هنا
لو الـ build بياخد 4 دقائق، أول deploy لكل PR هياخد تقريبًا 4 إلى 6 دقائق حسب حجم image والـ dependencies. بعد كده أي تعديل صغير ممكن ينزل في 60 إلى 120 ثانية لو Docker cache شغال. ده مش سحر، ده نتيجة reuse للـ layers كما تشرح Docker في توثيق build cache.
التكلفة مش صفر. لو كل Preview Stack بتستهلك 300MB RAM و0.2 CPU أثناء الخمول، وتشغل 10 PRs في نفس الوقت، فأنت محتاج حوالي 3GB RAM متاحة فوق خدمات النظام. بتكسب مراجعة أسرع وعزل أفضل، وبتخسر إدارة موارد وتنظيف دوري.
خلي عندك cron job يمسح أي بيئة أقدم من 3 أيام لو الـ PR اتقفل بدون cleanup. ده يحمي السيرفر من امتلاء disk بسبب volumes قديمة.
# شغله يوميًا على سيرفر المراجعات
find /srv/previews -mindepth 1 -maxdepth 1 -type d -mtime +3 -print -exec rm -rf {} \;
docker system prune -af --filter "until=72h"متى لا تستخدم هذه الطريقة
لا تستخدم Docker Compose preview لو المنتج عنده بيانات حساسة حقيقية، أو لو كل PR يحتاج 15 خدمة وKafka وElasticsearch كاملين. هنا الأفضل تستخدم Kubernetes namespaces، أو منصة جاهزة مثل Heroku Review Apps، أو Vercel Preview Deployments لو التطبيق frontend فقط.
كمان لا تستخدمها لو الفريق مش قادر يحمي secrets. أي secret يوصل لبيئة PR لازم يكون محدود الصلاحية. بدل ما تستخدم production database، استخدم database مؤقتة ببيانات وهمية.
مصادر اعتمد عليها
- GitHub Docs: pull_request workflow event
- Docker Docs: build cache
- Docker Docs: Compose project name
- Heroku Dev Center: Review Apps
- Vercel Docs: Preview Deployments
الخطوة التالية
افتح آخر Pull Request عندك، واكتب قائمة الخدمات التي يحتاجها للتجربة. لو القائمة أقل من 6 خدمات، طبّق GitHub Actions workflow فوق على branch تجريبي، وقِس زمن أول deploy وزمن deploy بعد تعديل صغير.