الفصل 1: أساسيات Docker
قبل أن تكتب أول Dockerfile، افهم أولًا لماذا يوجد
Docker وما المشكلة التي يحلّها. في هذا الفصل نتعرّف على الفرق بين
الحاوية والجهاز الافتراضي، وعلى علاقة الـ Image بالحاوية، وكيف يعمل
Docker من الداخل.
نشرح كل فكرة بشكل عام أولًا، ثم نطبّقها على مثال حقيقي — تطبيق Spring Boot — كلما احتجنا أن نراها تعمل.
أهداف التعلّم
بنهاية هذا الفصل ستعرف كيف:
- تشرح مشكلة «يعمل على جهازي» ولماذا يحلّها Docker.
- تفرّق بين الجهاز الافتراضي (VM) والحاوية (Container).
- تصف العلاقة بين الـ Image والحاوية.
- تعرف مكوّنات Docker الثلاثة: Client و Daemon و Registry.
- تتابع مراحل دورة حياة الحاوية.
- تربط هذه المفاهيم بتطبيق واقعي (Spring Boot).
المتطلبات المسبقة
هذا الفصل نظري بالكامل. لا تحتاج إلى تثبيت Docker بعد؛ سنفعل ذلك في الفصل التالي. المفاهيم عامة وتنطبق على أي تطبيق. بعض الأمثلة تستخدم Java و Spring Boot، فمعرفتهما تفيد لكنها ليست شرطًا.
💡 ملاحظة
الأوامر التي تظهر في قسمَي «مثال» و«عرض عملي» هي معاينة فقط لتكوين تصوّر ذهني — ستنفّذها بنفسك في الفصل 2 بعد تثبيت Docker.
لماذا Docker؟ مشكلة «يعمل على جهازي»
تخيّل أنك أنهيت تطوير تطبيقك على جهازك. يعمل بشكل ممتاز. ترسله إلى زميلك أو إلى الخادم، فينهار فورًا. لماذا؟
لأن أي تطبيق لا يعمل في فراغ، بل يعتمد على بيئة كاملة حوله:
- إصدار محدّد من بيئة التشغيل (runtime) والمكتبات.
- متغيّرات بيئة (environment variables) وإعدادات.
- خدمات خارجية مثل قاعدة بيانات بإصدار معيّن.
- منافذ (ports) ومكتبات نظام تشغيل.
أي اختلاف بسيط في هذه البيئة بين جهازك والخادم قد يكسر التطبيق. هذه هي مشكلة «works on my machine» الشهيرة.
مثال ملموس: تطبيق Java يعتمد على JRE بإصدار Java 21 على جهازك، بينما الخادم عليه Java 17 فقط — فيفشل التشغيل.
ما الذي يحلّه Docker؟
فكرة Docker بسيطة. بدل نقل التطبيق وحده، نحزمه مع بيئته كاملة في وحدة واحدة اسمها Image.
هذه الوحدة تعمل بنفس الطريقة على أي جهاز فيه Docker: جهازك، جهاز زميلك، أو الخادم. تختفي الاختلافات، وتختفي معها المشكلة.
✅ الفكرة الجوهرية
Docker لا يجعل تطبيقك أسرع، بل يجعله يعمل بنفس الطريقة في كل مكان. القيمة هي التطابق (consistency) بين التطوير والإنتاج.
الأجهزة الافتراضية (Virtual Machines)
قبل الحاويات، كان العزل يتم بالجهاز الافتراضي. الـ VM يحاكي جهازًا كاملًا: لكل تطبيق نظام تشغيل ضيف (Guest OS) مستقل يعمل فوق طبقة اسمها Hypervisor.
هذا يوفّر عزلًا قويًا، لكن بثمن:
- كل VM يحمل نظام تشغيل كامل بحجم جيجابايتات.
- الإقلاع يستغرق دقائق.
- استهلاك عالٍ للذاكرة والمعالج لأن كل نظام ضيف يعمل بالكامل.
الحاويات (Containers)
الحاوية تحلّ نفس المشكلة بطريقة أخف. بدل محاكاة جهاز كامل، تتشارك كل الحاويات نواة نظام التشغيل المضيف (host kernel)، وتعمل كعمليات (processes) معزولة فوقها.
النتيجة:
- الحجم بـ ميجابايتات لا جيجابايتات.
- الإقلاع خلال ثوانٍ أو أقل.
- استهلاك أقل بكثير — يمكن تشغيل عشرات الحاويات على نفس الجهاز.
❗ مهم جدًّا
الحاوية ليست جهازًا افتراضيًا صغيرًا، بل هي عملية معزولة. لا تحتوي على نظام تشغيل كامل، بل على ملفات وبيئة التطبيق فقط، وتستعير النواة من المضيف.
مقارنة سريعة: VM مقابل Container
| المعيار | Virtual Machine | Container |
|---|---|---|
| العزل | نظام تشغيل ضيف كامل | عملية معزولة على نواة المضيف |
| الحجم | جيجابايتات (GB) | ميجابايتات (MB) |
| زمن الإقلاع | دقائق | ثوانٍ |
| الاستهلاك | عالٍ | منخفض |
| الكثافة على الجهاز | عدد محدود | عشرات أو أكثر |
الـ Images والحاويات (Images & Containers)
هذان المصطلحان هما جوهر Docker، والخلط بينهما شائع. الفرق بسيط:
- Image: قالب ثابت للقراءة فقط (read-only) يحتوي على تطبيقك وكل ما يحتاجه ليعمل. لا يعمل بذاته — إنه مجرد «مخطّط».
- Container: نسخة حيّة قابلة للتشغيل من الـ Image. من الـ Image الواحدة يمكنك تشغيل عدد لا محدود من الحاويات.
✅ بلغة Java
إن كنت تعرف Java فالتشبيه مباشر: الـ Image كالـ Class، والحاوية كالـ Object (نسخة من الكلاس). كما تنشئ عدة كائنات من كلاس واحد، تنشئ عدة حاويات من Image واحدة.
تشبيه من الواقع
سُمّي Docker على اسم عمّال الموانئ (dockers) الذين يشحنون الحاويات، والتشبيه مقصود:
✅ حاوية الشحن
قبل حاويات الشحن القياسية، كان كل نوع بضاعة يُشحن بطريقته الخاصة — فوضى. جاءت الحاوية الموحّدة: صندوق واحد بمقاس ثابت يمكن أن ترفعه أي رافعة وتنقله أي سفينة أو شاحنة، بغضّ النظر عمّا بداخله.
حاوية Docker تفعل الشيء نفسه بالبرمجيات: تغلّف تطبيقك في «صندوق» موحّد يعمل على أي بنية تحتية تدعم Docker، دون أن تهتمّ هذه البنية بما بداخله.
مخطّطات توضيحية
البنية: VM مقابل Container
flowchart TB
subgraph VM["نموذج الأجهزة الافتراضية"]
direction TB
H1["Hardware"] --> HV["Hypervisor"]
HV --> G1["Guest OS + App A"]
HV --> G2["Guest OS + App B"]
end
subgraph CT["نموذج الحاويات"]
direction TB
H2["Hardware"] --> OS["Host OS + Kernel"]
OS --> DE["Docker Engine"]
DE --> C1["Container A"]
DE --> C2["Container B"]
DE --> C3["Container C"]
end
معمارية Docker
يعمل Docker بنموذج Client / Server. عندما تكتب أمرًا
مثل docker run، فأنت تستخدم الـ Client
الذي يرسل الطلب إلى الـ Docker Daemon (الخادم)، وهو
من يبني الـ Images ويشغّل الحاويات. أما الـ Images الجاهزة فتأتي من
Registry مثل Docker Hub.
flowchart LR
U["أنت"] -->|"docker run / build / pull"| CLI["Docker Client (CLI)"]
CLI -->|"REST API"| D["Docker Daemon (dockerd)"]
D -->|"يدير"| IMG["Images"]
D -->|"يشغّل"| CON["Containers"]
D <-->|"pull / push"| REG["Registry — Docker Hub"]
دورة حياة الحاوية
تمرّ الحاوية بحالات واضحة يتحكّم بها أمر واحد في كل انتقال:
stateDiagram-v2
[*] --> Created: docker create
Created --> Running: docker start
Running --> Paused: docker pause
Paused --> Running: docker unpause
Running --> Stopped: docker stop
Stopped --> Running: docker start
Stopped --> Removed: docker rm
Removed --> [*]
docker run يختصر الطريق: ينشئ الحاوية ويشغّلها
في خطوة واحدة.
مثال: المشكلة بشكل ملموس
لنجعل مشكلة «يعمل على جهازي» ملموسة. تطبيق Spring Boot الخاص بك يتطلّب Java 21، لكن الخادم عليه Java 17 فقط. عند التشغيل:
$ java -jar tasks-service.jar
Error: LinkageError occurred while loading main class
UnsupportedClassVersionError: class file version 65.0,
this version only recognizes up to 61.0
مع Docker، نثبّت إصدار Java داخل الـ Image نفسها، فلا يهمّ ما المثبّت على الخادم:
# The image carries Java 21 with it, wherever it runs
FROM eclipse-temurin:21-jre
COPY tasks-service.jar app.jar
ENTRYPOINT ["java", "-jar", "app.jar"]
الآن تعمل الحاوية بـ Java 21 على أي خادم، حتى لو كان لا يحوي Java إطلاقًا.
عرض عملي (معاينة)
💡 معاينة فقط
لن تنفّذ هذه الأوامر الآن — Docker غير مثبّت بعد. الهدف أن ترى الشكل النهائي الذي نتّجه إليه. ستشغّلها بنفسك في الفصل 2.
أشهر أمر تشغيل أول حاوية للتأكّد من أن Docker يعمل هو
hello-world:
docker run hello-world
ماذا يحدث خلف الكواليس عند تنفيذ هذا الأمر؟
- الـ Client يرسل الطلب إلى الـ Daemon.
- الـ Daemon لا يجد الـ Image محليًّا، فيسحبها (
pull) من Docker Hub. - ينشئ حاوية من الـ Image ويشغّلها.
- تطبع الحاوية رسالة ترحيب ثم تتوقّف.
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
Hello from Docker!
This message shows that your installation appears to be working correctly.
المعمل التطبيقي
كلما احتجنا إلى تطبيق حقيقي عبر الدليل، سنستعين بمثال متكرّر واحد: خدمة المهام (Tasks Service) — واجهة REST بسيطة بـ Spring Boot تحفظ المهام في قاعدة PostgreSQL. تبقى مفاهيم Docker عامة، ونستخدم هذا المثال فقط لرؤيتها مطبَّقة على شيء ملموس.
🧪 معمل 1: جرد بيئة التشغيل
لن نكتب كودًا بعد. مهمتك أن تحدّد ما الذي يجعل تطبيقك يعمل، لأن هذا بالضبط ما سنحزمه لاحقًا داخل الـ Image.
لأي تطبيق لديك (أو خدمة المهام كمثال)، اكتب في ورقة:
- إصدار Java المطلوب (JRE).
- المنفذ الذي يستمع عليه التطبيق (غالبًا
8080). - متغيّرات البيئة والإعدادات (رابط قاعدة البيانات، كلمة المرور، الـ profile).
- الخدمات الخارجية والـ dependencies (PostgreSQL وإصداره).
المخرجات: قائمة الجرد هذه هي «مواصفات» الـ Image
المستقبلية الخاصة بك — ستتحوّل مباشرةً إلى تعليمات Dockerfile في
الفصل 5، وإلى خدمات في compose.yaml في الفصل 8.
أخطاء شائعة
⚠️ الحاوية جهاز افتراضي صغير
خطأ شائع. الحاوية عملية معزولة تتشارك نواة المضيف، وليست نظام تشغيل كامل. لذلك هي أخف وأسرع، ولذلك أيضًا لا تُشغّل نظامًا مختلفًا كليًّا عن نواة المضيف.
⚠️ الخلط بين Image و Container
الـ Image قالب ثابت، والحاوية تشغيل حيّ له. تعديل حاوية لا يغيّر الـ Image التي بُنيت منها.
⚠️ ظنّ أن Docker يُلغي بيئة التشغيل (runtime)
Docker لا يستبدل الـ runtime الخاص بتطبيقك، بل يحزمه معه. مثلًا تطبيق Java ما زال يحتاج JRE — لكن الآن الـ JRE موجود داخل الـ Image بإصدار نعرفه ونتحكّم به.
أفضل الممارسات
📦 عملية واحدة لكل حاوية
اجعل كل حاوية مسؤولة عن شيء واحد: خدمة Spring Boot في حاوية، وقاعدة البيانات في أخرى.
🧊 الـ Image قطعة أثرية ثابتة
عامل الـ Image كـ artifact غير قابل للتغيير (immutable): تُبنى مرة، وتُشغَّل في كل البيئات كما هي.
♻️ الحاويات قابلة للاستبدال
اعتبر الحاوية سريعة الزوال (ephemeral): يمكن حذفها وإعادة إنشائها في أي لحظة دون خوف.
ورقة المصطلحات (Cheat Sheet)
| المصطلح | المعنى المختصر |
|---|---|
| Image | قالب ثابت للقراءة فقط يحوي التطبيق وبيئته |
| Container | نسخة حيّة قابلة للتشغيل من Image |
| Docker Client | واجهة الأوامر التي تكتبها (docker ...) |
| Docker Daemon | الخادم (dockerd) الذي يبني ويشغّل |
| Registry | مخزن الـ Images (Docker Hub مثلًا) |
| Dockerfile | وصفة نصية لبناء Image |
| Docker Engine | Client + Daemon معًا على الجهاز |
الخلاصة
أهم الأفكار
- Docker يحلّ مشكلة «يعمل على جهازي» بتحزيم التطبيق مع بيئته.
- الحاوية عملية معزولة تتشارك نواة المضيف — أخف وأسرع من الـ VM.
- الـ Image قالب ثابت، والحاوية تشغيل حيّ له (Class مقابل Object).
- معمارية Docker = Client + Daemon + Registry.
مصطلحات تذكّرها
- Image / Container / Registry
- Docker Client / Docker Daemon
أخطاء تجنّبها
- اعتبار الحاوية جهازًا افتراضيًّا صغيرًا.
- الخلط بين الـ Image والحاوية.
- ظنّ أن Docker يُلغي الـ JVM.