Logo

المعمل 08: مقدمة في Docker - الحاويات والصور

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

المعمل 08: مقدمة في Docker - الحاويات والصور

لماذا وُجد Docker، وكتابة Dockerfile، وبناء الحاويات وتشغيلها

نظرة عامة

يقدّم هذا المعمل Docker كتقنية حوسبة بالحاويات (Containerization). ستتعلم لماذا أُنشئ Docker، وما المشكلة التي يحلها، وكيفية كتابة ملف Dockerfile، وكيفية بناء وتشغيل صور Docker (Images) والحاويات (Containers). التطبيق التوضيحي هو خادم Node.js/Express بسيط يُستخدَم طوال المعمل لجعل كل مفهوم عمليًا منذ اليوم الأول.


الأهداف

  • فهم مشكلة اتساق النشر والبيئة (Environment Consistency) التي يحلها Docker
  • تثبيت وإعداد Docker Desktop على جهازك المحلي
  • بناء تطبيق Node.js/Express بسيط لاستخدامه كهدف لتطبيق Docker
  • كتابة ملف Dockerfile بكل التعليمات المطلوبة
  • بناء صورة Docker من ملف Dockerfile
  • تشغيل حاويات Docker وفحصها وإيقافها وحذفها
  • فهم الفرق بين Dockerfile، والصورة (Image)، والحاوية (Container)
  • استخدام Docker Hub لإيجاد الصور الأساسية الرسمية

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

  • جهاز حاسوب يعمل بنظام Windows أو macOS أو Linux
  • إلمام أساسي بالطرفية (Terminal) / سطر الأوامر
  • تثبيت Node.js LTS (لتشغيل التطبيق التوضيحي قبل تحويله إلى Docker)
  • تثبيت Docker Desktop (انظر المهمة 1)
  • Visual Studio Code (موصى به، مع إضافة Docker)

خلفية نظرية

المشكلة التي يحلها Docker

عندما ينضم مطور جديد إلى مشروع أو عندما تحتاج إلى نشر تطبيق على خادم جديد، يتطلب سير العمل التقليدي تثبيت كل تبعية يحتاجها التطبيق يدويًا. تخيل مشروعًا بالمكدس التالي: PHP مع Laravel، وPostgreSQL مع إضافة PostGIS للبيانات المكانية، وRedis للتخزين المؤقت (Caching)، وAngular للواجهة الأمامية. لتشغيل هذا محليًا، يجب على كل مطور تثبيت نسخ متوافقة من كل مكوّن. تعارضات الإصدارات - مثلًا، إصدار Laravel يتطلب إصدار PHP معينًا، أو PostGIS يتطلب إصدار Postgres معينًا - غالبًا ما تنتج أخطاء تثبيت قد تستغرق حلها ساعات أو أيامًا.

تتكرر نفس المشكلة في كل مرة ينتقل فيها التطبيق إلى بيئة جديدة: التطوير المحلي، خادم الاختبار، خادم التجهيز (Staging)، والإنتاج. تتطلب كل بيئة نفس خطوات الإعداد، وكل خطوة نقطة فشل محتملة.

graph LR
    A[جهاز المطور] -->|تثبيت يدوي لكل التبعيات| B[يعمل على جهازي]
    C[خادم الاختبار] -->|تثبيت يدوي مرة أخرى| D[تعارض في الإصدارات!]
    E[خادم الإنتاج] -->|تثبيت يدوي مرة أخرى| F[تعارضات أكثر!]

ما هو Docker؟

Docker أداة تتيح لك تغليف تطبيقك مع كل ما يحتاجه للعمل - بيئة التشغيل، والتبعيات، والإعدادات - في وحدة قابلة للنقل تُسمى حاوية (Container). يمكنك بعد ذلك نقل تلك الحاوية من بيئة إلى أخرى بأمر واحد فقط، دون إعادة تثبيت أي شيء.

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

