المعمل 10: Docker في بيئات الإنتاج - البنى متعددة المراحل وقواعد البيانات
نظرة عامة
يغطي هذا المعمل مواضيع 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 (التطوير مقابل الإنتاج)
يتطلب تشغيل نفس التطبيق في التطوير والإنتاج إعدادات مختلفة:
| الإعداد | التطوير | الإنتاج |
|---|---|---|
| أمر بدء تشغيل Node | npm run dev (nodemon) | npm start |
NODE_ENV | development | production |
| ربط مسار الحجم (إعادة التحميل الفوري) | نعم | لا |
| تثبيت تبعيات التطوير | نعم | لا (--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 devdocker-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=developmentdocker-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"] --> D3. 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_ADMINUSERNAME | root | اسم مستخدم مسؤول MongoDB |
ME_CONFIG_MONGODB_ADMINPASSWORD | example | كلمة مرور مسؤول MongoDB |
ME_CONFIG_MONGODB_SERVER | mongo | اسم خدمة حاوية 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 واحد إلى ملف أساسي بالإضافة إلى ملفي تجاوز خاصين بكل بيئة.
الخطوات:
-
أنشئ بنية مجلد المشروع:
my-app/ ├── index.js ├── package.json ├── Dockerfile ├── .dockerignore ├── 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 -
شغّل في وضع التطوير:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build -
تحقق من التطبيق على
http://localhost:3000. -
حرّر
index.jsوتأكد من إعادة التحميل الفوري (تنعكس تغييرات الملف دون إعادة بناء). -
أوقف وانتقل إلى الإنتاج:
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 -
حرّر
index.jsمرة أخرى وتأكد أن التغييرات لا تُلتَقط دون إعادة بناء.
الناتج المتوقع عند البدء:
[+] Running 1/1
✔ Container my-app-node-app-1 Startedالمهمة 2: اكتب ملف Dockerfile متعدد المراحل
الهدف: استبدل ملف Dockerfile ذا بيئة واحدة بملف Dockerfile متعدد المراحل يستهدف مرحلتي التطوير والإنتاج بشكل منفصل.
الخطوات:
-
اكتب
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"] -
حدّث
docker-compose.dev.ymlليستهدف مرحلة التطوير:version: "3" services: node-app: build: context: . target: development -
حدّث
docker-compose.prod.ymlليستهدف مرحلة الإنتاج:version: "3" services: node-app: build: context: . target: production -
ابنِ وشغّل في الإنتاج:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build -
تحقق أن
nodemonغير مثبّتة في الإنتاج:docker exec -it <container_name> bash ls node_modules | grep nodemon # Expected: no output -
أوقف، ثم ابنِ وشغّل في التطوير:
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 -
تحقق أن
nodemonموجودة:docker exec -it <container_name> bash ls node_modules | grep nodemon # Expected: nodemon
المهمة 3: اربط تطبيق Node.js بـ MongoDB
الهدف: أضف MongoDB كخدمة واربط تطبيق Node.js باستخدام Mongoose.
الخطوات:
-
ثبّت Mongoose في مشروعك:
npm install mongoose -
حدّث
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: -
أضف اتصال قاعدة بيانات في
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)); -
شغّل الخدمات:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build -
تحقق من الاتصال:
docker logs <node_app_container> -fالمتوقع:
App is up and running on port 3000 Connected to database -
أدرج سجلًا داخل MongoDB:
docker exec -it <mongo_container> mongosh -u root -p example use testdb db.books.insertOne({ title: "Docker Lab 10" }) db.books.find() -
اختبر إدامة البيانات:
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 كخدمة ثالثة وتصفّح قاعدة البيانات عبر واجهة ويب.
الخطوات:
-
حدّث
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: -
شغّل كل الخدمات:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build -
تحقق أن ثلاث حاويات تعمل:
docker psالمتوقع (3 صفوف):
CONTAINER ID IMAGE STATUS xxxxxxxxxxxx node-app Up xxxxxxxxxxxx mongo Up xxxxxxxxxxxx mongo-express Up -
افتح
http://localhost:8081في المتصفح. -
انتقل إلى
testdb←booksوتأكد أن السجل من المهمة 3 مرئي. -
استخدم واجهة Mongo Express لإدراج مستند جديد.
-
تحقق أن المستند الجديد يظهر عندما تشغّل
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 عبر اسم الخدمة |