Logo

المعمل 10: Docker في بيئات الإنتاج - البنى متعددة المراحل وقواعد البيانات

12 دقيقة قراءة
شرائح الدرس
1 / 12

المعمل 10: Docker في بيئة الإنتاج - البيئات، والبناء متعدد المراحل، وقواعد البيانات

تخصيصات التطوير/الإنتاج، وDockerfile متعدد المراحل، وMongoDB، والشبكات، وديمومة البيانات

نظرة عامة

يغطي هذا المعمل مواضيع Docker المتقدمة: إدارة بيئات نشر متعددة باستخدام ملفات Docker Compose منفصلة، وكتابة ملفات Dockerfile متعددة المراحل (Multi-Stage) لإنتاج صور إنتاج خفيفة، وربط تطبيق Node.js بقاعدة بيانات MongoDB تعمل داخل حاوية Docker، وإدامة بيانات قاعدة البيانات باستخدام أحجام Docker (Volumes)، وإضافة واجهة ويب Mongo Express كخدمة إضافية.


الأهداف

  • فصل إعدادات التطوير والإنتاج باستخدام عدة ملفات Docker Compose
  • استخدام ملف docker-compose.yml أساسي مشترك مع ملفات تجاوز (Override) خاصة بكل بيئة
  • كتابة ملف Dockerfile متعدد المراحل بمراحل بناء منفصلة لـ development وproduction
  • تشغيل MongoDB كحاوية Docker جنبًا إلى جنب مع تطبيق Node.js
  • استخدام شبكات Docker حتى تتواصل الحاويات عبر اسم الخدمة
  • إدامة بيانات قاعدة البيانات عبر عمليات إعادة تشغيل الحاوية باستخدام أحجام Docker مُسمّاة
  • إضافة Mongo Express كحاوية واجهة إدارة لـ MongoDB

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

  • إتمام المعملين 08 و09 (أساسيات Docker، تطبيق Node.js بحاوية واحدة، Docker Compose)
  • تثبيت Docker وDocker Compose
  • تطبيق Node.js يحتوي بالفعل على package.json

خلفية نظرية

1. بيئات Docker (التطوير مقابل الإنتاج)

يتطلب تشغيل نفس التطبيق في التطوير والإنتاج إعدادات مختلفة:

الإعدادالتطويرالإنتاج
أمر بدء تشغيل Nodenpm run dev (nodemon)npm start
NODE_ENVdevelopmentproduction
ربط مسار الحجم (إعادة التحميل الفوري)نعملا
تثبيت تبعيات التطويرنعملا (--only=production)

الاستراتيجية: ملف أساسي واحد + ملفات تجاوز لكل بيئة

بدلًا من تكرار ملف Docker Compose كامل لكل بيئة، استخدم:

  • docker-compose.yml - الإعدادات المشتركة (المنافذ، اسم الخدمة، سياق البناء)
  • docker-compose.dev.yml - تجاوزات خاصة بالتطوير (الأحجام، الأمر، متغيرات البيئة)
  • docker-compose.prod.yml - تجاوزات خاصة بالإنتاج (الأمر، متغيرات البيئة)

docker-compose.yml (الأساسي - إعدادات مشتركة)

version: "3"
services:
  node-app:
    build: .
    ports:
      - "3000:3000"

docker-compose.dev.yml (تجاوزات التطوير)

version: "3"
services:
  node-app:
    volumes:
      - ./:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
    command: npm run dev

docker-compose.prod.yml (تجاوزات الإنتاج)

version: "3"
services:
  node-app:
    environment:
      - NODE_ENV=production
    command: npm start

التشغيل ببيئة محددة

حدد كلا الملفين باستخدام -f. يدمج Docker Compose بينهما - يفوز ملف التجاوز في أي مفتاح موجود في كليهما.

# Development
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
 
# Production
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
 
# Rebuild and start (after Dockerfile changes)
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
 
# Stop (same files must be specified)
docker compose -f docker-compose.yml -f docker-compose.dev.yml down