graph LR
    A[Dockerfile] -->|docker build| B[صورة Docker Image]
    B -->|docker run| C[الحاوية 1: التطبيق]
    B -->|docker run| D[الحاوية 2: قاعدة البيانات]
    B -->|docker run| E[الحاوية 3: Redis]

مع Docker، يصبح الإعداد الكامل بالنسبة لمطور جديد:

docker build -t myapp .
docker run myapp

هذا يحل محل أيام من تثبيت التبعيات وتصحيح الأخطاء.

صورة Docker مقابل حاوية Docker

يستخدم Docker مفهومًا يشبه البرمجة كائنية التوجه:

مفهوم OOPما يعادله في Docker
الصنف / المخطط (Class / Blueprint)صورة Docker (Image)
الكائن / النسخة (Object / Instance)حاوية Docker (Container)

صورة Docker (Docker Image) هي قالب للقراءة فقط (Read-only) يُبنى من ملف Dockerfile. تحتوي على كل الطبقات اللازمة لتشغيل تطبيقك. يمكن استخدام صورة واحدة لإنشاء عدة حاويات.

حاوية Docker (Docker Container) هي نسخة قيد التشغيل من صورة. إنها بيئة معزولة يُنفَّذ تطبيقك بداخلها فعليًا.

flowchart TD
    A[Dockerfile\nالتعليمات] -->|docker build| B[صورة Docker\nقالب / مخطط]
    B -->|docker run| C[الحاوية أ\nنسخة قيد التشغيل]
    B -->|docker run| D[الحاوية ب\nنسخة قيد التشغيل]
    B -->|docker run| E[الحاوية ج\nنسخة قيد التشغيل]

طبقات صورة Docker

كل تعليمة في ملف Dockerfile تُنشئ طبقة (Layer) في الصورة الناتجة. يخزّن Docker هذه الطبقات مؤقتًا (Cache). إذا لم تتغير طبقة منذ آخر بناء، يعيد Docker استخدام النسخة المخزّنة مؤقتًا بدلًا من إعادة بنائها. هذا يجعل عمليات البناء اللاحقة أسرع.

graph TD
    L1[الطبقة 1: الصورة الأساسية node:14]
    L2[الطبقة 2: WORKDIR /app]
    L3[الطبقة 3: COPY package.json]
    L4[الطبقة 4: RUN npm install]
    L5[الطبقة 5: COPY ملفات المصدر]
    L1 --> L2 --> L3 --> L4 --> L5

Docker Hub

Docker Hub (hub.docker.com) هو سجل عام (Registry) لصور Docker. إنه ما يعادل GitHub بالنسبة لصور Docker. تُخزَّن هناك كل الصور الأساسية الرسمية - Node.js وPHP وPython وPostgreSQL وRedis وnginx. يسحب Docker الصور تلقائيًا من Docker Hub عندما تشير إليها داخل ملف Dockerfile.

تمتلك كل صورة على Docker Hub عدة وسوم (Tags) تمثل إصدارات ومتغيرات مختلفة:

الوسم (Tag)الوصف
node:14صورة Node.js 14 كاملة (مبنية على Debian)
node:14-alpineصورة Node.js 14 مصغّرة (حجم أساسي ~5 ميجابايت، مبنية على Alpine Linux)
node:14-slimصورة Node.js 14 مخفّضة (حزم أقل من الصورة الكاملة)

مهام المعمل

المهمة 1: تثبيت المتطلبات الأساسية

1.1 تثبيت Docker Desktop

ابحث عن "install Docker Desktop" وانتقل إلى docker.com. ثبّت Docker Desktop لنظام التشغيل الخاص بك (Windows أو macOS أو Linux). يثبّت Docker Desktop محرك Docker (Docker Engine)، وDocker Compose، وDocker Swarm معًا.

بعد التثبيت، تحقق أن Docker يعمل عن طريق فحص شريط النظام (System Tray) بحثًا عن أيقونة Docker. يمكنك أيضًا فتح لوحة تحكم Docker Desktop لمراقبة الحاويات بشكل مرئي.

