Docker Compose للمطورين: Next.js مع PostgreSQL و Redis

AI Bot
بواسطة AI Bot ·

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

أمر واحد لتشغيل المشروع بالكامل. يتيح لك Docker Compose تعريف تطبيق Next.js وقاعدة بيانات PostgreSQL وذاكرة Redis المؤقتة كوحدة تصريحية واحدة. في هذا الدليل، ستبني بيئة تطوير جاهزة للإنتاج يمكن لأي عضو في الفريق تشغيلها بأمر docker compose up.

ما ستتعلمه

بنهاية هذا الدليل، ستتمكن من:

  • إعداد Docker Compose لتنسيق تطبيق متعدد الخدمات
  • تغليف تطبيق Next.js 15 مع إعادة التحميل الفوري أثناء التطوير
  • تشغيل PostgreSQL 16 مع وحدات تخزين دائمة وتهيئة تلقائية
  • إضافة Redis 7 كطبقة تخزين مؤقت مع فحوصات الصحة
  • ضبط متغيرات البيئة بشكل آمن عبر الخدمات
  • كتابة Dockerfile متعدد المراحل محسّن لبناءات الإنتاج
  • تنفيذ فحوصات الصحة وترتيب التبعيات بين الخدمات
  • إنشاء إعدادات تطوير وإنتاج منفصلة

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

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

  • Docker Desktop مثبت وقيد التشغيل (الإصدار 4.25 أو أحدث) — حمّله من هنا
  • Node.js 20+ مثبت محليًا لإعداد المشروع الأولي
  • معرفة أساسية بالطرفية — التنقل بين المجلدات وتنفيذ الأوامر
  • إلمام بـ Next.js — الصفحات ومسارات API و Server Components
  • محرر أكواد — يُنصح بـ VS Code مع إضافة Docker

لماذا Docker Compose للتطوير الكامل؟

كل مطور واجه مشكلة "يعمل على جهازي". تطبيق Next.js يتصل بقاعدة بيانات PostgreSQL محلية، ويستخدم Redis للتخزين المؤقت، وكل شيء يعمل بشكل مثالي — حتى يستنسخ زميل المستودع ويقضي ساعات في ضبط بيئته.

Docker Compose يحل هذه المشكلة بتعريف مجموعة التطبيق بالكامل في ملف docker-compose.yml واحد. كل خدمة، كل سلسلة اتصال، كل تعيين منفذ — يُعرَّف مرة واحدة ويُستنسخ في كل مكان.

إليك ما يجعل Docker Compose أساسيًا في 2026:

  • بيئات متسقة — التطوير والاختبار والإنتاج تستخدم نفس إعدادات الخدمات
  • إعداد بأمر واحد — أعضاء الفريق الجدد ينفذون docker compose up ويبدأون البرمجة فورًا
  • قواعد بيانات معزولة — لا تعارضات بين قواعد بيانات المشاريع المختلفة على نفس الجهاز
  • بيئات قابلة للتجديد — هدم وإعادة بناء المجموعة بالكامل في ثوانٍ
  • تكامل CI/CD — نفس ملف Compose يعمل في GitHub Actions و GitLab CI والتطوير المحلي

نظرة عامة على المشروع

ستبني واجهة API لإدارة المهام بالهيكل التالي:

┌─────────────────────────────────────────┐
│            Docker Compose               │
│                                         │
│  ┌───────────┐  ┌──────────┐  ┌──────┐ │
│  │  Next.js  │→ │PostgreSQL│  │Redis │ │
│  │  :3000    │→ │  :5432   │  │:6379 │ │
│  └───────────┘  └──────────┘  └──────┘ │
│       ↑              ↑           ↑      │
│       └──────── الشبكة ──────────┘      │
└─────────────────────────────────────────┘
  • Next.js 15 — App Router مع مسارات API لواجهة CRUD للمهام
  • PostgreSQL 16 — مخزن البيانات الأساسي للمهام والمستخدمين
  • Redis 7 — طبقة التخزين المؤقت للبيانات المتكررة الوصول

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

ابدأ بإنشاء تطبيق Next.js جديد:

npx create-next-app@latest docker-fullstack --typescript --tailwind --app --src-dir --eslint
cd docker-fullstack

ثبّت مكتبات قاعدة البيانات والتخزين المؤقت:

npm install pg redis
npm install -D @types/pg

هيكل المشروع سيبدو هكذا:

docker-fullstack/
├── src/
│   ├── app/
│   │   ├── api/
│   │   ├── layout.tsx
│   │   └── page.tsx
│   └── lib/
├── package.json
├── tsconfig.json
└── next.config.ts

الخطوة 2: إنشاء Dockerfile

أنشئ ملف Dockerfile في جذر المشروع. يستخدم بناء متعدد المراحل للحفاظ على صغر حجم الصورة النهائية:

# المرحلة 1: التبعيات
FROM node:20-alpine AS deps
WORKDIR /app
 
COPY package.json package-lock.json ./
RUN npm ci --only=production && cp -R node_modules /prod_deps
RUN npm ci
 
# المرحلة 2: البناء
FROM node:20-alpine AS builder
WORKDIR /app
 
COPY --from=deps /app/node_modules ./node_modules
COPY . .
 
RUN npm run build
 
# المرحلة 3: التشغيل (الإنتاج)
FROM node:20-alpine AS runner
WORKDIR /app
 
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
 
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
 
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
 
USER nextjs
EXPOSE 3000
ENV PORT=3000
 
CMD ["node", "server.js"]

لكي تعمل مرحلة الإنتاج مع الإخراج المستقل، حدّث next.config.ts:

import type { NextConfig } from "next";
 
const nextConfig: NextConfig = {
  output: "standalone",
};
 
export default nextConfig;

الخطوة 3: إنشاء Dockerfile للتطوير

للتطوير، تحتاج إعادة التحميل الفوري وتركيب المصادر. أنشئ Dockerfile.dev:

FROM node:20-alpine
 
WORKDIR /app
 
COPY package.json package-lock.json ./
RUN npm ci
 
COPY . .
 
EXPOSE 3000
 
CMD ["npm", "run", "dev"]

ملف Dockerfile المبسّط هذا يتخطى البناء متعدد المراحل — في التطوير، السرعة وإعادة التحميل الفوري أهم من حجم الصورة.


الخطوة 4: كتابة ملف Docker Compose

أنشئ docker-compose.yml في جذر المشروع:

services:
  # تطبيق Next.js
  app:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - ./src:/app/src
      - ./public:/app/public
      - /app/node_modules
      - /app/.next
    environment:
      - DATABASE_URL=postgresql://postgres:postgres@db:5432/taskdb
      - REDIS_URL=redis://cache:6379
      - NODE_ENV=development
    depends_on:
      db:
        condition: service_healthy
      cache:
        condition: service_healthy
    networks:
      - app-network
 
  # قاعدة بيانات PostgreSQL
  db:
    image: postgres:16-alpine
    ports:
      - "5432:5432"
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: taskdb
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 5s
      timeout: 5s
      retries: 5
    networks:
      - app-network
 
  # ذاكرة Redis المؤقتة
  cache:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5
    command: redis-server --appendonly yes
    networks:
      - app-network
 
volumes:
  postgres_data:
  redis_data:
 
networks:
  app-network:
    driver: bridge

لنشرح المفاهيم الأساسية:

تركيب وحدات التخزين (Volume Mounts)

volumes:
  - ./src:/app/src        # تركيب المصادر لإعادة التحميل الفوري
  - ./public:/app/public  # تركيب الملفات العامة
  - /app/node_modules     # وحدة تخزين مجهولة — تحافظ على node_modules الحاوية
  - /app/.next            # وحدة تخزين مجهولة — تحافظ على ذاكرة البناء المؤقتة

وحدات التخزين المجهولة لـ node_modules و .next تمنع ملفاتك المحلية من تجاوز التبعيات المثبتة في الحاوية. هذا أمر حاسم — بدونها، ستتعطل الوحدات الأصلية بسبب اختلاف البنية المعمارية (macOS مقابل Linux).

فحوصات الصحة (Health Checks)

healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 5s
  timeout: 5s
  retries: 5

فحوصات الصحة تضمن أن PostgreSQL جاهز فعلًا لقبول الاتصالات قبل أن يحاول Next.js الاتصال. بدون هذا، سيتعطل تطبيقك عند البدء لأن المنفذ قد يكون مفتوحًا لكن الخادم لا يزال يتهيأ.