ملاحظة: يجب تحديد كلا ملفي -f عند تشغيل down أيضًا. إذا لم يستطع Docker Compose إيجاد اسم الخدمة سيُبلِغ عن خطأ.


2. ملف Dockerfile متعدد المراحل (Multi-Stage)

يتيح لك ملف Dockerfile متعدد المراحل استخدام تعليمات بناء مختلفة لبيئات مختلفة - كل ذلك في ملف واحد. يشير Docker Compose إلى المرحلة الصحيحة باستخدام حقل target.

لماذا متعدد المراحل؟

المشكلةالحل
تبعيات التطوير مُضمَّنة في صورة الإنتاجشغّل فقط npm install --only=production في مرحلة production
أمر بدء تشغيل خاطئ لكل بيئةكل مرحلة تُعرِّف CMD الخاصة بها
حجم كبير للصورة النهائيةمرحلة الإنتاج تتخطى أدوات التطوير كليًا

ملف Dockerfile متعدد المراحل في ملف واحد

FROM node:14 AS base
WORKDIR /app
COPY package*.json ./
 
FROM base AS development
RUN npm install
COPY . .
CMD ["npm", "run", "dev"]
 
FROM base AS production
RUN npm install --only=production
COPY . .
CMD ["npm", "start"]

الشرح:

  • مرحلة base - تضبط مجلد العمل وتنسخ package.json فقط؛ بدون تثبيت بعد
  • مرحلة development (FROM base AS development) - تثبيت npm install كامل شامل تبعيات التطوير؛ تبدأ باستخدام nodemon
  • مرحلة production (FROM base AS production) - npm install --only=production يتخطى أدوات التطوير؛ تبدأ بشكل طبيعي

توجيه Docker Compose إلى مرحلة

استبدل build: . البسيطة بكتلة بناء تحدد context وtarget:

docker-compose.dev.yml

version: "3"
services:
  node-app:
    build:
      context: .
      target: development
    volumes:
      - ./:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development

docker-compose.prod.yml

version: "3"
services:
  node-app:
    build:
      context: .
      target: production
    environment:
      - NODE_ENV=production

مخطط معماري

graph TD
    A[Dockerfile] --> B["مرحلة base\nFROM node:14\nWORKDIR /app\nCOPY package*.json"]
    B --> C["مرحلة development\nRUN npm install\nCMD npm run dev"]
    B --> D["مرحلة production\nRUN npm install --only=production\nCMD npm start"]
 
    E["docker-compose.dev.yml\ntarget: development"] --> C
    F["docker-compose.prod.yml\ntarget: production"] --> D

3. Docker مع MongoDB وNode.js

إضافة MongoDB كخدمة

تمتلك MongoDB صورة رسمية على Docker Hub. أضفها كخدمة ثانية في docker-compose.yml:

version: "3"
services:
  node-app:
    build: .
    ports:
      - "3000:3000"
 
  mongo:
    image: mongo
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=example
    volumes:
      - mongo-db:/data/db
 
volumes:
  mongo-db:

يُعلِن قسم volumes: في الأسفل عن الحجم المُسمّى mongo-db. تربطه الخدمة بـ /data/db داخل الحاوية - وهذا هو المكان الذي تخزّن فيه MongoDB ملفات بياناتها.

الاتصال من Node.js باستخدام Mongoose

ثبّت mongoose:

npm install mongoose

في تطبيق Node.js الخاص بك (index.js):

const mongoose = require("mongoose");
 
const DB_USER = "root";
const DB_PASSWORD = "example";
const DB_HOST = "mongo";    // Docker service name — resolved automatically by Docker DNS
const DB_PORT = 27017;
 
const URI = `mongodb://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}`;
 
mongoose
  .connect(URI)
  .then(() => console.log("Connected to database"))
  .catch((err) => console.log("Failed to connect to database", err));

نقطة أساسية: استخدم اسم خدمة Docker Compose (mongo) كاسم مضيف (Hostname). يربط نظام DNS المدمج في Docker اسم الخدمة بعنوان IP الخاص بالحاوية تلقائيًا. لن تحتاج أبدًا لكتابة عنوان IP بشكل ثابت.