1.2 تحقق من التثبيت

docker --version
docker ps

الناتج المتوقع لـ docker ps:

CONTAINER ID   IMAGE   COMMAND   CREATED   STATUS   PORTS   NAMES

جدول فارغ يعني أن Docker يعمل ولا توجد حاويات نشطة بعد.

1.3 إعداد Docker Desktop (اختياري)

في إعدادات Docker Desktop ← General، يمكنك تفعيل "Start Docker Desktop when you login" حتى يبدأ Docker تلقائيًا مع تشغيل جهازك.


المهمة 2: أنشئ تطبيق Node.js التوضيحي

يعمل هذا التطبيق كهدف لتحويله إلى Docker طوال المعمل.

2.1 أنشئ مجلد المشروع

mkdir node-app
cd node-app

2.2 هيّئ مشروع Node.js

npm init -y

هذا ينشئ package.json. اقبل كل الإعدادات الافتراضية.

2.3 ثبّت Express

npm install express

2.4 ثبّت nodemon كتبعية تطوير (Development Dependency)

npm install --save-dev nodemon

تراقب nodemon تغييرات الملفات وتعيد تشغيل الخادم تلقائيًا أثناء التطوير المحلي.

2.5 أنشئ index.js

const express = require('express');
const app = express();
 
const PORT = 4000;
 
app.get('/', (req, res) => {
    res.send('<h2>Hello from Docker!</h2>');
});
 
app.listen(PORT, () => {
    console.log(`App is up and running on port ${PORT}`);
});

2.6 أضف أوامر تشغيل (Scripts) إلى package.json

عدّل قسم scripts:

"scripts": {
    "start": "node index.js",
    "start:dev": "nodemon index.js"
}

2.7 اختبر التطبيق بدون Docker

npm start

الناتج المتوقع:

App is up and running on port 4000

افتح http://localhost:4000 في متصفحك. يجب أن ترى استجابة HTML.

اضغط Ctrl+C لإيقاف الخادم قبل المتابعة.


المهمة 3: اكتب ملف Dockerfile

أنشئ ملفًا باسم Dockerfile (بدون امتداد) في جذر مجلد node-app.

ثبّت إضافة Docker في VS Code للحصول على تظليل الصياغة والإكمال التلقائي في ملفات Dockerfile.

ملف Dockerfile كامل:

FROM node:14
 
WORKDIR /app
 
COPY package.json .
 
RUN npm install
 
COPY . .
 
EXPOSE 4000
 
CMD ["npm", "start"]

شرح كل تعليمة:

التعليمةالغرض
FROM node:14يضبط الصورة الأساسية. يسحب Docker النسخة 14 من Node.js من Docker Hub. هذا يثبّت Node وnpm داخل الحاوية.
WORKDIR /appينشئ ويضبط /app كمجلد العمل داخل الحاوية. تُنفَّذ كل الأوامر اللاحقة من هذا المسار.
COPY package.json .ينسخ فقط package.json إلى مجلد العمل داخل الحاوية قبل تشغيل npm install.
RUN npm installيثبّت كل التبعيات المذكورة في package.json داخل /app/node_modules داخل الحاوية.
COPY . .ينسخ كل ملفات مصدر التطبيق المتبقية إلى مجلد العمل داخل الحاوية.
EXPOSE 4000يوثّق أن التطبيق يستمع على المنفذ (Port) 4000. هذه بيانات وصفية (Metadata) فقط - لا تنشر المنفذ إلى الجهاز المضيف.
CMD ["npm", "start"]الأمر الذي يشغّله Docker عند بدء تشغيل حاوية. يشغّل سكربت start المُعرَّف في package.json.

لماذا نسخ package.json بشكل منفصل قبل نسخ المصدر الكامل؟