ترتيب التبعيات

depends_on:
  db:
    condition: service_healthy
  cache:
    condition: service_healthy

استخدام condition: service_healthy بدلًا من مجرد depends_on: [db] ينتظر حتى يمر فحص الصحة، وليس فقط حتى يبدأ الحاوية.


الخطوة 5: تهيئة قاعدة البيانات

أنشئ init.sql لإعداد مخطط قاعدة البيانات عند التشغيل الأول:

-- إنشاء جدول المهام
CREATE TABLE IF NOT EXISTS tasks (
    id SERIAL PRIMARY KEY,
    title VARCHAR(255) NOT NULL,
    description TEXT,
    status VARCHAR(20) DEFAULT 'pending' CHECK (status IN ('pending', 'in_progress', 'completed')),
    priority INTEGER DEFAULT 0 CHECK (priority BETWEEN 0 AND 3),
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
 
-- إنشاء فهارس للاستعلامات الشائعة
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_priority ON tasks(priority);
 
-- إدراج بيانات نموذجية
INSERT INTO tasks (title, description, status, priority) VALUES
    ('إعداد بيئة Docker', 'ضبط Docker Compose للمشروع', 'completed', 3),
    ('تصميم مخطط قاعدة البيانات', 'إنشاء جداول وفهارس PostgreSQL', 'in_progress', 2),
    ('تنفيذ طبقة التخزين المؤقت', 'إضافة تخزين Redis المؤقت لاستجابات API', 'pending', 1),
    ('كتابة نقاط نهاية API', 'بناء عمليات CRUD للمهام', 'pending', 2),
    ('إضافة المصادقة', 'تنفيذ مصادقة JWT', 'pending', 1);
 
-- إنشاء مشغّل updated_at
CREATE OR REPLACE FUNCTION update_modified_column()
RETURNS TRIGGER AS $$
BEGIN
    NEW.updated_at = NOW();
    RETURN NEW;
END;
$$ language 'plpgsql';
 
CREATE TRIGGER update_tasks_modtime
    BEFORE UPDATE ON tasks
    FOR EACH ROW
    EXECUTE FUNCTION update_modified_column();

هذا الملف يُنفَّذ تلقائيًا عند بدء تشغيل حاوية PostgreSQL لأول مرة (عبر تركيب docker-entrypoint-initdb.d).


الخطوة 6: وحدة اتصال قاعدة البيانات

أنشئ src/lib/db.ts لإدارة مجموعة اتصالات PostgreSQL:

import { Pool } from "pg";
 
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20,
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});
 
pool.on("error", (err) => {
  console.error("خطأ غير متوقع في مجموعة قاعدة البيانات:", err);
});
 
export async function query<T>(text: string, params?: unknown[]): Promise<T[]> {
  const result = await pool.query(text, params);
  return result.rows as T[];
}
 
export async function queryOne<T>(
  text: string,
  params?: unknown[]
): Promise<T | null> {
  const rows = await query<T>(text, params);
  return rows[0] || null;
}
 
export default pool;

الخطوة 7: وحدة اتصال Redis

أنشئ src/lib/redis.ts لطبقة التخزين المؤقت:

import { createClient } from "redis";
 
const redis = createClient({
  url: process.env.REDIS_URL,
});
 
redis.on("error", (err) => {
  console.error("خطأ في اتصال Redis:", err);
});
 
redis.on("connect", () => {
  console.log("تم الاتصال بـ Redis");
});
 
// الاتصال عند أول استيراد
if (!redis.isOpen) {
  redis.connect();
}
 
export async function getCache<T>(key: string): Promise<T | null> {
  const data = await redis.get(key);
  if (!data) return null;
  return JSON.parse(data) as T;
}
 
export async function setCache(
  key: string,
  value: unknown,
  ttlSeconds = 60
): Promise<void> {
  await redis.set(key, JSON.stringify(value), { EX: ttlSeconds });
}
 
export async function invalidateCache(pattern: string): Promise<void> {
  const keys = await redis.keys(pattern);
  if (keys.length > 0) {
    await redis.del(keys);
  }
}
 
export default redis;

الخطوة 8: بناء مسارات API

أنشئ واجهة API للمهام مع عمليات CRUD الكاملة. ابدأ بـ src/app/api/tasks/route.ts:

import { NextRequest, NextResponse } from "next/server";
import { query } from "@/lib/db";
import { getCache, setCache, invalidateCache } from "@/lib/redis";
 
interface Task {
  id: number;
  title: string;
  description: string | null;
  status: string;
  priority: number;
  created_at: string;
  updated_at: string;
}
 
// GET /api/tasks
export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url);
  const status = searchParams.get("status");
 
  // التحقق من الذاكرة المؤقتة أولاً
  const cacheKey = `tasks:${status || "all"}`;
  const cached = await getCache<Task[]>(cacheKey);
  if (cached) {
    return NextResponse.json({ data: cached, source: "cache" });
  }
 
  // الاستعلام من قاعدة البيانات
  let tasks: Task[];
  if (status) {
    tasks = await query<Task>(
      "SELECT * FROM tasks WHERE status = $1 ORDER BY priority DESC, created_at DESC",
      [status]
    );
  } else {
    tasks = await query<Task>(
      "SELECT * FROM tasks ORDER BY priority DESC, created_at DESC"
    );
  }
 
  // التخزين المؤقت لمدة 30 ثانية
  await setCache(cacheKey, tasks, 30);
 
  return NextResponse.json({ data: tasks, source: "database" });
}
 
// POST /api/tasks
export async function POST(request: NextRequest) {
  const body = await request.json();
  const { title, description, priority } = body;
 
  if (!title) {
    return NextResponse.json(
      { error: "العنوان مطلوب" },
      { status: 400 }
    );
  }
 
  const task = await query<Task>(
    "INSERT INTO tasks (title, description, priority) VALUES ($1, $2, $3) RETURNING *",
    [title, description || null, priority || 0]
  );
 
  // إبطال ذاكرة المهام المؤقتة
  await invalidateCache("tasks:*");
 
  return NextResponse.json({ data: task[0] }, { status: 201 });
}

ثم أنشئ المسار الديناميكي في src/app/api/tasks/[id]/route.ts:

import { NextRequest, NextResponse } from "next/server";
import { query, queryOne } from "@/lib/db";
import { invalidateCache } from "@/lib/redis";
 
interface Task {
  id: number;
  title: string;
  description: string | null;
  status: string;
  priority: number;
  created_at: string;
  updated_at: string;
}
 
// GET /api/tasks/:id
export async function GET(
  _request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const task = await queryOne<Task>("SELECT * FROM tasks WHERE id = $1", [id]);
 
  if (!task) {
    return NextResponse.json({ error: "المهمة غير موجودة" }, { status: 404 });
  }
 
  return NextResponse.json({ data: task });
}
 
// PATCH /api/tasks/:id
export async function PATCH(
  request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const body = await request.json();
  const { title, description, status, priority } = body;
 
  const task = await queryOne<Task>(
    `UPDATE tasks
     SET title = COALESCE($1, title),
         description = COALESCE($2, description),
         status = COALESCE($3, status),
         priority = COALESCE($4, priority)
     WHERE id = $5
     RETURNING *`,
    [title, description, status, priority, id]
  );
 
  if (!task) {
    return NextResponse.json({ error: "المهمة غير موجودة" }, { status: 404 });
  }
 
  await invalidateCache("tasks:*");
  return NextResponse.json({ data: task });
}
 
// DELETE /api/tasks/:id
export async function DELETE(
  _request: NextRequest,
  { params }: { params: Promise<{ id: string }> }
) {
  const { id } = await params;
  const task = await queryOne<Task>(
    "DELETE FROM tasks WHERE id = $1 RETURNING *",
    [id]
  );
 
  if (!task) {
    return NextResponse.json({ error: "المهمة غير موجودة" }, { status: 404 });
  }
 
  await invalidateCache("tasks:*");
  return NextResponse.json({ data: task });
}

الخطوة 9: إضافة نقطة نهاية فحص الصحة

أنشئ src/app/api/health/route.ts للتحقق من اتصال جميع الخدمات:

import { NextResponse } from "next/server";
import pool from "@/lib/db";
import redis from "@/lib/redis";
 