كيف تعمل شبكات Docker

عندما يبدأ Docker Compose عدة خدمات، يُنشئ شبكة افتراضية (Default Network) ويربط كل الحاويات بها. تحصل كل حاوية على عنوان IP، ويربط نظام DNS الداخلي في Docker كل اسم خدمة بعنوان IP الحالي للحاوية الخاصة بها.

graph LR
    subgraph default_network["شبكة Docker الافتراضية"]
        A["حاوية\nnode-app"] <-->|"اسم المضيف: mongo"| B["حاوية\nmongo"]
    end
    C["الجهاز المضيف"] -->|":3000"| A

فحص الحاويات والشبكات

# List running containers
docker ps
 
# Inspect a container (shows IP, network, env, volumes)
docker inspect <container_name>
 
# List Docker networks
docker network ls
 
# Inspect the default network (shows all containers and their IPs)
docker network inspect <network_name>

التحقق من الاتصال

# Follow application logs
docker logs <node_app_container> -f
 
# Expected output:
# App is up and running on port 3000
# Connected to database

العمل داخل حاوية MongoDB

# Open a terminal on the MongoDB container
docker exec -it <mongo_container> bash
 
# Start the mongo shell
mongosh -u root -p example
 
# Inside mongosh
show dbs
use testdb
db.books.insertOne({ title: "Book One" })
db.books.find()

يمكنك أيضًا تشغيل أمر مباشرة دون فتح صدفة (Shell):

docker exec -it <mongo_container> mongosh -u root -p example --eval "show dbs"

4. أحجام Docker لإدامة البيانات

بدون حجم (Volume)، تُفقَد كل بيانات MongoDB عند إيقاف الحاوية.

توضيح مشكلة فقدان البيانات:

docker compose down        # stops and removes containers
docker compose up -d       # restarts — database is empty again

الحل: حجم مُسمّى (Named Volume)

أضف إلى docker-compose.yml:

services:
  mongo:
    image: mongo
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=example
    volumes:
      - mongo-db:/data/db   # named volume -> MongoDB data directory inside container
 
volumes:
  mongo-db:                 # declare the named volume here

أوامر إدارة الأحجام:

# List all volumes
docker volume ls
 
# Remove a specific volume
docker volume rm <volume_name>
 
# Remove all unused volumes (cleanup)
docker volume prune
 
# Stop containers but KEEP volumes (default)
docker compose down
 
# Stop containers AND DELETE volumes
docker compose down -v

تحذير: يحذف docker compose down -v بيانات قاعدة بياناتك بشكل دائم. لا تستخدم -v أبدًا على قاعدة بيانات إنتاج.


5. واجهة إدارة Mongo Express

Mongo Express هي واجهة إدارة MongoDB تعمل عبر الويب، وتُشغَّل كحاوية Docker خاصة بها جنبًا إلى جنب مع MongoDB.

إضافة Mongo Express إلى Docker Compose

version: "3"
services:
  node-app:
    build: .
    ports:
      - "3000:3000"
 
  mongo:
    image: mongo
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=example
    volumes:
      - mongo-db:/data/db
 
  mongo-express:
    image: mongo-express
    ports:
      - "8081:8081"
    environment:
      - ME_CONFIG_MONGODB_ADMINUSERNAME=root
      - ME_CONFIG_MONGODB_ADMINPASSWORD=example
      - ME_CONFIG_MONGODB_SERVER=mongo
    depends_on:
      - mongo
 
volumes:
  mongo-db:

متغيرات بيئة Mongo Express:

المتغيّرالقيمةالغرض
ME_CONFIG_MONGODB_ADMINUSERNAMErootاسم مستخدم مسؤول MongoDB
ME_CONFIG_MONGODB_ADMINPASSWORDexampleكلمة مرور مسؤول MongoDB
ME_CONFIG_MONGODB_SERVERmongoاسم خدمة حاوية MongoDB

depends_on: - mongo يخبر Docker Compose ببدء تشغيل حاوية mongo قبل بدء تشغيل mongo-express.

تشغيل الخدمات الثلاث جميعًا

docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