يخزّن Docker كل طبقة مؤقتًا (Cache). إذا نسخت package.json أولًا وشغّلت npm install كطبقة منفصلة، فإن Docker يعيد تشغيل npm install فقط عندما يتغير package.json فعليًا. إذا غيّرت فقط كود المصدر (index.js، إلخ)، يعيد Docker استخدام طبقة npm install المخزّنة مؤقتًا، مما يجعل عمليات إعادة البناء أسرع بكثير.


المهمة 4: ابنِ صورة Docker

docker build -t express-node-app .
  • -t express-node-app يسمّي الصورة express-node-app
  • . يخبر Docker بالبحث عن Dockerfile في المجلد الحالي

الناتج المتوقع (مختصر):

[1/5] FROM node:14
[2/5] WORKDIR /app
[3/5] COPY package.json .
[4/5] RUN npm install
[5/5] COPY . .
Successfully built <image-id>
Successfully tagged express-node-app:latest

يسحب Docker الصورة الأساسية node:14 من Docker Hub عند أول تشغيل. تستخدم عمليات البناء اللاحقة الطبقة الأساسية المخزّنة مؤقتًا.

تحقق من إنشاء الصورة:

docker image ls

الناتج المتوقع:

REPOSITORY          TAG       IMAGE ID       CREATED         SIZE
express-node-app    latest    <id>           X seconds ago   960MB

المهمة 5: شغّل حاوية Docker

5.1 التشغيل في المقدمة (للاختبار الأولي)

docker run --name express-node-app-container express-node-app

الناتج المتوقع:

App is up and running on port 4000

الطرفية الآن مرتبطة بالحاوية. اضغط Ctrl+C لإيقافها.

5.2 التشغيل في وضع منفصل (Detached) مع إعادة توجيه المنفذ

docker run -d --name express-node-app-container -p 4000:4000 express-node-app
العلامة (Flag)المعنى
-dالوضع المنفصل (Detached) - يعمل في الخلفية، ويعيد التحكم إلى طرفيتك
--name express-node-app-containerيخصص اسمًا مقروءًا للحاوية
-p 4000:4000إعادة توجيه المنفذ: يربط المنفذ 4000 على جهازك المضيف بالمنفذ 4000 داخل الحاوية

لماذا تُعد إعادة توجيه المنفذ ضرورية؟

تعمل الحاويات في بيئة شبكية معزولة. حتى وإن كان التطبيق بداخلها يستمع على المنفذ 4000، وحتى وإن أُعلِن EXPOSE 4000 داخل ملف Dockerfile، لن يكون المنفذ متاحًا من جهازك المضيف حتى تعيد توجيهه صراحة باستخدام -p. الصيغة هي -p <منفذ_المضيف>:<منفذ_الحاوية>.

5.3 تحقق أن الحاوية تعمل

docker ps

الناتج المتوقع:

CONTAINER ID   IMAGE              COMMAND        CREATED        STATUS        PORTS                    NAMES
<id>           express-node-app   "npm start"    5 seconds ago  Up 4 seconds  0.0.0.0:4000->4000/tcp   express-node-app-container

5.4 اختبر في المتصفح

افتح http://localhost:4000. يجب أن ترى استجابة HTML من داخل الحاوية.


المهمة 6: أدر الحاويات والصور

عرض الحاويات قيد التشغيل:

docker ps

عرض كل الحاويات (بما فيها المتوقفة):

docker ps -a

إيقاف حاوية قيد التشغيل:

docker stop express-node-app-container

حذف حاوية متوقفة:

docker rm express-node-app-container

إيقاف قسري وحذف حاوية قيد التشغيل بأمر واحد:

docker rm -f express-node-app-container

عرض كل الصور:

docker image ls

حذف صورة:

docker rmi express-node-app

لا يمكنك حذف صورة أثناء استخدام حاوية لها (حتى لو كانت متوقفة). احذف الحاوية أولًا.

عرض سجلات الحاوية:

docker logs express-node-app-container

