نشر تطبيق Next.js باستخدام Docker و CI/CD في بيئة الإنتاج

AI Bot
بواسطة AI Bot ·

جاري تحميل مشغل تحويل النص إلى كلام الصوتي...

يرشدك هذا الدرس خطوة بخطوة لحوسلة تطبيق Next.js باستخدام Docker، وإعداد خط أنابيب CI/CD مع GitHub Actions، والنشر التلقائي في بيئة الإنتاج على خادم VPS Linux. في النهاية، كل عملية دفع (push) إلى الفرع الرئيسي ستُطلق عملية نشر تلقائية.

أهداف التعلم

بنهاية هذا الدرس، ستكون قادراً على:

  • كتابة Dockerfile مُحسّن متعدد المراحل لـ Next.js
  • تكوين Docker Compose للتطوير المحلي والإنتاج
  • إعداد خط أنابيب CI/CD كامل مع GitHub Actions
  • النشر التلقائي على خادم VPS عبر SSH
  • إدارة متغيرات البيئة بشكل آمن
  • تكوين خادم وكيل عكسي Nginx مع SSL

المتطلبات الأساسية

قبل البدء، تأكد من توفر:

  • Node.js 20+ مثبت محلياً
  • Docker Desktop مثبت وعامل (تثبيت Docker)
  • حساب GitHub مع مستودع يحتوي على تطبيق Next.js
  • خادم VPS Linux (يُوصى بـ Ubuntu 22.04+) مع وصول SSH
  • اسم نطاق يشير إلى خادم VPS الخاص بك (اختياري لكن مُوصى به)
  • معرفة أساسية بـ Next.js و سطر الأوامر

ما ستبنيه

سنُعدّ بنية نشر كاملة:

  1. Dockerfile متعدد المراحل — صورة Docker مُحسّنة (~150 ميجابايت بدلاً من ~1 جيجابايت)
  2. Docker Compose — تنسيق الخدمات (التطبيق + قاعدة البيانات + التخزين المؤقت)
  3. خط أنابيب CI/CD — اختبارات تلقائية وبناء ونشر عبر GitHub Actions
  4. وكيل عكسي — Nginx مع شهادة SSL من Let's Encrypt
  5. مراقبة — فحوصات صحة وسجلات منظمة

الخطوة 1: تحضير مشروع Next.js

ابدأ بالتحقق من أن مشروع Next.js يعمل بشكل صحيح محلياً.

# Verifier la version de Node.js
node --version  # v20.x.x requis
 
# Installer les dependances
npm install
 
# Tester le build de production
npm run build
 
# Verifier que le serveur demarre
npm start

إذا لم يكن لديك مشروع Next.js بعد، أنشئ واحداً بسرعة باستخدام npx create-next-app@latest mon-app --typescript. هذا الدرس يعمل مع Next.js 14 و 15.

إضافة سكربت فحص الصحة

أنشئ ملف app/api/health/route.ts حتى يتمكن Docker من التحقق من حالة تطبيقك:

// app/api/health/route.ts
import { NextResponse } from "next/server";
 
export async function GET() {
  return NextResponse.json({
    status: "healthy",
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
  });
}

سيُستخدم هذا الـ endpoint من قبل Docker وخط أنابيب CI/CD للتحقق من أن التطبيق يعمل.


الخطوة 2: كتابة Dockerfile متعدد المراحل

سر صورة Docker عالية الأداء لـ Next.js هو البناء متعدد المراحل. يسمح هذا بفصل أدوات البناء (الثقيلة) عن الصورة النهائية (الخفيفة).

أنشئ ملف Dockerfile في جذر مشروعك:

# =============================================================================
# Etape 1 : Image de base avec les dependances
# =============================================================================
FROM node:20-alpine AS base
 
# Installer libc6-compat pour les modules natifs
RUN apk add --no-cache libc6-compat
WORKDIR /app
 
# =============================================================================
# Etape 2 : Installer les dependances
# =============================================================================
FROM base AS deps
 
# Copier les fichiers de dependances
COPY package.json package-lock.json* ./
 
# Installer toutes les dependances (y compris devDependencies pour le build)
RUN npm ci
 
# =============================================================================
# Etape 3 : Build de l'application
# =============================================================================
FROM base AS builder
WORKDIR /app
 
# Copier les dependances installees
COPY --from=deps /app/node_modules ./node_modules
 