افتح http://localhost:8081 في المتصفح للوصول إلى واجهة Mongo Express.

المعمارية الكاملة للحاويات الثلاث

graph LR
    Browser["المتصفح"] -->|":8081"| ME["حاوية\nmongo-express"]
    App["حاوية\nnode-app"] -->|"service: mongo"| DB["حاوية\nmongo"]
    ME -->|"service: mongo"| DB
    DB --- Vol[("حجم\nmongo-db")]
 
    subgraph net["شبكة Docker الافتراضية"]
        App
        ME
        DB
    end

مهام المعمل

المهمة 1: أنشئ إعدادات التطوير والإنتاج

الهدف: قسّم ملف docker-compose.yml واحد إلى ملف أساسي بالإضافة إلى ملفي تجاوز خاصين بكل بيئة.

الخطوات:

  1. أنشئ بنية مجلد المشروع:

    my-app/
    ├── index.js
    ├── package.json
    ├── Dockerfile
    ├── .dockerignore
    ├── docker-compose.yml
    ├── docker-compose.dev.yml
    └── docker-compose.prod.yml
  2. اكتب docker-compose.yml (الأساسي المشترك):

    version: "3"
    services:
      node-app:
        build: .
        ports:
          - "3000:3000"
  3. اكتب docker-compose.dev.yml:

    version: "3"
    services:
      node-app:
        volumes:
          - ./:/app
          - /app/node_modules
        environment:
          - NODE_ENV=development
        command: npm run dev
  4. اكتب docker-compose.prod.yml:

    version: "3"
    services:
      node-app:
        environment:
          - NODE_ENV=production
        command: npm start
  5. شغّل في وضع التطوير:

    docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
  6. تحقق من التطبيق على http://localhost:3000.

  7. حرّر index.js وتأكد من إعادة التحميل الفوري (تنعكس تغييرات الملف دون إعادة بناء).

  8. أوقف وانتقل إلى الإنتاج:

    docker compose -f docker-compose.yml -f docker-compose.dev.yml down
    docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
  9. حرّر index.js مرة أخرى وتأكد أن التغييرات لا تُلتَقط دون إعادة بناء.

الناتج المتوقع عند البدء:

[+] Running 1/1
 ✔ Container my-app-node-app-1  Started

المهمة 2: اكتب ملف Dockerfile متعدد المراحل

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

الخطوات:

  1. اكتب Dockerfile متعدد المراحل:

    FROM node:14 AS base
    WORKDIR /app
    COPY package*.json ./
     
    FROM base AS development
    RUN npm install
    COPY . .
    CMD ["npm", "run", "dev"]
     
    FROM base AS production
    RUN npm install --only=production
    COPY . .
    CMD ["npm", "start"]
  2. حدّث docker-compose.dev.yml ليستهدف مرحلة التطوير:

    version: "3"
    services:
      node-app:
        build:
          context: .
          target: development
  3. حدّث docker-compose.prod.yml ليستهدف مرحلة الإنتاج:

    version: "3"
    services:
      node-app:
        build:
          context: .
          target: production
  4. ابنِ وشغّل في الإنتاج:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
  5. تحقق أن nodemon غير مثبّتة في الإنتاج:

    docker exec -it <container_name> bash
    ls node_modules | grep nodemon
    # Expected: no output
  6. أوقف، ثم ابنِ وشغّل في التطوير:

    docker compose -f docker-compose.yml -f docker-compose.prod.yml down
    docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
  7. تحقق أن nodemon موجودة:

    docker exec -it <container_name> bash
    ls node_modules | grep nodemon
    # Expected: nodemon

المهمة 3: اربط تطبيق Node.js بـ MongoDB

الهدف: أضف MongoDB كخدمة واربط تطبيق Node.js باستخدام Mongoose.