export async function GET() {
  const health: Record<string, string> = {
    status: "ok",
    timestamp: new Date().toISOString(),
  };
 
  // فحص PostgreSQL
  try {
    await pool.query("SELECT 1");
    health.database = "connected";
  } catch {
    health.database = "disconnected";
    health.status = "degraded";
  }
 
  // فحص Redis
  try {
    await redis.ping();
    health.cache = "connected";
  } catch {
    health.cache = "disconnected";
    health.status = "degraded";
  }
 
  const statusCode = health.status === "ok" ? 200 : 503;
  return NextResponse.json(health, { status: statusCode });
}

الخطوة 10: تشغيل المجموعة

كل شيء جاهز. شغّل التطبيق بالكامل بأمر واحد:

docker compose up --build

سترى مخرجات تُظهر بدء تشغيل الخدمات الثلاث:

[+] Running 3/3
 ✔ Container docker-fullstack-cache-1  Healthy
 ✔ Container docker-fullstack-db-1     Healthy
 ✔ Container docker-fullstack-app-1    Started

اختبر نقاط النهاية:

# فحص الصحة
curl http://localhost:3000/api/health
 
# عرض جميع المهام
curl http://localhost:3000/api/tasks
 
# إنشاء مهمة جديدة
curl -X POST http://localhost:3000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{"title": "تعلم Docker Compose", "description": "اتبع الدليل", "priority": 3}'
 
# التصفية حسب الحالة
curl http://localhost:3000/api/tasks?status=pending
 
# تحديث مهمة
curl -X PATCH http://localhost:3000/api/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{"status": "completed"}'
 
# حذف مهمة
curl -X DELETE http://localhost:3000/api/tasks/1

الخطوة 11: نصائح لسير عمل التطوير

عرض السجلات

# جميع الخدمات
docker compose logs -f
 
# خدمة واحدة
docker compose logs -f app
 
# آخر 50 سطرًا
docker compose logs --tail 50 db

الوصول إلى طرفية قاعدة البيانات

docker compose exec db psql -U postgres -d taskdb

بمجرد الدخول إلى psql، يمكنك تنفيذ الاستعلامات مباشرة:

SELECT * FROM tasks;
\dt  -- عرض الجداول
\d tasks  -- وصف مخطط الجدول

الوصول إلى طرفية Redis

docker compose exec cache redis-cli

أوامر Redis مفيدة لتصحيح الأخطاء:

KEYS *           # عرض جميع المفاتيح
GET tasks:all    # عرض البيانات المخزنة
TTL tasks:all    # التحقق من وقت الانتهاء
FLUSHALL         # مسح جميع الذاكرة المؤقتة

إعادة البناء بعد تغيير التبعيات

عند إضافة حزم npm جديدة، أعد بناء حاوية التطبيق:

docker compose up --build app

إعادة تعيين قاعدة البيانات

لمسح قاعدة البيانات والبدء من جديد:

docker compose down -v  # -v يزيل وحدات التخزين
docker compose up --build

الخطوة 12: إعدادات الإنتاج

أنشئ docker-compose.prod.yml لتجاوزات الإنتاج:

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
    volumes: []  # بدون تركيب مصادر في الإنتاج
    environment:
      - NODE_ENV=production
      - DATABASE_URL=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}
      - REDIS_URL=redis://cache:6379
    restart: always
 
  db:
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    restart: always
 
  cache:
    command: redis-server --appendonly yes --requirepass ${REDIS_PASSWORD}
    restart: always

أنشئ ملف .env.production (لا تُرسله أبدًا إلى المستودع):

DB_USER=taskapp
DB_PASSWORD=كلمة-مرور-قوية-جداً-هنا
DB_NAME=taskdb_prod
REDIS_PASSWORD=كلمة-مرور-قوية-أخرى

نشر التطبيق:

docker compose -f docker-compose.yml -f docker-compose.prod.yml --env-file .env.production up -d --build

علامات -f تدمج ملفَي Compose، حيث يتجاوز ملف الإنتاج قيم التطوير.


الخطوة 13: إضافة ملف .dockerignore

أنشئ .dockerignore للحفاظ على نظافة الصور:

node_modules
.next
.git
.gitignore
*.md
docker-compose*.yml
.env*
.vscode
coverage

هذا يمنع نسخ المجلدات الكبيرة والملفات الحساسة إلى صورة Docker، مما يقلل وقت البناء وحجم الصورة.