# Copier le code source
COPY . .
 
# Desactiver la telemetrie Next.js pendant le build
ENV NEXT_TELEMETRY_DISABLED=1
 
# Build de production
RUN npm run build
 
# =============================================================================
# Etape 4 : Image de production (legere)
# =============================================================================
FROM node:20-alpine AS runner
WORKDIR /app
 
# Mode production
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
 
# Creer un utilisateur non-root pour la securite
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
# Copier les fichiers publics
COPY --from=builder /app/public ./public
 
# Copier le build standalone de Next.js
# Le mode standalone copie uniquement les fichiers necessaires
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
 
# Utiliser l'utilisateur non-root
USER nextjs
 
# Exposer le port
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
 
# Healthcheck Docker
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost:3000/api/health || exit 1
 
# Demarrer l'application
CMD ["node", "server.js"]

تكوين وضع standalone في Next.js

لكي يعمل Dockerfile، يجب عليك تفعيل standalone output في تكوين Next.js:

// next.config.js (ou next.config.mjs)
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: "standalone",
};
 
module.exports = nextConfig;

وضع standalone ضروري لـ Docker. بدونه، ستحتاج الصورة النهائية لكامل مجلد node_modules، مما يزيد حجمها بشكل كبير. مع standalone، ينسخ Next.js فقط الملفات اللازمة للتشغيل.

إنشاء ملف .dockerignore

أنشئ ملف .dockerignore لتجنب نسخ ملفات غير ضرورية في الصورة:

# .dockerignore
node_modules
.next
.git
.gitignore
*.md
docker-compose*.yml
.env*.local
.vscode
.idea
coverage
.husky

اختبار بناء Docker محلياً

# Construire l'image
docker build -t mon-app-nextjs .
 
# Verifier la taille de l'image
docker images mon-app-nextjs
# REPOSITORY        TAG       SIZE
# mon-app-nextjs    latest    ~150MB
 
# Lancer le conteneur
docker run -p 3000:3000 mon-app-nextjs
 
# Tester dans un autre terminal
curl http://localhost:3000/api/health

إذا حصلت على {"status":"healthy"}، فإن صورة Docker تعمل بشكل صحيح. يجب أن يكون الحجم بين 130 و 200 ميجابايت حسب اعتمادياتك.


الخطوة 3: تكوين Docker Compose

يسمح Docker Compose بتنسيق عدة خدمات. سنُعدّ ملفين: واحد للتطوير وآخر للإنتاج.

Docker Compose للتطوير

أنشئ docker-compose.dev.yml:

# docker-compose.dev.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: deps  # Utiliser seulement l'etape des dependances
    command: npm run dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules  # Exclure node_modules du volume
    environment:
      - NODE_ENV=development
      - DATABASE_URL=postgresql://postgres:password@db:5432/myapp
      - REDIS_URL=redis://cache:6379
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_started
 
  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: password
      POSTGRES_DB: myapp
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
 
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
 
volumes:
  postgres_data:
  redis_data:

Docker Compose للإنتاج

أنشئ docker-compose.prod.yml:

# docker-compose.prod.yml
services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    restart: unless-stopped
    ports:
      - "3000:3000"
    env_file:
      - .env.production
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      start_period: 40s
      retries: 3
    deploy:
      resources:
        limits:
          memory: 512M
          cpus: "0.5"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
 
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    env_file:
      - .env.production
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          memory: 256M
 
  cache:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --maxmemory 128mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    deploy:
      resources:
        limits:
          memory: 128M
 
volumes:
  postgres_data:
  redis_data:

تشغيل بيئة التطوير

# Demarrer tous les services
docker compose -f docker-compose.dev.yml up -d
 
# Voir les logs
docker compose -f docker-compose.dev.yml logs -f app
 
# Arreter les services
docker compose -f docker-compose.dev.yml down

الخطوة 4: تكوين خط أنابيب CI/CD مع GitHub Actions

هذا هو الجزء الأهم. سننشئ خط أنابيب يقوم بـ:

  1. اختبار الكود عند كل طلب سحب (pull request)
  2. بناء صورة Docker
  3. نشر تلقائي على خادم VPS عند كل دمج في فرع main

إنشاء سير العمل الرئيسي

أنشئ ملف .github/workflows/deploy.yml:

# .github/workflows/deploy.yml
name: CI/CD Pipeline
 
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
jobs:
  # =====================================================
  # Job 1 : Linting et tests
  # =====================================================
  test:
    name: Tests et qualite
    runs-on: ubuntu-latest
 
    steps:
      - name: Checkout du code
        uses: actions/checkout@v4
 
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
 
      - name: Installer les dependances
        run: npm ci
 
      - name: Linting
        run: npm run lint
 
      - name: Tests unitaires
        run: npm test -- --passWithNoTests
 
      - name: Build de verification
        run: npm run build
 
  # =====================================================
  # Job 2 : Build et push de l'image Docker
  # =====================================================
  build:
    name: Build Docker
    runs-on: ubuntu-latest
    needs: test
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
 
    permissions:
      contents: read
      packages: write
 
    outputs:
      image-tag: ${{ steps.meta.outputs.tags }}
 
    steps:
      - name: Checkout du code
        uses: actions/checkout@v4
 
      - name: Connexion au registre GitHub
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
 
      - name: Extraire les metadonnees Docker
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=sha,prefix=
            type=raw,value=latest
 
      - name: Setup Docker Buildx
        uses: docker/setup-buildx-action@v3
 
      - name: Build et push
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max
 
  # =====================================================
  # Job 3 : Deploiement sur le VPS
  # =====================================================
  deploy:
    name: Deploiement production
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
 
    environment:
      name: production
      url: https://${{ vars.DOMAIN_NAME }}
 
    steps:
      - name: Checkout du code
        uses: actions/checkout@v4
 
      - name: Deployer via SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.VPS_HOST }}
          username: ${{ secrets.VPS_USER }}
          key: ${{ secrets.VPS_SSH_KEY }}
          script: |
            # Se placer dans le repertoire du projet
            cd /opt/apps/mon-app
 
            # Se connecter au registre GitHub
            echo ${{ secrets.GITHUB_TOKEN }} | docker login ghcr.io -u ${{ github.actor }} --password-stdin
 
            # Telecharger la derniere image
            docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
 
            # Mettre a jour le service avec zero downtime
            docker compose -f docker-compose.prod.yml up -d --no-deps --wait app
 
            # Nettoyer les anciennes images
            docker image prune -f
 
            # Verifier le healthcheck
            sleep 10
            curl -f http://localhost:3000/api/health || exit 1
 
      - name: Notification de succes
        if: success()
        run: echo "Deploiement reussi sur ${{ vars.DOMAIN_NAME }}"
 
      - name: Notification d'echec
        if: failure()
        run: echo "Echec du deploiement - verifier les logs"

تكوين أسرار GitHub

انتقل إلى Settings > Secrets and variables > Actions في مستودع GitHub الخاص بك وأضف:

السرالوصفمثال
VPS_HOSTعنوان IP أو نطاق خادم VPS203.0.113.50
VPS_USERمستخدم SSHdeploy
VPS_SSH_KEYالمفتاح الخاص لـ SSHمحتوى ~/.ssh/id_ed25519

وفي المتغيرات:

المتغيرالوصفمثال
DOMAIN_NAMEاسم النطاقmonapp.example.com

لا تخزّن أبداً مفاتيح SSH أو التوكنات في الكود المصدري. استخدم أسرار GitHub حصرياً للمعلومات الحساسة. أنشئ مفتاح SSH مخصصاً للنشر باستخدام ssh-keygen -t ed25519 -C "deploy@github-actions".


الخطوة 5: تحضير خادم VPS

اتصل بخادم VPS وحضّر البيئة.

تثبيت Docker على خادم VPS

# Mise a jour du systeme
sudo apt update && sudo apt upgrade -y
 
# Installer Docker via le script officiel
curl -fsSL https://get.docker.com | sudo sh
 
# Ajouter l'utilisateur au groupe docker
sudo usermod -aG docker $USER
 
# Installer Docker Compose (inclus dans Docker Engine moderne)
docker compose version
 
# Redemarrer la session pour appliquer les permissions
exit
# Reconnectez-vous

إنشاء هيكل المشروع

# Creer le repertoire de l'application
sudo mkdir -p /opt/apps/mon-app
sudo chown $USER:$USER /opt/apps/mon-app
cd /opt/apps/mon-app
 
# Creer le fichier d'environnement production
cat > .env.production << 'EOF'
NODE_ENV=production
DATABASE_URL=postgresql://postgres:VOTRE_MOT_DE_PASSE_FORT@db:5432/myapp
REDIS_URL=redis://cache:6379
POSTGRES_USER=postgres
POSTGRES_PASSWORD=VOTRE_MOT_DE_PASSE_FORT
POSTGRES_DB=myapp
EOF
 