ملخص الأوامر الأساسية:

الأمرالإجراء
docker build -t <name> .ابنِ صورة من ملف Dockerfile في المجلد الحالي
docker image lsاعرض كل الصور المحلية
docker run -d -p <host>:<container> --name <name> <image>شغّل حاوية في الخلفية مع إعادة توجيه المنفذ
docker psاعرض الحاويات قيد التشغيل
docker ps -aاعرض كل الحاويات
docker stop <name>أوقف حاوية قيد التشغيل
docker rm <name>احذف حاوية متوقفة
docker rm -f <name>احذف حاوية قيد التشغيل قسرًا
docker rmi <image>احذف صورة
docker logs <name>اعرض سجلات ناتج الحاوية

المهمة 7: استكشف Docker Hub

7.1 تصفّح Docker Hub

افتح https://hub.docker.com وابحث عن node.

7.2 اقرأ صفحة صورة Node.js الرسمية

النتيجة الأولى التي تحمل الشارة الرسمية هي صورة Node.js التي تديرها Docker نفسها. مرر لأسفل لرؤية كل الوسوم (Tags) المتاحة. لاحظ المتغيرات المختلفة: الصورة الكاملة، slim، alpine. الصور المبنية على Alpine أصغر بكثير لأنها مبنية على Alpine Linux (حجم أساسي ~5 ميجابايت).

7.3 اختر الصورة الأساسية المناسبة

لحالات استخدام الإنتاج حيث يهم حجم الصورة، فضّل:

FROM node:14-alpine

بالنسبة للتطوير أو عندما تحتاج أدوات نظام كاملة، الصورة الكاملة مقبولة.

7.4 جرّب متغيرًا مختلفًا للصورة الأساسية

غيّر السطر الأول في ملف Dockerfile الخاص بك إلى:

FROM node:14-alpine

أعد البناء وقارن حجم الصورة:

docker build -t express-node-app-alpine .
docker image ls

يجب أن تكون الصورة المبنية على Alpine أصغر بكثير من صورة node:14 الكاملة.


الملخص

المفهومالفكرة الأساسية
المشكلةنقل تطبيق بين البيئات يتطلب إعادة تثبيت كل التبعيات في كل مرة، وهذا عرضة للأخطاء ويستهلك الوقت
Dockerأداة تغلّف تطبيقك وتبعياته في حاويات قابلة للنقل تعمل باتساق في أي مكان
Docker Hubسجل عام على hub.docker.com تُخزَّن فيه صور Docker الرسمية والمجتمعية
Dockerfileملف نصي يحتوي على تعليمات خطوة بخطوة تُخبر Docker كيف يبني صورة لتطبيقك
FROMيحدد الصورة الأساسية - نقطة البداية التي توفّر بيئة التشغيل (مثل Node.js وPHP وPython)
WORKDIRيضبط مجلد العمل داخل الحاوية
COPYينسخ ملفات من جهازك المحلي إلى نظام ملفات الحاوية
RUNينفّذ أمرًا أثناء خطوة بناء الصورة (مثل npm install)
CMDالأمر الذي يعمل عند بدء تشغيل حاوية (مثل npm start)
EXPOSEيوثّق المنفذ الذي يستمع عليه التطبيق (لا ينشر المنفذ)
صورة Dockerالقالب المبني (المخطط/الصنف) الناتج عن docker build
طبقات الصورةكل تعليمة في Dockerfile تنشئ طبقة مخزّنة مؤقتًا؛ يُعاد استخدام الطبقات غير المتغيرة عند إعادة البناء
حاوية Dockerنسخة قيد التشغيل من صورة (مثل كائن تم إنشاؤه من صنف)
إعادة توجيه المنفذ (-p)يربط منفذ المضيف بمنفذ الحاوية حتى يصبح التطبيق متاحًا من خارج الحاوية
العلامة -dتشغّل الحاوية في وضع منفصل (خلفية)