يا أهلاً وسهلاً فيكم يا جماعة الخير. اسمي أبو عمر، مبرمج فلسطيني قضيت سنين عمري بين الأكواد والخوارزميات، وشفت العجب في عالم التكنولوجيا. اليوم بدي أحكي لكم قصة صارت معي ومع فريقي، قصة كانت ممكن تكون كابوس لولا رحمة ربنا وأداة غيرت كل شيء.
قبل كم سنة، انضم لفريقنا مبرمج جديد، شب اسمه “أمجد”، شاطر ومتحمس. كالعادة، أول يوم إله كان مخصص لإعداد جهازه عشان يبدأ يشتغل على المشروع. أعطيناه المستودع (repository) وملف الـ README اللي فيه “شرح” من عشرين خطوة لكيفية تنصيب كل شيء: “نصّب Python نسخة 3.8.5 بالضبط، بعدين PostgreSQL نسخة 12، وما تنسى Redis، وطبعاً لازمك مليون مكتبة بتنزلها بـ pip، وكل وحدة فيهم بدها حكاية”.
قضى أمجد أول يوم وهو بحاول. ثاني يوم، ثالث يوم… والزلمة غرقان في بحر من الأخطاء اللي ما إلها أول من آخر. كل شوي يجينا على المكتب ويقول: “يا جماعة، طلعلي خطأ غريب… المكتبة الفلانية مش راضية تنزل”، أو “قاعدة البيانات ما بتشبك”. واحنا، بكل برود، كنا نرد عليه الرد المشهور: “غريبة والله… شغّالة عندي!“.
في هذيك اللحظة، أدركت إنه المشكلة مش في أمجد ولا فينا. المشكلة في طريقتنا كلها. إحنا بنبني أنظمة معقدة، وبنتوقع من كل واحد يركّبها على جهازه المختلف (واحد بشتغل على Windows، والثاني على Mac، والثالث على توزيعة Linux ما حدا سمع فيها) بنفس الطريقة وتشتغل! هالحكي مش منطقي. كان لازم نلاقي حل جذري، وهنا بدأت رحلتنا مع Docker و Docker Compose.
ما هي مشكلة “لكنها تعمل على جهازي”؟
قبل ما ندخل في الحل، خلينا نفصّص المشكلة. ليش بيئة التطوير على جهازك بتختلف عن جهازي؟
- أنظمة التشغيل المختلفة: الأوامر، مسارات الملفات، وحتى طريقة تعامل النظام مع الشبكات تختلف بين Windows, macOS, و Linux.
- إصدارات البرمجيات: مشروعك قد يتطلب نسخة Node.js 16، لكن زميلك عنده نسخة 18 على جهازه. هذا كفيل بتكسير كل شيء.
- التبعيات (Dependencies) الخفية: أحياناً يعتمد الكود على مكتبة مثبتة على مستوى النظام (system-level library) نسيت توثيقها، ولا أحد يعلم بوجودها إلا جهازك.
- إعدادات الخدمات: إعدادات قاعدة البيانات، خادم الويب، وخدمات التخزين المؤقت (caching) قد تختلف من جهاز لآخر.
- ملفات README.md المنسية: التعليمات المكتوبة لإعداد المشروع تصبح قديمة بسرعة، ويصبح صيانتها عبئاً بحد ذاتها.
هذه الفوضى لا تضيع الوقت فقط، بل تقتل حماس أي مبرمج جديد وتخلق بيئة عمل محبطة.
الحل يبدأ من هنا: Docker
أول قطعة في الأحجية كانت Docker. ببساطة شديدة، Docker يسمح لنا بـ “تغليف” تطبيقنا وكل ما يحتاجه للعمل داخل صندوق معزول اسمه “حاوية” (Container). هذه الحاوية تشبه جهاز كمبيوتر مصغر وخفيف الوزن، يحتوي على نظام تشغيل مصغّر، لغة البرمجة، المكتبات، والكود الخاص بتطبيقك.
الجميل في الأمر أن هذه الحاوية تعمل بنفس الطريقة تماماً على أي جهاز مثبت عليه Docker. وداعاً لاختلافات أنظمة التشغيل!
الـ Dockerfile: وصفة بناء الحاوية
لكي نبني هذه الحاوية، نكتب تعليمات البناء في ملف اسمه Dockerfile. هذا الملف هو بمثابة وصفة طبخة. تخيل أنك تبني تطبيق Node.js، قد يبدو الـ Dockerfile الخاص به كالتالي:
# استخدم صورة رسمية لـ Node.js كقاعدة
FROM node:16
# أنشئ مجلد العمل داخل الحاوية
WORKDIR /usr/src/app
# انسخ ملفات package.json و package-lock.json
COPY package*.json ./
# قم بتثبيت تبعيات المشروع
RUN npm install
# انسخ باقي ملفات الكود إلى مجلد العمل
COPY . .
# عرّض البورت 3000 ليكون متاحاً خارج الحاوية
EXPOSE 3000
# الأمر الذي سيتم تشغيله عند بدء الحاوية
CMD [ "node", "server.js" ]
هذا رائع! الآن صار عنا طريقة موحدة لبناء وتشغيل تطبيقنا. لكن ماذا عن قاعدة البيانات؟ ماذا عن Redis؟ هل سنقوم بإنشاء وتشغيل كل حاوية يدوياً وربطها ببعضها؟ هنا يأتي دور البطل الحقيقي.
الأوركسترا الكاملة مع Docker Compose
معظم التطبيقات الحديثة ليست مجرد خدمة واحدة. هي عبارة عن مجموعة من الخدمات التي تتحدث مع بعضها (microservices). تطبيق الويب يحتاج قاعدة بيانات، وقد يحتاج خدمة تخزين مؤقت مثل Redis، وربما خادم للبحث مثل Elasticsearch.
Docker Compose هو “المايسترو” الذي يدير هذه الأوركسترا. هو أداة تسمح لك بتعريف وتشغيل تطبيقات متعددة الحاويات باستخدام ملف واحد بسيط بصيغة YAML اسمه docker-compose.yml.
باستخدام هذا الملف، يمكنك وصف كل خدمات تطبيقك (الويب، قاعدة البيانات، إلخ)، الشبكات التي تربطها، والمجلدات المشتركة (volumes) التي تحفظ البيانات.
مثال عملي: لنبني بيئة تطوير حقيقية
لنفترض أن لدينا مشروع بسيط يتكون من:
- backend: تطبيق API مكتوب بـ Node.js.
- db: قاعدة بيانات PostgreSQL.
هكذا سيبدو ملف docker-compose.yml الخاص بنا:
version: '3.8'
services:
# خدمة تطبيق الـ Backend
backend:
build: .
ports:
- "3000:3000"
volumes:
# هذا السطر هو السحر: يربط الكود على جهازك بالكود داخل الحاوية
# أي تغيير تحفظه في الكود يظهر فوراً داخل الحاوية بدون إعادة بناء
- .:/usr/src/app
environment:
- DATABASE_URL=postgres://user:password@db:5432/mydatabase
depends_on:
- db
# خدمة قاعدة البيانات
db:
image: postgres:13
environment:
POSTGRES_DB: mydatabase
POSTGRES_USER: user
POSTGRES_PASSWORD: password
ports:
- "5432:5432"
volumes:
# هذا السطر يحفظ بيانات قاعدة البيانات على جهازك
# حتى لو أوقفت الحاويات وأعدت تشغيلها، بياناتك لن تضيع
- postgres_data:/var/lib/postgresql/data
# تعريف الـ Volume لضمان استمرارية البيانات
volumes:
postgres_data:
شرح بسيط للملف:
- services: نعرّف خدماتنا هنا (
backendوdb).- backend.build: يطلب من Compose بناء الحاوية باستخدام الـ
Dockerfileالموجود في نفس المجلد.- backend.ports: يربط البورت 3000 على جهازك بالبورت 3000 داخل الحاوية.
- backend.volumes: هذا هو سر الإنتاجية! يربط مجلد المشروع الحالي (
.) بالمجلد/usr/src/appداخل الحاوية. أي تعديل على الكود ينعكس فوراً.- backend.depends_on: يخبر Compose أن خدمة
backendتعتمد على خدمةdb، فيقوم بتشغيلdbأولاً.- db.image: بدلاً من البناء من الصفر، نستخدم صورة جاهزة لـ PostgreSQL من Docker Hub.
- db.volumes: نستخدم ما يسمى بـ “Named Volume” لحفظ بيانات قاعدة البيانات بشكل دائم. هذا مهم جداً!
الأوامر التي غيرت حياتنا
الآن، بدلاً من عشرين خطوة في ملف الـ README، أصبح إعداد المشروع بأكمله عبارة عن أمرين فقط يقوم بهما المبرمج الجديد:
- تنزيل الكود من Git.
- كتابة أمر واحد في الطرفية (Terminal):
docker-compose up
هذا الأمر يقوم بقراءة ملف docker-compose.yml، بناء الصور اللازمة، تنزيل الصور الجاهزة، إنشاء وتشغيل كل الحاويات، وربطها ببعضها. المشروع بأكمله يعمل الآن على جهاز المبرمج الجديد تماماً كما يعمل على جهازك.
بعض الأوامر المفيدة الأخرى:
docker-compose up -d: لتشغيل الحاويات في الخلفية.docker-compose down: لإيقاف وحذف الحاويات والشبكات.docker-compose down -v: نفس الأمر السابق، لكنه يحذف الـ volumes أيضاً (احذر استخدامه مع بيانات مهمة!).docker-compose logs -f backend: لمشاهدة سجلات (logs) خدمة الـ backend بشكل حي.docker-compose build: لإعادة بناء الصور إذا قمت بتعديل الـDockerfile.
نصائح من خبرة أبو عمر: من الميدان
على مدار السنين، تعلمت كم شغلة بتخلي استخدام Docker Compose أسهل وأكثر فعالية. تفضلوا:
- استخدموا ملفات
.env: لا تكتبوا كلمات السر أو المفاتيح الحساسة مباشرة في ملفdocker-compose.yml. ضعوها في ملف اسمه.env. سيقوم Docker Compose بقرائته تلقائياً. - لا تهملوا
.dockerignore: تماماً مثل.gitignore، هذا الملف يخبر Docker ما هي الملفات التي يجب تجاهلها عند بناء الصورة. أضفوا مجلدات مثلnode_modulesو.gitلتسريع عملية البناء وتقليل حجم الصورة. - افهموا الـ Volumes جيداً: هناك فرق بين ربط مجلد (bind mount) مثل
.:/usr/src/appالذي نستخدمه للكود، وبين الـ “named volume” مثلpostgres_dataالذي نستخدمه لبيانات قاعدة البيانات. الأول للتطوير السريع، والثاني للحفاظ على البيانات. - فصل ملفات Compose: للمشاريع الكبيرة، يمكنكم فصل الإعدادات. ملف
docker-compose.ymlأساسي، وملفdocker-compose.override.ymlلإعدادات التطوير المحلية، وملفdocker-compose.prod.ymlلإعدادات الإنتاج.
الخلاصة: وداعاً للفوضى، أهلاً بالإنتاجية ✅
التحول إلى Docker Compose لم يكن مجرد تغيير تقني، بل كان تغييراً في ثقافة العمل في الفريق. لقد حررنا من ساعات لا تحصى من التنصيب وحل المشاكل، وأعاد التركيز إلى ما يهم حقاً: كتابة كود رائع وحل مشاكل حقيقية.
الآن، عندما ينضم عضو جديد، يستغرق إعداد بيئة عمله دقائق معدودة بدلاً من أيام. عبارة “لكنها تعمل على جهازي” اختفت تقريباً من قاموسنا، وحل محلها الثقة والإنتاجية.
نصيحتي الأخيرة لكم: استثمروا يوماً أو يومين لتعلم Docker و Docker Compose. قد يبدو الأمر معقداً في البداية، لكن الفائدة التي ستحصلون عليها على المدى الطويل لا تقدر بثمن. صدقوني، هذا الاستثمار سيوفر عليكم أسابيع وربما شهوراً من الصداع في المستقبل. 🚀