الخطوات:

  1. ثبّت Mongoose في مشروعك:

    npm install mongoose
  2. حدّث docker-compose.yml لإضافة خدمة MongoDB وحجم مُسمّى:

    version: "3"
    services:
      node-app:
        build: .
        ports:
          - "3000:3000"
     
      mongo:
        image: mongo
        environment:
          - MONGO_INITDB_ROOT_USERNAME=root
          - MONGO_INITDB_ROOT_PASSWORD=example
        volumes:
          - mongo-db:/data/db
     
    volumes:
      mongo-db:
  3. أضف اتصال قاعدة بيانات في index.js:

    const mongoose = require("mongoose");
     
    const URI = "mongodb://root:example@mongo:27017";
     
    mongoose
      .connect(URI)
      .then(() => console.log("Connected to database"))
      .catch((err) => console.log("Failed to connect:", err));
  4. شغّل الخدمات:

    docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
  5. تحقق من الاتصال:

    docker logs <node_app_container> -f

    المتوقع:

    App is up and running on port 3000
    Connected to database
  6. أدرج سجلًا داخل MongoDB:

    docker exec -it <mongo_container> mongosh -u root -p example
    use testdb
    db.books.insertOne({ title: "Docker Lab 10" })
    db.books.find()
  7. اختبر إدامة البيانات:

    docker compose down
    docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
    docker exec -it <mongo_container> mongosh -u root -p example
    use testdb
    db.books.find()

    المتوقع: لا يزال السجل موجودًا بعد إعادة التشغيل.


المهمة 4: أضف واجهة إدارة Mongo Express

الهدف: أضف Mongo Express كخدمة ثالثة وتصفّح قاعدة البيانات عبر واجهة ويب.

الخطوات:

  1. حدّث docker-compose.yml لإضافة Mongo Express:

    version: "3"
    services:
      node-app:
        build: .
        ports:
          - "3000:3000"
     
      mongo:
        image: mongo
        environment:
          - MONGO_INITDB_ROOT_USERNAME=root
          - MONGO_INITDB_ROOT_PASSWORD=example
        volumes:
          - mongo-db:/data/db
     
      mongo-express:
        image: mongo-express
        ports:
          - "8081:8081"
        environment:
          - ME_CONFIG_MONGODB_ADMINUSERNAME=root
          - ME_CONFIG_MONGODB_ADMINPASSWORD=example
          - ME_CONFIG_MONGODB_SERVER=mongo
        depends_on:
          - mongo
     
    volumes:
      mongo-db:
  2. شغّل كل الخدمات:

    docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
  3. تحقق أن ثلاث حاويات تعمل:

    docker ps

    المتوقع (3 صفوف):

    CONTAINER ID   IMAGE           STATUS
    xxxxxxxxxxxx   node-app        Up
    xxxxxxxxxxxx   mongo           Up
    xxxxxxxxxxxx   mongo-express   Up
  4. افتح http://localhost:8081 في المتصفح.

  5. انتقل إلى testdbbooks وتأكد أن السجل من المهمة 3 مرئي.

  6. استخدم واجهة Mongo Express لإدراج مستند جديد.

  7. تحقق أن المستند الجديد يظهر عندما تشغّل db.books.find() في صدفة mongo.


الملخص

المفهومالفكرة الأساسية
فصل البيئاتاستخدم docker-compose.yml مشترك + ملفات تجاوز خاصة بكل بيئة عبر -f
دمج التجاوزاتيدمج Docker Compose الملفات - تفوز القيم في ملف التجاوز
ملف Dockerfile متعدد المراحلملف Dockerfile واحد، عدة كتل FROM ... AS <stage>؛ يختار target: المرحلة
تبعيات الإنتاجnpm install --only=production يتخطى أدوات التطوير
شبكات الحاوياتينشئ Docker Compose شبكة افتراضية؛ استخدم أسماء الخدمات كأسماء مضيف
docker inspectيعرض عنوان IP الحاوية، وعضوية الشبكة، والأحجام، ومتغيرات البيئة
الأحجام المُسمّاةمُعلَنة في كتلة volumes:؛ تُديم البيانات عبر عمليات إعادة تشغيل الحاوية
docker compose down -vيحذف الحاويات والأحجام معًا - استخدمه بحذر
Mongo Expressواجهة ويب لـ MongoDB؛ تتصل بحاوية mongo عبر اسم الخدمة