# Proteger le fichier
chmod 600 .env.production

نسخ ملف Docker Compose

# Copier docker-compose.prod.yml sur le serveur depuis votre machine locale
scp docker-compose.prod.yml deploy@VOTRE_VPS:/opt/apps/mon-app/

إنشاء مستخدم مخصص للنشر

# Creer l'utilisateur deploy
sudo adduser --disabled-password deploy
sudo usermod -aG docker deploy
 
# Configurer la cle SSH pour le deploiement
sudo mkdir -p /home/deploy/.ssh
sudo cp ~/.ssh/authorized_keys /home/deploy/.ssh/
# Ajoutez la cle publique generee pour GitHub Actions
echo "VOTRE_CLE_PUBLIQUE_ED25519" | sudo tee -a /home/deploy/.ssh/authorized_keys
sudo chown -R deploy:deploy /home/deploy/.ssh
sudo chmod 700 /home/deploy/.ssh
sudo chmod 600 /home/deploy/.ssh/authorized_keys

الخطوة 6: تكوين Nginx كوكيل عكسي

يوضع Nginx أمام تطبيق Next.js لإدارة SSL والتخزين المؤقت والضغط.

تثبيت Nginx و Certbot

# Installer Nginx
sudo apt install -y nginx
 
# Installer Certbot pour Let's Encrypt
sudo apt install -y certbot python3-certbot-nginx

تكوين المضيف الافتراضي

أنشئ ملف تكوين Nginx:

# /etc/nginx/sites-available/mon-app
upstream nextjs_upstream {
    server 127.0.0.1:3000;
    keepalive 64;
}
 
server {
    listen 80;
    server_name monapp.example.com;
 
    # Redirection vers HTTPS (sera configure par Certbot)
    location / {
        return 301 https://$server_name$request_uri;
    }
}
 
