المعمل 09: Docker المتقدم - التحسين، الأحجام (Volumes)، Compose، ومتغيرات البيئة
نظرة عامة
يغطي هذا المعمل مفاهيم Docker المتقدمة موضّحة عبر تطبيق Node.js/Express. تشمل المواضيع: تحسين ملف Dockerfile، وإعادة التحميل الفوري (Hot Reload) أثناء التطوير باستخدام ربط الأحجام (Volume Bind Mounts)، وأحجام Docker (Docker Volumes) (ربط المسارات مقابل الأحجام المجهولة)، وDocker Compose لتنسيق الحاويات المتعددة، وإدارة متغيرات البيئة.
أهداف التعلّم
بنهاية هذا المعمل ستكون قادرًا على:
- تحسين صور Docker باستخدام
.dockerignoreوتقنيات تخزين الطبقات المؤقت - استخدام ربط المسارات (Bind Mounts) لتفعيل إعادة التحميل الفوري أثناء التطوير
- التمييز بين ربط المسارات (Bind Mounts) والأحجام المجهولة (Anonymous Volumes) ومعرفة متى تُستخدم كل منها
- تعريف تطبيقات متعددة الحاويات باستخدام Docker Compose
- إدارة متغيرات البيئة في حاويات Docker وملفات Compose
- استخدام
docker execلفتح طرفيات تفاعلية داخل الحاويات قيد التشغيل
المفاهيم الأساسية
تحسين الصورة (Image Optimization)
ملف .dockerignore
تمامًا كما يخبر .gitignore نظام Git بالملفات التي يجب تجاهلها، يخبر .dockerignore نظام Docker بالملفات التي يجب استبعادها من سياق البناء (Build Context). هذا يقلل حجم ما يُرسَل إلى خدمة Docker (Daemon) ويمنع تسرب الملفات الحساسة إلى الصور.
الملفات الشائع استبعادها ولماذا:
| الملف / المجلد | سبب الاستبعاد |
|---|---|
node_modules/ | يُعاد تثبيته داخل الحاوية بواسطة npm install. نسخه يهدر الوقت وقد يتضمن ملفات ثنائية (Binaries) لمنصة خاطئة. |
Dockerfile | يُستخدَم بالفعل لإنشاء الحاوية - لا داعي لوجوده بداخلها. |
.env | يحتوي على أسرار يجب ألا تُخبَز داخل الصورة. |
.git/ | بيانات وصفية لنظام التحكم بالإصدارات - غير مطلوبة وقت التشغيل. |
*.log | ملفات السجل مؤقتة وكبيرة الحجم. |
مثال على .dockerignore:
node_modules
Dockerfile
.dockerignore
.env
.git
*.log
dist
build
coverage
.DS_Storeبدون
.dockerignore، تنسخ تعليمةCOPY . .في ملف Dockerfile الخاص بك مجلدnode_modulesبالكامل (قد يصل إلى مئات الميجابايت) داخل الصورة - رغم أنnpm installسيعيد إنشاءه على أي حال.
تخزين الطبقات المؤقت (Layer Caching)
يبني Docker الصور في طبقات. كل تعليمة في Dockerfile تُنشئ طبقة جديدة. يخزّن Docker الطبقات مؤقتًا (Cache) ويعيد استخدامها إذا لم تتغير التعليمة وكل الطبقات السابقة لها.
الاستراتيجية: انسخ ملفات التبعيات قبل كود المصدر.
# Bad: copying everything first invalidates cache on every code change
FROM node:20-alpine
WORKDIR /app
COPY . . # ← any file change busts ALL subsequent layers
RUN npm install
CMD ["node", "server.js"]# Good: copy package files first so npm install is cached
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./ # ← only changes when deps change
RUN npm install # ← cached unless deps change
COPY . . # ← source code changes don't bust npm install
CMD ["node", "server.js"]قواعد تخزين الطبقات المؤقت:
- تُخزَّن الطبقات مؤقتًا من الأعلى إلى الأسفل
- بمجرد إبطال طبقة واحدة، يُعاد بناء كل الطبقات الموجودة أسفلها
- ضع الخطوات البطيئة، نادرة التغيير (مثل
RUN npm install) بالقرب من الأعلى - ضع الخطوات متكررة التغيير (مثل
COPY . .) بالقرب من الأسفل
flowchart TD
A["FROM node:20-alpine\n(نادرًا ما تتغير)"] --> B["WORKDIR /app\n(لا تتغير أبدًا)"]
B --> C["COPY package.json\n(تتغير عند تغيّر التبعيات)"]
C --> D["RUN npm install\n(مخزّنة مؤقتًا إذا لم تتغير C)"]
D --> E["COPY . .\n(تتغير كثيرًا)"]
E --> F["CMD\n(نادرًا ما تتغير)"]
style D fill:#90EE90,stroke:#333
style C fill:#FFD700,stroke:#333
style E fill:#FF7F7F,stroke:#333أحجام Docker (Volumes) وإعادة التحميل الفوري (Hot Reload)
مشكلة الحاويات أثناء التطوير
عندما تشغّل حاوية من صورة، تحصل الحاوية على لقطة (Snapshot) من كودك في وقت البناء. إذا حرّرت ملفًا على جهازك المضيف، لن تلاحظ الحاوية التغيير - سيتوجب عليك إعادة بناء الصورة في كل مرة. هذا غير عملي أثناء التطوير النشط.
فحص حاوية قيد التشغيل
قبل العمل مع الأحجام (Volumes)، تحتاج معرفة كيفية فحص ما يحدث داخل الحاوية:
# Open an interactive bash terminal inside a running container
docker exec -it <container_name> bash
# Inside the container: list files in current directory
ls
# Inside the container: print working directory
pwd
# Inside the container: display contents of a file
cat index.js
# View container logs (useful to verify nodemon is running)
docker logs <container_name>العلامة -it تعني طرفية تفاعلية (Interactive Terminal) - تفتح جلسة صدفة (Shell) حية داخل الحاوية.
جعل إعادة التحميل الفوري تعمل: nodemon
افتراضيًا، لا يعيد تطبيق Node.js الذي يُشغَّل بـ node index.js تشغيل نفسه عند تغيّر الملفات. استخدم nodemon لمراقبة التغييرات وإعادة تشغيل الخادم تلقائيًا:
// package.json
{
"scripts": {
"start": "node index.js",
"start:dev": "nodemon index.js"
},
"devDependencies": {
"nodemon": "^3.0.0"
}
}حدّث تعليمة CMD في Dockerfile لاستخدام سكربت التطوير:
FROM node:18-alpine
WORKDIR /app
COPY package.json .
RUN npm install
COPY . .
CMD ["npm", "run", "start:dev"]ربط المسارات (Bind Mounts)
ربط المسار (Bind Mount) يربط مجلدًا على جهازك المضيف مباشرة داخل الحاوية. تكون التغييرات على المضيف مرئية فورًا داخل الحاوية - لا حاجة لإعادة بناء الصورة.
# -v flag: bind mount current directory into /app inside the container
# $(pwd) resolves to your absolute path (required — relative paths do NOT work)
docker run -d \
--name express-node-app-container \
-p 4000:4000 \
-v $(pwd):/app \
my-express-appعلى نظام Windows، استخدم %cd% بدلًا من $(pwd).
معمارية إعادة التحميل الفوري:
graph LR
subgraph Host["المضيف"]
SRC["كود المصدر\n$(pwd)"]
end
subgraph Container["الحاوية"]
APP["/app\n(مربوط)"]
NODEMON["nodemon\n(مراقب الملفات)"]
SERVER["خادم Node.js\n:4000"]
end
SRC <-->|"مزامنة فورية"| APP
APP --> NODEMON
NODEMON -->|"يعيد التشغيل عند التغيير"| SERVERربط مسار للقراءة فقط (موصى به للتطوير)
ربط المسار الافتراضي ثنائي الاتجاه: تظهر التغييرات على المضيف في الحاوية، والتغييرات داخل الحاوية تظهر أيضًا على المضيف. هذا يمثّل خطرًا أمنيًا - يمكن لعملية داخل الحاوية أن تحذف ملفاتك.
أضف :ro (للقراءة فقط) لمنع الحاوية من الكتابة مرة أخرى إلى المضيف:
docker run -d \
--name express-node-app-container \
-p 4000:4000 \
-v $(pwd):/app:ro \
my-express-appالآن إذا حاول شيء داخل الحاوية إنشاء أو حذف ملفات:
# Inside the container:
touch newfile.js
# Error: touch: newfile.js: Read-only file systemالأحجام المجهولة: حماية node_modules
مشكلة ربط المسار للقراءة فقط: إذا حذفت node_modules على جهازك المضيف (أو إذا لم تكن موجودة هناك)، تفقدها الحاوية أيضًا - مما يُسقط التطبيق. يحدث هذا لأن ربط المسار يعكس كلا المجلدين.
الحل: الحجم المجهول (Anonymous Volume)
علامة -v ثانية تحمل فقط مسار الحاوية (بدون مسار المضيف) تُنشئ حجمًا مجهولًا. يدير Docker هذا الحجم بشكل مستقل عن ربط المسار. يحمي ذلك المجلد من الاستبدال:
docker run -d \
--name express-node-app-container \
-p 4000:4000 \
-v $(pwd):/app:ro \
-v /app/node_modules \
my-express-app-v /app/node_modules تخبر Docker: "احتفظ بـ /app/node_modules في حجمه الخاص - لا تدع ربط مسار المضيف يمسّه."
ملخص أنواع الأحجام:
| النوع | الصياغة | الوصف | حالة الاستخدام |
|---|---|---|---|
| ربط مسار (Bind Mount) | -v /host/path:/container/path | مزامنة ثنائية الاتجاه بين المضيف والحاوية | إعادة التحميل الفوري أثناء التطوير |
| ربط مسار للقراءة فقط | -v /host/path:/container/path:ro | باتجاه واحد: تغييرات المضيف تصل للحاوية، وليس العكس | مزامنة كود المصدر (موصى به) |
| حجم مجهول (Anonymous Volume) | -v /container/path | يديره Docker، بلا مسار مضيف | حماية node_modules، بيانات مؤقتة |
| حجم مُسمّى (Named Volume) | -v myvolume:/container/path | يديره Docker باسم محدد | استمرارية بيانات قاعدة البيانات |
# List all volumes
docker volume ls
# Inspect a volume
docker volume inspect <volume_name>
# Remove a volume
docker volume rm <volume_name>
# Create a named volume explicitly
docker volume create mydataربط مسار انتقائي: مجلد المصدر فقط
نهج أنظف هو ربط مجلد يحتوي على كود مصدر تطبيقك فقط (مثل ./src)، وليس جذر المشروع بأكمله. هذا يُبقي node_modules خارج الربط بشكل طبيعي:
docker run -d \
--name express-node-app-container \
-p 4000:4000 \
-v $(pwd)/src:/app/src:ro \
my-express-appهذا هو الحل الأنظف: لا حاجة لحجم مجهول، ولا خطر من استبدال node_modules.
Docker Compose
Docker Compose أداة تأتي مع Docker (لكن لها رقم إصدار خاص بها) تتيح لك تعريف وتشغيل الحاويات باستخدام ملف إعدادات YAML. بدلًا من تشغيل أوامر docker build وdocker run الطويلة في كل مرة، تكتب الإعدادات مرة واحدة وتستخدم أوامر بسيطة.
تحقق من الإصدارات:
docker version
docker compose versionلماذا Compose؟
بدون Compose، يتطلب تشغيل تطبيق موزّع في حاويات:
# Step 1: build
docker build -t my-express-app .
# Step 2: run (long command, error-prone to retype every time)
docker run -d \
--name express-node-app-container \
-p 4000:4000 \
-v $(pwd)/src:/app/src:ro \
-v /app/node_modules \
my-express-appمع Compose، يصبح هذا خطوتين - مرة واحدة فقط:
docker compose up -dيصبح Compose أكثر أهمية عندما يكون لديك حاويات متعددة (تطبيق + قاعدة بيانات + تخزين مؤقت + وكيل عكسي Reverse Proxy) تحتاج للتواصل.
بنية docker-compose.yml
يعيش ملف docker-compose.yml في جذر المشروع بجانب Dockerfile.
version: "3"
services:
node-app:
container_name: express-node-app-container
build: .
ports:
- "4000:4000"
volumes:
- ./src:/app/src:ro
- /app/node_modulesالحقول الأساسية:
| الحقل | ما يعادله في docker run | الوصف |
|---|---|---|
container_name | --name | اسم الحاوية |
build: . | (يشغّل docker build أولًا) | مسار Dockerfile؛ . = المجلد الحالي |
ports | -p host:container | ربط المنافذ |
volumes | -v | ربط الأحجام (ربط مسار أو مجهول) |
environment | --env | متغيرات بيئة مباشرة (Inline) |
env_file | --env-file | تحميل المتغيرات من ملف |
depends_on | (لا يوجد ما يعادله) | تبعية ترتيب البدء |
ملاحظة حول الأحجام في Compose: على عكس علامة -v في docker run (التي تتطلب مسارات مطلقة)، يدعم Compose المسارات النسبية. ./src تعمل بشكل جيد في Compose وتُحل تلقائيًا إلى المجلد الحالي.
ملاحظة حول تباعد YAML (Indentation): صياغة YAML حساسة للتباعد. يجب أن يكون كل مفتاح فرعي متسق التباعد (مسافتان هو المعيار). التباعد غير الصحيح يسبب أخطاء تحليل (Parsing Errors).
مثال معماري (حاويات متعددة)
graph TD
subgraph Docker Compose Application["تطبيق Docker Compose"]
WEB["خدمة node-app\n(Node.js)\nمنفذ 4000"]
DB["خدمة db\n(PostgreSQL)\nمنفذ 5432"]
CACHE["خدمة cache\n(Redis)\nمنفذ 6379"]
end
subgraph Volumes["الأحجام"]
PGDATA["pgdata (حجم مُسمّى)"]
end
WEB -->|"depends_on"| DB
WEB -->|"depends_on"| CACHE
DB --- PGDATAكل الخدمات في نفس ملف docker-compose.yml يمكنها الوصول لبعضها باستخدام اسم الخدمة كاسم مضيف (Hostname). ينشئ Docker Compose شبكة داخلية تلقائيًا.
أوامر Docker Compose الأساسية
# Start all services — builds image if needed, then runs containers
docker compose up
# Start in detached mode (background, terminal freed)
docker compose up -d
# Start and force rebuild images
docker compose up -d --build
# Stop and remove all containers defined in the file
docker compose down
# Stop and remove containers AND volumes
docker compose down -v
# View logs for all services
docker compose logs
# Follow logs for a specific service
docker compose logs -f node-app
# List running services
docker compose ps
# Open a shell inside a running service
docker compose exec node-app bash
# Rebuild a specific service without cache
docker compose build --no-cache node-app
# Show available commands
docker compose --helpكيف يسمّي Compose الصورة التي يبنيها:
إذا كان اسم المجلد الذي يحتوي على docker-compose.yml هو my-express-app واسم الخدمة node-app، ستُسمَّى الصورة my-express-app_node-app. يشتق Compose اسم الصورة من مجلد المشروع واسم الخدمة تلقائيًا.
مثال كامل: تطبيق + قاعدة بيانات
version: "3"
services:
node-app:
container_name: express-node-app-container
build: .
ports:
- "4000:4000"
volumes:
- ./src:/app/src:ro
- /app/node_modules
env_file:
- .env
depends_on:
- db
db:
image: postgres:15-alpine
ports:
- "5432:5432"
environment:
POSTGRES_USER: devuser
POSTGRES_PASSWORD: devpassword
POSTGRES_DB: myappdb
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:متغيرات البيئة (Environment Variables)
تتيح لك متغيرات البيئة ضبط سلوك التطبيق دون كتابة القيم بشكل ثابت داخل الصورة. هذا ضروري لـ:
- التبديل بين بيئات التطوير والتجهيز (Staging) والإنتاج
- إبقاء بيانات اعتماد قاعدة البيانات والأسرار خارج قاعدة الكود
- جعل الحاويات قابلة للنقل والتهيئة وقت التشغيل
الطريقة 1: ENV داخل Dockerfile
FROM node:18-alpine
WORKDIR /app
ENV PORT=4000
ENV NODE_ENV=production
COPY package.json .
RUN npm install
COPY . .
CMD ["npm", "start"]يصل التطبيق إليها عبر process.env.PORT.
الطريقة 2: علامة --env مع docker run
docker run -d \
--name my-node-app-container \
-p 4000:4000 \
--env PORT=4000 \
--env NODE_ENV=development \
my-express-appالصيغة المختصرة: -e PORT=4000
تحقق من متغيرات البيئة داخل حاوية قيد التشغيل:
docker exec -it my-node-app-container bash
# List all environment variables
printenv
# Get a specific variable
printenv PORT
# Output: 4000
printenv NODE_ENV
# Output: developmentالطريقة 3: علامة --env-file
لعدة متغيرات، خزّنها في ملف .env:
# .env
PORT=4000
NODE_ENV=development
DB_HOST=localhost
DB_PASSWORD=secretمرّر الملف بأكمله:
docker run -d \
--name my-node-app-container \
-p 4000:4000 \
--env-file .env \
my-express-appالطريقة 4: environment داخل docker-compose.yml (مباشرة - Inline)
services:
node-app:
build: .
ports:
- "4000:4000"
environment:
- PORT=4000
- NODE_ENV=productionالطريقة 5: env_file داخل docker-compose.yml (موصى بها)
services:
node-app:
build: .
ports:
- "4000:4000"
env_file:
- .envهذا هو النهج المُوصى به للأسرار - يُحمَّل ملف .env وقت التشغيل لكنه لا يُخبَز داخل الصورة.
ملف .env:
PORT=4000
NODE_ENV=development
DB_HOST=localhost
DB_PASSWORD=secretأمان .env
أضف .env دائمًا إلى كل من .gitignore و.dockerignore:
# .gitignore and .dockerignore
.env
.env.local
.env.productionوفّر ملف .env.example لزملاء الفريق:
PORT=4000
NODE_ENV=development
DB_HOST=localhost
DB_PASSWORD=your-password-hereاستبدال المتغيرات داخل docker-compose.yml
services:
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER:-defaultuser} # default value if variable not setيقرأ Docker Compose ملف .env تلقائيًا في نفس المجلد - لا حاجة لعلامة --env-file.
الوصول لمتغيرات البيئة في Node.js
const port = process.env.PORT || 4000;
const dbUrl = process.env.DATABASE_URL;
const nodeEnv = process.env.NODE_ENV;
app.listen(port, () => {
console.log(`Server running on port ${port} in ${nodeEnv} mode`);
});أمثلة عملية
إعداد تطوير كامل باستخدام Docker Compose
بنية المشروع:
my-app/
├── .dockerignore
├── .env
├── .env.example
├── .gitignore
├── docker-compose.yml
├── docker-compose.prod.yml
├── Dockerfile
├── Dockerfile.dev
├── package.json
├── package-lock.json
└── src/
└── server.jsDockerfile (الإنتاج):
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
EXPOSE 3000
CMD ["node", "src/server.js"]Dockerfile.dev (التطوير):
FROM node:20-alpine
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
EXPOSE 3000
CMD ["npx", "nodemon", "src/server.js"]docker-compose.yml (التطوير):
version: "3.9"
services:
app:
build:
context: .
dockerfile: Dockerfile.dev
ports:
- "3000:3000"
volumes:
- .:/app
- /app/node_modules
env_file:
- .env
depends_on:
- db
db:
image: postgres:15-alpine
ports:
- "5432:5432"
env_file:
- .env
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:.dockerignore:
node_modules
.env
.git
.gitignore
*.log
npm-debug.log*
.DS_Store
coverage
dist.env:
NODE_ENV=development
PORT=3000
POSTGRES_USER=devuser
POSTGRES_PASSWORD=devpassword
POSTGRES_DB=myappdb
DATABASE_URL=postgres://devuser:devpassword@db:5432/myappdbربط الأحجام للتطوير
# Start dev environment with hot reload
docker compose up -d
# Edit any file in src/ → nodemon detects change → server restarts
# No rebuild needed
# When you add a new package:
docker compose down
# add package to package.json
docker compose up -d --build # rebuild to run npm installاستخدام .env مع Docker
# Docker Compose automatically reads .env in the same directory
docker compose up -d
# Override with a different env file
docker compose --env-file .env.staging up -d
# Pass a single variable override
DATABASE_URL=postgres://prod@prodhost/db docker compose up -dمهام المعمل
المهمة 1: حسّن ملف Dockerfile باستخدام .dockerignore وتخزين الطبقات المؤقت
بالنظر إلى ملف Dockerfile غير الفعّال هذا:
FROM node:18-alpine
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "index.js"]الخطوات:
-
أعد كتابة ملف Dockerfile بحيث ينسخ
package.jsonأولًا، ثم يشغّلnpm install، ثم ينسخ الباقي:FROM node:18-alpine WORKDIR /app COPY package.json . RUN npm install COPY . . CMD ["node", "index.js"] -
أنشئ
.dockerignore:node_modules Dockerfile .dockerignore .env -
ابنِ الصورة:
docker build -t my-express-app . -
أعد البناء مرة أخرى دون تغيير أي شيء - لاحظ أن كل الخطوات تُظهر
CACHED. -
غيّر سطرًا في
index.js، وأعد البناء. تأكد أنnpm installلا تزالCACHED(فقط خطوةCOPY . .تُعاد). -
غيّر
package.json(أضف مسافة)، وأعد البناء. تأكد أنnpm installتُعاد الآن (تم إبطال التخزين المؤقت). -
شغّل الحاوية وتحقق أنها تعمل:
docker run -d --name express-node-app-container -p 4000:4000 my-express-app curl http://localhost:4000 -
افتح صدفة (Shell) بداخلها وتحقق أن
Dockerfileغير موجود:docker exec -it express-node-app-container bash ls # Dockerfile should NOT appear
المهمة 2: إعادة التحميل الفوري باستخدام ربط الأحجام
الهدف: حرّر ملفات المصدر وشاهد التغييرات دون إعادة بناء الصورة.
الخطوات:
-
أضف
nodemonإلى مشروعك وحدّثpackage.json:{ "scripts": { "start": "node index.js", "start:dev": "nodemon index.js" }, "devDependencies": { "nodemon": "^3.0.0" } } -
حدّث تعليمة CMD في Dockerfile:
CMD ["npm", "run", "start:dev"] -
أعد بناء الصورة:
docker build -t my-express-app . -
أوقف واحذف الحاوية الموجودة:
docker stop express-node-app-container docker rm express-node-app-container -
شغّل مع ربط مسار (مصدر فقط) وحجم مجهول لـ
node_modules:docker run -d \ --name express-node-app-container \ -p 4000:4000 \ -v $(pwd):/app:ro \ -v /app/node_modules \ my-express-app -
تحقق أن nodemon يعمل:
docker logs express-node-app-container # Expected: [nodemon] watching path(s): *.* -
افتح
index.jsوغيّر نص الاستجابة. حدّثhttp://localhost:4000- يجب أن يظهر التغيير فورًا دون إعادة بناء. -
حاول الكتابة داخل الحاوية للتحقق من حماية القراءة فقط:
docker exec -it express-node-app-container bash touch /app/newfile.js # Expected: Read-only file system error -
احذف
node_modulesمن جهازك المضيف. تحقق أن الحاوية لا تزال تعمل (الحجم المجهول يحميها):rm -rf node_modules curl http://localhost:4000 # App should still respond
المهمة 3: استخدم Docker Compose
الهدف: استبدل docker build + docker run بملف docker-compose.yml.
الخطوات:
-
أوقف واحذف الحاويات:
docker stop express-node-app-container docker rm express-node-app-container -
أنشئ
docker-compose.ymlفي جذر المشروع:version: "3" services: node-app: container_name: express-node-app-container build: . ports: - "4000:4000" volumes: - ./:/app:ro - /app/node_modules -
ابدأ باستخدام Compose (يبني الصورة تلقائيًا):
docker compose up -d -
تحقق أن الحاوية تعمل:
docker ps # Expected: express-node-app-container listed -
تحقق أن التطبيق يستجيب:
curl http://localhost:4000 -
أوقف الحاوية باستخدام Compose:
docker compose down -
تأكد أن الحاوية اختفت:
docker ps # express-node-app-container should NOT be listed
المهمة 4: متغيرات البيئة من .env عبر Compose
الهدف: استخرج كل الإعدادات إلى ملف .env خارجي.
الخطوات:
-
أنشئ
.envفي جذر المشروع:PORT=4000 NODE_ENV=development DB_HOST=localhost DB_PASSWORD=secret123 -
أضف
.envإلى.dockerignoreو.gitignore. -
حدّث
docker-compose.ymlلاستخدامenv_file:version: "3" services: node-app: container_name: express-node-app-container build: . ports: - "4000:4000" volumes: - ./:/app:ro - /app/node_modules env_file: - .env -
ابدأ الخدمات:
docker compose up -d -
تحقق من متغيرات البيئة داخل الحاوية:
docker exec -it express-node-app-container bash printenv PORT # Expected: 4000 printenv NODE_ENV # Expected: development printenv DB_PASSWORD # Expected: secret123 -
أوقف كل شيء:
docker compose down
الملخص
| المفهوم | الفكرة الأساسية |
|---|---|
.dockerignore | استبعد node_modules وDockerfile و.env لتسريع البناء وإبقاء الأسرار خارج الصورة |
| تخزين الطبقات المؤقت | انسخ package.json قبل ملفات المصدر حتى تُخزَّن npm install مؤقتًا بين تغييرات الكود |
docker exec -it | تفتح طرفية تفاعلية داخل حاوية قيد التشغيل |
docker logs | تعرض الناتج (stdout) من حاوية قيد التشغيل |
ربط المسار (:) | مزامنة ثنائية الاتجاه بين مسار المضيف ومسار الحاوية |
المُعدِّل :ro | يجعل ربط المسار للقراءة فقط - لا يمكن للحاوية الكتابة مرة أخرى إلى المضيف |
| الحجم المجهول | علامة -v /container/path ثانية (بلا مسار مضيف) تحمي مجلدًا من ربط المسار |
docker volume ls | تسرد كل الأحجام التي يديرها Docker |
docker compose up -d | تبني الصورة وتبدأ كل الخدمات من docker-compose.yml |
docker compose down | توقف وتحذف كل الخدمات |
| المسارات النسبية في Compose | على عكس docker run، يقبل Compose ./src بدلًا من المسارات المطلقة |
environment: في Compose | عرّف متغيرات البيئة مباشرة داخل docker-compose.yml |
env_file: في Compose | حمّل متغيرات البيئة من ملف .env (موصى به للأسرار) |
printenv (داخل الحاوية) | تسرد كل متغيرات البيئة المضبوطة في الحاوية |
أوامر مرجعية سريعة
# --- Containers ---
docker build -t myapp . # Build image from Dockerfile in current directory
docker run -d --name myapp -p 4000:4000 myapp # Run container in background
docker ps # List running containers
docker stop myapp # Stop a container
docker rm myapp # Remove a stopped container
docker rm -f myapp # Force stop and remove
# --- Inspect containers ---
docker exec -it myapp bash # Open interactive shell in container
docker logs myapp # View container logs
docker logs -f myapp # Follow (tail) container logs
# --- Volumes ---
docker volume ls # List all volumes
docker volume inspect <name> # Show volume details
docker volume rm <name> # Remove a volume
# --- Bind mounts (development) ---
docker run -v $(pwd):/app myapp # Two-way bind mount
docker run -v $(pwd):/app:ro myapp # Read-only bind mount
docker run -v $(pwd):/app:ro -v /app/node_modules myapp # With anonymous volume
# --- Docker Compose ---
docker compose up -d # Start all services (detached)
docker compose up -d --build # Force rebuild before starting
docker compose down # Stop and remove all services
docker compose down -v # Also remove volumes
docker compose logs -f node-app # Follow logs for a service
docker compose exec node-app bash # Shell into a running service
docker compose ps # List running services
docker compose --help # Show available commands