المعمل 08: مقدمة في Docker - الحاويات والصور
نظرة عامة
يقدّم هذا المعمل 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 --> L5Docker 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-app2.2 هيّئ مشروع Node.js
npm init -yهذا ينشئ package.json. اقبل كل الإعدادات الافتراضية.
2.3 ثبّت Express
npm install express2.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-container5.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 | تشغّل الحاوية في وضع منفصل (خلفية) |