الخطوة 14: المراقبة مع Docker Compose

أضف مراقبة أساسية باستخدام أمر الإحصائيات:

# استخدام الموارد في الوقت الفعلي
docker compose stats
 
# المخرجات:
# NAME          CPU %   MEM USAGE / LIMIT   NET I/O
# app-1         0.50%   245MiB / 8GiB       1.2kB / 890B
# db-1          0.10%   45MiB / 8GiB        500B / 200B
# cache-1       0.05%   12MiB / 8GiB        300B / 100B

لبيئات الإنتاج، يمكنك إضافة حدود الموارد:

services:
  app:
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.5"
          memory: 256M

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

المنفذ مستخدم بالفعل

Error: bind: address already in use

عملية أخرى تستخدم المنفذ. ابحث عنها وأوقفها:

# البحث عن العملية التي تستخدم المنفذ 5432
lsof -i :5432
 
# أو غيّر تعيين المنفذ في docker-compose.yml
ports:
  - "5433:5432"  # تعيين إلى 5433 محليًا

رفض اتصال قاعدة البيانات

إذا بدأ التطبيق قبل جاهزية PostgreSQL، سترى:

Error: connect ECONNREFUSED 172.18.0.2:5432

هذا لا يجب أن يحدث مع فحوصات الصحة المُعدّة، لكن إن حدث، تأكد أن depends_on يستخدم condition: service_healthy.

مشاكل أذونات وحدات التخزين على Linux

# إصلاح أذونات بيانات PostgreSQL
sudo chown -R 999:999 ./postgres_data
 
# أو استخدم وحدات تخزين مسمّاة (مُنصح بها، مستخدمة بالفعل في هذا الدليل)

إعادة التحميل الفوري لا تعمل

تأكد أن تركيب وحدات التخزين يشمل مجلد المصادر:

volumes:
  - ./src:/app/src

وتحقق أن خادم تطوير Next.js يراقب التغييرات. إذا كنت تستخدم Docker Desktop على macOS، مراقبة الملفات تعمل تلقائيًا عبر gRPC FUSE.


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

الآن وبيئة Docker Compose تعمل، فكّر في هذه التحسينات:

  • أضف Prisma أو Drizzle ORM — استبدل SQL الخام بـ ORM آمن الأنواع لإدارة الهجرات والمخططات
  • نفّذ المصادقة — أضف جدول مستخدمين ومصادقة JWT لحماية مسارات API
  • أعدّ Nginx — أضف خدمة وكيل عكسي لإنهاء SSL وموازنة الحمل
  • أضف pgAdmin — أضف واجهة إدارة قاعدة البيانات كخدمة Compose أخرى
  • اضبط CI/CD — استخدم نفس ملف Compose في GitHub Actions لاختبارات التكامل
  • أضف Adminer — أداة خفيفة لإدارة قواعد البيانات كخدمة Compose

الخلاصة

Docker Compose يحوّل التطوير الكامل بالقضاء على تناقضات البيئات. في هذا الدليل، بنيت مجموعة تطبيق كاملة تشمل:

  • Next.js 15 يخدم الواجهة البرمجية مع إعادة التحميل الفوري أثناء التطوير
  • PostgreSQL 16 مع تخزين دائم وتهيئة تلقائية وفحوصات الصحة
  • Redis 7 لتخزين الاستجابات مؤقتًا مع إبطال قائم على TTL
  • Dockerfile متعدد المراحل محسّن لكل من التطوير والإنتاج
  • إعدادات Compose منفصلة لبيئتَي التطوير والإنتاج

المجموعة بالكامل تبدأ بأمر docker compose up واحد. كل عضو في الفريق يحصل على بيئة متطابقة، ونفس الإعدادات تمتد لخطوط أنابيب CI/CD ونشر الإنتاج.

الأنماط الأساسية التي تعلمتها — فحوصات الصحة، ترتيب التبعيات، تركيب وحدات التخزين، والبناء متعدد المراحل — تنطبق على أي مشروع Docker Compose، بغض النظر عن مجموعة التقنيات.


هل تريد قراءة المزيد من الدروس التعليمية؟ تحقق من أحدث درس تعليمي لدينا على بناء واجهات REST API جاهزة للإنتاج باستخدام FastAPI و PostgreSQL و Docker.

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

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

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

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