server {
    listen 443 ssl http2;
    server_name monapp.example.com;
 
    # Les certificats SSL seront ajoutes par Certbot
    # ssl_certificate /etc/letsencrypt/live/monapp.example.com/fullchain.pem;
    # ssl_certificate_key /etc/letsencrypt/live/monapp.example.com/privkey.pem;
 
    # En-tetes de securite
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
 
    # Compression gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript
               application/xml+rss application/atom+xml image/svg+xml;
 
    # Cache des fichiers statiques de Next.js
    location /_next/static {
        proxy_pass http://nextjs_upstream;
        proxy_cache_valid 60m;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
 
    # Fichiers publics statiques
    location /images {
        proxy_pass http://nextjs_upstream;
        proxy_cache_valid 60m;
        add_header Cache-Control "public, max-age=86400";
    }
 
    # Proxy vers Next.js
    location / {
        proxy_pass http://nextjs_upstream;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
 
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }
}

تفعيل الموقع و SSL

# Activer le site
sudo ln -s /etc/nginx/sites-available/mon-app /etc/nginx/sites-enabled/
 
# Verifier la configuration
sudo nginx -t
 
# Recharger Nginx
sudo systemctl reload nginx
 
# Obtenir le certificat SSL avec Certbot
sudo certbot --nginx -d monapp.example.com --non-interactive --agree-tos -m votre@email.com
 
# Verifier le renouvellement automatique
sudo certbot renew --dry-run

سيعدّل Certbot تلقائياً تكوين Nginx لإضافة شهادات SSL. التجديد تلقائي عبر مؤقت systemd.


الخطوة 7: النشر بدون توقف

لتجنب الانقطاعات أثناء التحديثات، سنُعدّ نشراً تدريجياً.

سكربت نشر متقدم

أنشئ scripts/deploy.sh على خادم VPS:

#!/bin/bash
# scripts/deploy.sh - Deploiement avec zero downtime
 
set -euo pipefail
 
APP_DIR="/opt/apps/mon-app"
HEALTH_URL="http://localhost:3000/api/health"
MAX_RETRIES=30
RETRY_INTERVAL=2
 
log() {
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1"
}
 
check_health() {
    curl -sf "$HEALTH_URL" > /dev/null 2>&1
}
 
cd "$APP_DIR"
 
log "Demarrage du deploiement..."
 
# Telecharger la nouvelle image
log "Telechargement de la nouvelle image..."
docker compose -f docker-compose.prod.yml pull app
 
# Demarrer le nouveau conteneur
log "Demarrage du nouveau conteneur..."
docker compose -f docker-compose.prod.yml up -d --no-deps app
 
# Attendre que le healthcheck passe
log "Verification du healthcheck..."
retries=0
until check_health; do
    retries=$((retries + 1))
    if [ "$retries" -ge "$MAX_RETRIES" ]; then
        log "ERREUR: Le healthcheck a echoue apres $MAX_RETRIES tentatives"
        log "Rollback en cours..."
        docker compose -f docker-compose.prod.yml rollback app 2>/dev/null || true
        exit 1
    fi
    log "En attente du healthcheck... ($retries/$MAX_RETRIES)"
    sleep "$RETRY_INTERVAL"
done
 
log "Healthcheck OK"
 
# Nettoyer les anciennes images
docker image prune -f
 
log "Deploiement termine avec succes"
# Rendre le script executable
chmod +x /opt/apps/mon-app/scripts/deploy.sh

الخطوة 8: المراقبة والسجلات

تكوين السجلات المنظمة

أضف تكوين التسجيل في تطبيق Next.js:

// lib/logger.ts
type LogLevel = "info" | "warn" | "error" | "debug";
 
interface LogEntry {
  level: LogLevel;
  message: string;
  timestamp: string;
  [key: string]: unknown;
}
 
function log(level: LogLevel, message: string, meta?: Record<string, unknown>) {
  const entry: LogEntry = {
    level,
    message,
    timestamp: new Date().toISOString(),
    ...meta,
  };
 
  const output = JSON.stringify(entry);
 
  if (level === "error") {
    console.error(output);
  } else if (level === "warn") {
    console.warn(output);
  } else {
    console.log(output);
  }
}
 
export const logger = {
  info: (msg: string, meta?: Record<string, unknown>) => log("info", msg, meta),
  warn: (msg: string, meta?: Record<string, unknown>) => log("warn", msg, meta),
  error: (msg: string, meta?: Record<string, unknown>) => log("error", msg, meta),
  debug: (msg: string, meta?: Record<string, unknown>) => log("debug", msg, meta),
};

أوامر مفيدة للمراقبة

# Voir les logs en temps reel
docker compose -f docker-compose.prod.yml logs -f app
 
# Voir les statistiques des conteneurs
docker stats
 
# Verifier l'etat des conteneurs
docker compose -f docker-compose.prod.yml ps
 
# Verifier l'utilisation disque de Docker
docker system df
 
# Nettoyer les ressources inutilisees
docker system prune -f

إضافة فحص صحة متقدم

أثرِ endpoint الـ /api/health ليشمل مزيداً من المعلومات:

// app/api/health/route.ts
import { NextResponse } from "next/server";
 
export async function GET() {
  const healthData = {
    status: "healthy",
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
    memory: {
      used: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
      total: Math.round(process.memoryUsage().heapTotal / 1024 / 1024),
      unit: "MB",
    },
    version: process.env.npm_package_version || "unknown",
    node: process.version,
  };
 
  return NextResponse.json(healthData);
}

الخطوة 9: تأمين كل شيء

متغيرات البيئة

أنشئ ملف .env.example لتوثيق المتغيرات المطلوبة دون كشف القيم:

# .env.example - Variables d'environnement requises
NODE_ENV=production
DATABASE_URL=postgresql://user:password@db:5432/dbname
REDIS_URL=redis://cache:6379
 
# Base de donnees
POSTGRES_USER=postgres
POSTGRES_PASSWORD=changez_moi
POSTGRES_DB=myapp
 
# Application
NEXTAUTH_SECRET=generez_un_secret_fort
NEXTAUTH_URL=https://monapp.example.com

أفضل ممارسات الأمان

إليك النقاط الأساسية الواجب اتباعها:

  1. مستخدم غير root في الحاوية (مُعدّ مسبقاً في Dockerfile)
  2. أسرار GitHub لجميع المعلومات الحساسة
  3. شهادة SSL عبر Let's Encrypt (تجديد تلقائي)
  4. رؤوس أمان في Nginx (HSTS، X-Frame-Options، إلخ.)
  5. حدود الموارد في Docker Compose (الذاكرة، المعالج)
  6. سجلات منظمة لكشف الحالات الشاذة

غيّر دائماً كلمات المرور الافتراضية. استخدم openssl rand -base64 32 لتوليد أسرار قوية. لا تدفع أبداً ملفات .env إلى مستودع Git.


اختبار خط الأنابيب الكامل

إليك كيف تتحقق أن كل شيء يعمل من البداية إلى النهاية:

1. اختبار محلي مع Docker

# Build et demarrage
docker compose -f docker-compose.dev.yml up --build -d
 
# Verifier les logs
docker compose -f docker-compose.dev.yml logs -f
 
# Tester le healthcheck
curl http://localhost:3000/api/health

2. اختبار خط أنابيب CI/CD

# Creer une branche de test
git checkout -b test/cicd-pipeline
 
# Faire un changement mineur
echo "// test" >> lib/logger.ts
 
# Commit et push
git add . && git commit -m "test: verify CI/CD pipeline"
git push -u origin test/cicd-pipeline
 
# Creer une pull request sur GitHub
gh pr create --title "Test CI/CD pipeline" --body "Verification du pipeline"

3. التحقق من النشر

# Apres le merge sur main, verifier le deploiement
curl https://monapp.example.com/api/health
 
# Reponse attendue :
# {"status":"healthy","timestamp":"...","uptime":42,"memory":{"used":85,"total":128,"unit":"MB"}}

استكشاف الأخطاء وحلها

فشل بناء Docker

المشكلةالحل
COPY failed: file not foundتحقق أن .dockerignore لا يستبعد الملفات الضرورية
فشل npm ciتأكد أن package-lock.json موجود ومُحدّث
نفاد الذاكرة أثناء البناءزِد ذاكرة Docker Desktop أو أضف --max-old-space-size=4096

فشل خط أنابيب GitHub Actions

المشكلةالحل
Permission denied (SSH)تحقق أن مفتاح SSH مُعدّ بشكل صحيح في الأسرار
رفض دفع الصورةتحقق من صلاحيات packages: write في سير العمل
انتهاء مهلة فحص الصحةزِد start-period وتحقق من سجلات التطبيق

النشر لا يعمل

# Verifier les logs du conteneur
docker compose -f docker-compose.prod.yml logs app --tail 50
 
# Verifier que les ports ne sont pas utilises
sudo ss -tulnp | grep 3000
 
# Redemarrer les services
docker compose -f docker-compose.prod.yml restart
 
# Reconstruire completement
docker compose -f docker-compose.prod.yml up -d --build --force-recreate

الخطوات التالية

الآن بعد أن أصبح خط الأنابيب جاهزاً، إليك كيف تُحسّنه:

  • إضافة اختبارات E2E مع Playwright في خط أنابيب CI
  • تكوين إشعارات Slack أو Discord للنشر
  • إعداد CDN (Cloudflare) أمام Nginx للتخزين المؤقت العالمي
  • إضافة مراقبة تطبيقية مع Sentry أو Datadog
  • تطبيق نشر blue/green لضمان عدم التوقف
  • تكوين نسخ احتياطية تلقائية لقاعدة بيانات PostgreSQL

الخلاصة

لديك الآن بنية نشر كاملة ومهنية لتطبيق Next.js:

  • Docker متعدد المراحل لصور خفيفة وآمنة
  • Docker Compose لتنسيق الخدمات
  • GitHub Actions للتكامل والنشر المستمر
  • Nginx + SSL لوكيل عكسي آمن
  • مراقبة مع فحوصات صحة وسجلات منظمة

هذه البنية مُستخدمة في الإنتاج من قبل العديد من الشركات والشركات الناشئة. إنها موثوقة وقابلة لإعادة الإنتاج وسهلة الصيانة. كل عملية دفع إلى فرع main تُطلق تلقائياً عملية نشر مُتحقق منها بفحوصات الصحة.

الأهم هو أن تبدأ بشكل بسيط وتتطور تدريجياً. انشر نسخة أساسية أولاً، ثم أضف تدريجياً طبقات المراقبة والأمان والتحسين.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على البدء مع Laravel 11: التثبيت والتكوين وهيكل المجلدات.

ناقش مشروعك معنا

نحن هنا للمساعدة في احتياجات تطوير الويب الخاصة بك. حدد موعدًا لمناقشة مشروعك وكيف يمكننا مساعدتك.

دعنا نجد أفضل الحلول لاحتياجاتك.

مقالات ذات صلة