جدید فرنٹ اینڈ انجینئرنگ اب ویسی نہیں رہی جیسی ایک دہائی پہلے ہوا کرتی تھی۔ اب آپ صرف HTML اور CSS نہیں لکھ رہے۔ ایک عام پروجیکٹ اب ایک مخصوص Node.js runtime، پیکج مینیجر کے ایک لاک شدہ ورژن، بلڈ ٹولز کے ایک پیچیدہ جال، اور ایسی ڈیپلائمنٹ پائپ لائنز کے ساتھ آتا ہے جو توقع کرتی ہیں کہ ہر چیز بالکل درست طریقے سے ترتیب میں ہو۔ پہلے npm install اور آخری پروڈکشن بلڈ کے درمیان کہیں، چھوٹی چھوٹی تبدیلیاں پیدا ہو جاتی ہیں۔ آپ کا ایک ساتھی Node 20 استعمال کر رہا ہے۔ آپ Node 18 استعمال کر رہے ہیں۔ آپ کی مشین پر موجود ایک گلوبل CLI ٹول ان کی مشین پر موجود کسی گمشدہ ڈیپینڈینسی (dependency) کو چھپا دیتا ہے۔ پھر وہ جملہ آتا ہے جو کوئی نہیں سننا چاہتا: "یہ میری مشین پر تو چل رہا ہے۔"
Docker آپ کے فرنٹ اینڈ ٹول کٹ میں اپنی جگہ اس لیے بناتا ہے کیونکہ یہ اس غیر یقینی صورتحال کو ختم کر دیتا ہے۔ یہ آپ کی ایپلی کیشن کو بالکل اسی runtime، سسٹم لائبریریز اور ڈیپینڈینسیز کے ساتھ بنڈل کرتا ہے جن کی اسے ضرورت ہوتی ہے۔ چاہے آپ Windows پر کوڈنگ کر رہے ہوں، macOS سے شپنگ کر رہے ہوں، یا Linux کلاؤڈ انسٹنس پر ڈیپلائمنٹ کر رہے ہوں، اس کا طرزِ عمل (behavior) بالکل ایک جیسا رہتا ہے۔
فرنٹ اینڈ ڈویلپرز کو اس کی فکر کیوں کرنی چاہیے
Docker جن مسائل کا حل پیش کرتا ہے وہ محض خیالی نہیں ہیں۔ وہ ہر اسپرنٹ (sprint) میں سامنے آتے ہیں۔
ورژن کے تنازعات (version conflicts) وقت ضائع کرتے ہیں۔ ایک پرانے کلائنٹ پروجیکٹ کو Node 18 چاہیے، آپ کے سائیڈ پروجیکٹ کو Node 20 چاہیے، اور نئے اسٹارٹ اپ کے کام کے لیے Node 22 درکار ہے۔ کنٹینرز کے بغیر، آپ اسے ورژن مینیجرز کے ذریعے سنبھالتے ہیں۔ یہ تب تک کام کرتا ہے جب تک کہ کوئی مسئلہ نہ آ جائے۔ npm کے ورژن میں معمولی سا فرق peer dependencies کے حل کرنے کے طریقے کو بدل سکتا ہے، جس کے نتیجے میں آپ کا بلڈ خراب ہو جاتا ہے جبکہ وہ کسی دوسرے کے لیے بالکل ٹھیک چل رہا ہوتا ہے۔ جب کوئی فریم ورک نیا ریلیز کا اعلان کرتا ہے، تو فیڈ بیک لوپ دو گھنٹے کی ری انسٹالیشنز پر مبنی نہیں ہونا چاہیے۔ بلکہ اسے صرف ایک فائل کی تبدیلی اور کنٹینر کے ری اسٹارٹ تک محدود ہونا چاہیے۔
گلوبل پیکجز خاموش رکاوٹوں کا ایک اور ذریعہ ہیں۔ ہو سکتا ہے کہ آپ کے پاس چھ ماہ پہلے سے انسٹال شدہ Angular CLI، Expo، یا Prisma گلوبلی طور پر موجود ہو۔ ایک نیا ڈویلپر اسی ٹول کو نئے سرے سے انسٹال کرتا ہے اور اسے ایک مختلف ورژن ملتا ہے۔ اچانک آپ کے بلڈ اسکرپٹس ایسی وارننگز دینے لگتے ہیں جو کہیں اور نظر نہیں آتیں۔ Docker اس کا حل یہ نکالتا ہے کہ وہ ہر چیز کو پروجیکٹ کے مقامی (local) سطح پر رکھتا ہے۔ آپ اپنی Dockerfile میں Node کا ورژن متعین کرتے ہیں۔ ڈیپینڈینسیز کنٹینر کے اندر انسٹال ہوتی ہیں، جو آپ کے ہوسٹ آپریٹنگ سسٹم (host Operating System) سے الگ ہوتی ہیں۔ آپ کا لیپ ٹاپ macOS، Windows، یا Ubuntu ہو سکتا ہے؛ ایپلی کیشن ہر بار بالکل ایک جیسا ماحول دیکھتی ہے۔
آن بورڈنگ (onboarding) کے فوائد کو نظر انداز کرنا مشکل ہے۔ نئے ملازمین کو Homebrew انسٹالیشنز، nvm aliases، اور گلوبل پرمیشن فکسز پر مشتمل تین صفحات کی readme فائل کی ضرورت نہیں ہوتی۔ وہ بس Docker انسٹال کرتے ہیں، ریپوزٹری (repository) کلون کرتے ہیں، اور ایک کمانڈ چلاتے ہیں۔ وہ سیٹ اپ جس میں پہلے ایک دوپہر لگ جاتی تھی، اب منٹوں میں ہو جاتا ہے۔ اور جب وہ پروجیکٹس تبدیل کرتے ہیں، تو کچھ بھی پیچھے نہیں رہ جاتا۔ کوئی بے کار گلوبل ٹولز نہیں، اور نہ ہی PATH کی ترجیح کے لیے لڑنے والے ورژن مینیجرز۔ ان کی لوکل مشین بالکل صاف ستھری رہتی ہے۔
امیجز اور کنٹینرز: بنیادی باتیں
اگر آپ کے لیے Docker نیا ہے، تو اس کی اصطلاحات سننے میں جتنی مشکل لگتی ہیں، حقیقت میں اس سے کہیں زیادہ سادہ ہیں۔ ایک Docker image ایک بلیو پرنٹ (blueprint) ہے۔ اس میں آپ کا سورس کوڈ، Node.js runtime، آپ کی lockfile، اور ایپ چلانے کے لیے درکار ہر ڈیپینڈینسی شامل ہوتی ہے۔ کنٹینر اس امیج سے بنایا گیا ایک لائیو انسٹنس (live instance) ہے۔ امیج کو ایک ترکیب (recipe) اور کنٹینر کو اصل کھانے کے طور پر سمجھیں۔ آپ ایک ہی ترکیب سے سو بار ایک جیسا کیک بنا سکتے ہیں۔ آپ ہوسٹ کمپیوٹر پر انسٹال شدہ چیزوں کی فکر کیے بغیر ایک امیج سے بالکل ایک جیسے کنٹینرز چلا سکتے ہیں۔
ایک عملی Dockerfile
آئیے ایک ٹھوس نقطہ آغاز دیکھتے ہیں۔ اگر آپ
