SSD Nodes Learn 🎉 VPS $5.50/ماہ سے
تعلیمی Matt Connorتحریر: Matt Connor

Docker Compose میں build یا image: VPS پر فرق

image شائع شدہ tag pull کرتا ہے، جبکہ build مقامی Dockerfile سے image بناتا ہے۔ جانیں VPS پر compose up تبدیلی کیوں نظرانداز کرتا ہے اور درست fix کیا ہے۔

Docker Compose میں build اور image: مختصر جواب

Docker Compose فائل میں image: کسی registry سے pull کی جانے والی image کا نام بتاتا ہے، جبکہ build: Compose کو اس مشین پر Dockerfile سے image build کرنے کی ہدایت دیتا ہے۔ صرف image: مقرر کریں تو Compose اس tag کو pull کرکے چلاتا ہے۔ صرف build: مقرر کریں تو Compose یہاں image build کرتا ہے اور اسے project name اور service name سے اخذ کردہ نام دیتا ہے۔ دونوں مقرر کرنے پر Compose مقامی طور پر image build کرتا ہے، پھر نتیجے کو image: میں دیے گئے نام سے tag کرتا ہے۔ یہی طریقہ اپنی منتخب کردہ نام کے تحت image build کرکے push کرنے کے لیے استعمال ہوتا ہے۔

یہی بنیادی فرق ہے۔ نیچے دی گئی تمام تفصیل اس فرق کے server پر عملی اثرات بیان کرتی ہے۔ اس میں فرض کیا گیا ہے کہ Docker Engine اور Compose plugin پہلے ہی installed ہیں؛ VPS پر Docker چلانا اس حصے کا احاطہ کرتا ہے۔

تینوں صورتیں مکمل طور پر

شائع شدہ tag حاصل کرکے اسے چلائیں۔ اس عمل میں کسی بھی مرحلے پر Dockerfile شامل نہیں ہوتا۔

services:
  web:
    image: nginx:1.27
    restart: unless-stopped
    ports:
      - "80:80"

موجودہ directory میں موجود Dockerfile سے build کریں۔ FROM میں نامزد base image کے علاوہ کچھ بھی pull نہیں کیا جاتا۔

services:
  web:
    build: .
    restart: unless-stopped
    ports:
      - "80:80"

مقامی طور پر build کرکے نتیجے کو tag کریں۔ اس کے بعد docker compose push اس exact tag کو registry پر بھیج سکتا ہے۔

services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
    image: registry.example.com/acme/web:1.4.2
    restart: unless-stopped
    ports:
      - "80:80"

context وہ directory ہے جو builder کو بھیجی جاتی ہے۔ dockerfile کی resolution اسی context کے لحاظ سے ہوتی ہے، اس لیے context: . کو dockerfile: docker/prod.Dockerfile کے ساتھ استعمال کرنا معمول اور درست ہے۔ ہر service container کے پیچھے موجود image name اور image ID دیکھنے کے لیے docker compose images چلائیں۔ یہ تصدیق کرنے کا تیز ترین طریقہ ہے کہ آپ نے واقعی ان تین صورتوں میں سے کون سی صورت لکھی ہے۔

docker compose up کمانڈ Dockerfile تبدیل کرنے کے بعد دوبارہ build کیوں نہیں کرتی؟

کیونکہ up یہ دیکھتا ہے کہ image موجود ہے یا نہیں، یہ نہیں کہ وہ تازہ ترین ہے یا نہیں۔

جب Compose ایسی service شروع کرتا ہے جس میں build: section موجود ہو، تو وہ local image store میں image تلاش کرتا ہے۔ اگر اس نام کی image پہلے سے موجود ہو تو Compose اسے استعمال کرتا ہے۔ یہ Dockerfile نہیں پڑھتا، source files کا موازنہ نہیں کرتا، اور کسی timestamp کو بھی نہیں دیکھتا۔ Compose specification میں یہ اصول pull_policy attribute کے طور پر بیان کیا گیا ہے، اور default behaviour کے مطابق image صرف اس وقت build ہوتی ہے جب وہ موجود نہ ہو۔ موجود image کو کافی سمجھا جاتا ہے۔

اس لیے آپ app.py میں ترمیم کرتے ہیں، docker compose up -d چلاتے ہیں، Compose کو container کے running ہونے کی اطلاع دیتے دیکھتے ہیں، لیکن پرانا code serve ہوتا رہتا ہے۔ کوئی failure نہیں ہوئی، اس لیے کوئی warning بھی نہیں ملی۔ Compose کے ساتھ "میری تبدیلی لاگو نہیں ہوئی" کی یہ سب سے عام وجہ ہے۔ اس کی واضح علامت وہ status word ہے جو Compose container name کے ساتھ دکھاتا ہے: جس container کو Compose نے replace کیا ہو، اس کے لیے recreated یا started دکھائی دیتا ہے، جبکہ جس container کو Compose نے تبدیل نہ کرنے کا فیصلہ کیا ہو، اس کے لیے running دکھائی دیتا ہے۔

دو checks سے صورتِ حال واضح ہو جاتی ہے۔ docker compose images ہر container کے زیرِ استعمال image ID دکھاتا ہے، اس لیے deploy سے پہلے اسے نوٹ کریں اور بعد میں اس کا موازنہ کریں۔ docker image ls میں CREATED column موجود ہوتا ہے، اور اگر image آپ کے آخری commit سے پہلے بنائی گئی ہو تو وہ stale image ہے، چاہے deploy script نے کچھ بھی print کیا ہو۔

کون سے flags rebuild پر مجبور کرتے ہیں

  • docker compose up -d --build پہلے build کرتا ہے، پھر ہر اس container کو دوبارہ بناتا ہے جس کی image تبدیل ہو چکی ہو۔ زیادہ تر لوگ اسی flag کی تلاش میں ہوتے ہیں۔
  • docker compose build web ایک service کو build کرتا ہے اور کچھ start نہیں کرتا۔ اس کے بعد docker compose up --no-deps -d web چلائیں تاکہ صرف وہی container تبدیل ہو اور stack کی باقی services چلتی رہیں۔
  • docker compose build --no-cache web تمام cached layers خارج کرتا ہے اور پہلی instruction سے rebuild کرتا ہے۔
  • docker compose build --pull FROM میں base image کا نیا version pull کرنے کی کوشش کرتا ہے۔ اس طرح node:22 جیسا moving tag مارچ میں download کی گئی copy کے بجائے اپنی موجودہ contents حاصل کرتا ہے۔
  • docker compose up -d --force-recreate پہلے سے استعمال ہونے والی image سے containers دوبارہ بناتا ہے۔ یہ کبھی build نہیں کرتا۔ جب آپ کا مقصد --build ہو، تب اسے استعمال کرنا ایک عام dead end ہے۔

آپ یہ فیصلہ file میں بھی شامل کر سکتے ہیں۔ Compose specification کے مطابق، pull_policy: build کا مطلب ہے کہ Compose image کو build کرتا ہے، اور اگر وہ پہلے سے موجود ہو تو اسے rebuild بھی کرتا ہے۔ اس کے بعد ہر up build کی لاگت برداشت کرتا ہے۔ یہ laptop پر مطلوب ہو سکتا ہے، لیکن server پر شاذونادر ہی مناسب ہوتا ہے۔

services:
  web:
    build: .
    image: registry.example.com/acme/web:dev
    pull_policy: build

ایک اور باہمی اثر جاننا مفید ہے۔ docker compose pull ان services کے لیے بھی images pull کرنے کی کوشش کرتا ہے جن میں build section موجود ہو۔ اگر pull ناکام ہو جائے تو یہ بتاتا ہے کہ image کو build کرنا ضروری ہے۔ ان services کو خاموشی سے چھوڑنے کے لیے --ignore-buildable پاس کریں۔

build cache آپ کے deploy کا وقت کیسے طے کرتا ہے

Dockerfile کی ہر instruction ایک layer بناتی ہے۔ Builder cached layer کو اس وقت دوبارہ استعمال کرتا ہے جب وہ instruction اور اس کے inputs تبدیل نہ ہوئے ہوں۔ COPY کے لیے inputs، copy کی جانے والی files کا content ہوتا ہے۔ جب ایک layer cache سے miss ہو جائے تو اس کے بعد کی ہر layer دوبارہ build ہوتی ہے، کیونکہ ہر layer اس filesystem پر build ہوتی ہے جو پچھلی layer نے تیار کیا تھا۔

یہی ایک اصول طے کرتا ہے کہ آپ کا deploy seconds لے گا یا minutes۔ Dockerfile کو ان چیزوں سے شروع کریں جو کم تبدیل ہوتی ہیں، اور آخر میں وہ چیزیں رکھیں جو ہر commit پر تبدیل ہوتی ہیں۔

FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["node", "server.js"]

npm ci، COPY . . کے اوپر ہے۔ اس لیے source file میں ترمیم کرنے سے install layer cached رہتی ہے، اور build copy step سے دوبارہ شروع ہوتی ہے۔ اگر ان دونوں lines کو بدل دیا جائے تو ایک character کی تبدیلی ہر dependency کو دوبارہ install کر دیتی ہے، کیونکہ COPY . . اس layer کو invalidate کرتا ہے جس پر npm ci build ہوتا ہے۔ یہی structure pip install -r requirements.txt اور go mod download پر بھی لاگو ہوتا ہے۔

--no-cache اس وقت درست tool ہے جب آپ کو شبہ ہو کہ کوئی stale layer آپ کی fix کو چھپا رہی ہے۔ اسے default کے طور پر استعمال کرنا درست نہیں، کیونکہ یہ اس reuse کو ختم کر دیتا ہے جس کے لیے Dockerfile کی ordering بنائی جاتی ہے۔

ایک چیز ایسی ہے جسے image sets اور Compose override کر سکتے ہیں: Dockerfile کا CMD وہ command ہے جسے image default طور پر چلاتی ہے، جبکہ service میں موجود command: key اسے replace کر دیتی ہے۔ command اور entrypoint باہم کیسے کام کرتے ہیں یہاں اہم ہے، کیونکہ Compose override freshly built image کو بالکل پرانی image کی طرح behave کروا سکتا ہے۔

Build context اور .dockerignore

context: . اس directory کو Compose package کرتا ہے اور پہلی instruction چلنے سے پہلے اسے builder کو بھیج دیتا ہے۔ اس کے اندر موجود ہر چیز شامل ہوتی ہے، جس میں .git اور وہ data directory بھی شامل ہے جسے آپ source کے ساتھ رکھیں۔ اگر کسی ایسی project کی build، جس میں کوئی تبدیلی نہیں ہوئی، transferring-context مرحلے پر رک جائے تو اس کا مطلب ہے کہ context بہت بڑا ہے۔

context کے root پر موجود .dockerignore file ان paths کو اس transfer سے خارج کرتی ہے۔ اس کا syntax .gitignore سے ملتا جلتا ہے۔

.git
node_modules
*.log
data/
.env

اس کے دو فائدے ہیں۔ Transfer کا حجم کم ہو جاتا ہے، اس لیے ہر build زیادہ تیزی سے شروع ہوتی ہے۔ مزید یہ کہ COPY . . اب .env کو image میں copy نہیں کر سکتا، جہاں وہ image pull کرنے والا ہر شخص اسے دوبارہ پڑھ سکتا ہے۔

وقت کے ساتھ بڑھنے والی slow-build کی ایک عام وجہ bind mount ہے۔ Named volume آپ کی project directory سے باہر رہتا ہے، لیکن ./data:/var/lib/postgresql/data جیسا bind mount build context کے اندر ہوتا ہے۔ اس لیے database کے بڑھنے کے ساتھ ہر ہفتے builds سست ہوتی جاتی ہیں۔ .dockerignore میں ایک line اس مسئلے کو حل کر دیتی ہے۔ Named volumes کے مقابل bind mounts میں اس وسیع تر trade-off کی وضاحت ہے۔

Build arguments اسی خطرے کی ایک محدود شکل پیدا کرتے ہیں۔ args: کے ذریعے دی گئی values image history میں اس image کے مالک ہر شخص کو نظر آتی ہیں۔ اس لیے وہاں version number رکھیں، token کبھی نہ رکھیں۔ Compose میں env files اور secrets میں بتایا گیا ہے کہ credentials کہاں رکھنے چاہییں۔

کیا آپ VPS پر build کریں یا کسی اور جگہ build کر کے pull کریں؟

آپ کی network traffic فراہم کرنے والے box پر build کرنا default طریقہ ہے، کیونکہ یہ مختصر ترین راستہ ہے: git pull، پھر docker compose up -d --build۔ ایسے چھوٹے server پر یہ طریقہ درست ہے جس پر ابھی کوئی انحصار نہیں کرتا۔ لیکن دو قابلِ پیمائش وجوہات اور ایک ایسی وجہ کی بنا پر یہ طریقہ مناسب نہیں رہتا جو صرف خراب دن میں سامنے آتی ہے۔

Memory۔ Build آپ کی live application کے ساتھ compilers اور bundlers چلاتا ہے، اور زیادہ تر stacks میں یہی components سب سے زیادہ memory استعمال کرتے ہیں۔ 1 GB VPS پر JavaScript bundler یا Rust compile عموماً server کا سب سے بڑا process ہوتا ہے۔ جب kernel کی memory ختم ہو جاتی ہے تو یہ سب سے بڑے process کو kill کرتا ہے: یا تو build Killed اور exit status 137 کے ساتھ رک جاتی ہے، یا database ہی kill ہو جاتا ہے اور deploy کے دوران site بند ہو جاتی ہے۔ dmesg -T | grep -i oom process name کے ساتھ kill line دکھاتا ہے، اس لیے آپ اندازہ لگانے کے بجائے معلوم کر سکتے ہیں کہ دونوں میں سے کیا ہوا۔

Disk۔ ہر build اپنے layers پیچھے چھوڑتی ہے، اور builder آپ کی images سے الگ اپنا cache رکھتا ہے۔ docker system df دونوں دکھاتا ہے، اور build cache row مسلسل بڑھتی رہتی ہے۔ dangling images کے لیے docker image prune اور cached layers کے لیے docker builder prune سے جگہ واپس حاصل کریں۔ مکمل disk صرف build کو نہیں روکتی۔ Database بھی لکھنا بند کر دیتا ہے، اور اس failure کی قیمت slow deploy سے کہیں زیادہ ہوتی ہے۔

Reproducibility۔ Server پر build کی گئی image صرف اسی server پر موجود ہوتی ہے۔ Rollback کے لیے پرانا commit checkout کر کے دوبارہ build کرنا پڑتا ہے، اور اس build سے وہی output ملنے کی ضمانت نہیں ہوتی، کیونکہ base tag تبدیل ہو چکا ہوتا ہے اور package mirrors بھی اسی دوران بدل چکے ہوتے ہیں۔ کسی اور جگہ build کر کے tag push کرنے سے rollback ایک edit بن جاتا ہے: image: کو پچھلے tag کی طرف point کریں اور docker compose up -d چلائیں۔

جو arrangement قابلِ اعتماد رہتا ہے وہ سادہ ہے۔ آپ کا continuous integration system build چلاتا ہے اور registry.example.com/acme/web:<git-sha> push کرتا ہے، جبکہ VPS پر موجود Compose file میں image: ہوتا ہے اور اس میں build: key بالکل نہیں ہوتی۔ اس کے بعد deployment کے لیے دو ایسے commands درکار ہوتے ہیں جو تقریباً کوئی memory استعمال نہیں کرتے۔

docker compose pull
docker compose up -d

Server پر docker login registry.example.com ایک بار چلائیں، پھر Compose private tags pull کر سکتا ہے۔

Development کے لیے build section برقرار رکھیں، اسے حذف نہ کریں، بلکہ اپنی پسند کے نام والی file میں رکھیں۔

# compose.dev.yaml
services:
  web:
    build:
      context: .
    pull_policy: build
docker compose -f compose.yaml -f compose.dev.yaml up -d --build

اس file کا نام compose.dev.yaml رکھیں، compose.override.yaml نہیں۔ Compose جب بھی override file موجود ہو اسے خودکار طور پر load کرتا ہے، اس لیے server پر copy کی گئی کوئی غیر ضروری override خاموشی سے دوبارہ build شروع کر سکتی ہے۔ متعدد Compose files کی layering بتاتی ہے کہ merge ہر key کو کیسے resolve کرتا ہے۔

دوسری جگہ build کرتے وقت architecture کا مسئلہ

Image میں وہ CPU architecture شامل ہوتی ہے جس کے لیے اسے build کیا گیا ہو۔ Apple Silicon laptop پر build کر کے اسے push کریں، پھر اسی tag کو x86_64 VPS پر pull کریں تو Docker خبردار کرتا ہے کہ مطلوبہ image platform، detected host platform سے مطابقت نہیں رکھتا۔ اس کے بعد process exec format error کے ساتھ بند ہو جاتا ہے۔ یہ پیغام corrupt binary جیسا دکھائی دیتا ہے، لیکن binary corrupt نہیں ہوتی۔ Target کے لیے واضح طور پر build کریں:

docker buildx build --platform linux/amd64 \
  -t registry.example.com/acme/web:1.4.2 --push .

اگر laptop کا architecture x86 ہو اور آپ x86 کے بجائے ARM VPS استعمال کریں تو یہی عدم مطابقت الٹی سمت میں بھی پیش آتی ہے۔ CI کو اسی architecture پر build کرنے دیں جس پر آپ deployment کرتے ہیں۔ اس طرح یہ سوال ختم ہو جاتا ہے۔

تعیناتی کے بعد کیا جانچیں

  • docker compose images ہر چلتے ہوئے container کے پیچھے موجود image اور tag دکھاتا ہے۔ تبدیل شدہ image ID اس بات کا ثبوت ہے کہ نئی build سروس میں ہے۔
  • docker compose config variable substitution کے بعد merged file دکھاتا ہے، اس لیے آپ کچھ بھی چلانے سے پہلے Compose کے استعمال کردہ حتمی image name کو پڑھ سکتے ہیں۔
  • swap کے بعد پہلے 30 seconds کے لیے docker compose logs -f web چلائیں۔ جو container start ہو کر exit ہو جاتا ہے، وہ چلتا رہنے کے بجائے loop میں restart ہوتا ہے، اور جب تک آپ اسے دیکھیں نہیں، یہ loop خاموش رہتا ہے۔
  • docker image ls ایک CREATED column دکھاتا ہے۔ آپ کے آخری commit سے پرانی image کبھی rebuild نہیں ہوئی۔

اگر آپ ابھی وہ file تیار کر رہے ہیں جس پر یہ checks چلائی جائیں گی، تو VPS پر Compose file کی بنیادی باتیں متعلقہ keys کا احاطہ کرتا ہے، جبکہ Compose command cheat sheet باقی subcommands کی فہرست دیتا ہے۔

FAQ

کیا میں ایک ہی service میں build اور image استعمال کر سکتا ہوں؟

جی ہاں، اور خود build کیے جانے والے project کے لیے یہی معمول کی ترتیب ہے۔ Compose build: section سے build کرتا ہے اور نتیجے کو image: کی value سے tag کرتا ہے۔ یہی tag docker compose push registry کو بھیجتا ہے اور دوسری machine اسے pull کرتی ہے۔ image: key کے بغیر بھی Compose build کرتا ہے، لیکن image کا نام project اور service کے نام پر رکھتا ہے، اور warning دیتا ہے کہ یہ missing attribute image کو push ہونے سے روکتا ہے۔

docker compose up میری Dockerfile میں کی گئی تبدیلی کیوں نہیں اپناتا؟

کیونکہ up صرف یہ check کرتا ہے کہ اس نام کی image موجود ہے یا نہیں۔ جب image موجود ہو تو Compose اسے start کرتا ہے اور اسے Dockerfile یا source files سے compare نہیں کرتا۔ docker compose up -d --build چلائیں، یا کسی ایک service کو replace کرنے کے لیے docker compose build web کے بعد docker compose up --no-deps -d web چلائیں۔ Service پر pull_policy: build set کرنے سے ہر up rebuild ہوتا ہے، جو development machine کے لیے موزوں ہے۔

--build اور --force-recreate میں کیا فرق ہے؟

--build image کو دوبارہ build کرتا ہے، پھر ان containers کو recreate کرتا ہے جن کی image تبدیل ہوئی ہو۔ --force-recreate موجودہ image سے containers کو recreate کرتا ہے، اس لیے یہ code change کو کبھی نہیں اپناتا۔ اگر تبدیلی source یا Dockerfile میں ہے تو --build وہ flag ہے جو آپ کو درکار ہے۔ --force-recreate خود container کو reset کرنے کے لیے ہے، مثلاً اسی image کو برقرار رکھتے ہوئے اس کی writable layer صاف کرنے کے لیے۔

کیا مجھے اپنی Docker images VPS پر build کرنی چاہییں یا کہیں اور؟

Build کہیں اور کریں اور جب box network traffic بھی serve کرنے لگے تو tag pull کریں۔ Build آپ کی application کے ساتھ memory کے لیے مقابلہ کرتی ہے۔ چھوٹا VPS اس مقابلے کو اس طرح حل کرتا ہے کہ kernel سب سے بڑے process کو kill کر دیتا ہے، جو build بھی ہو سکتا ہے یا database بھی۔ Builds disk پر cache بھی چھوڑتی ہیں، جسے کوئی خودکار طور پر reclaim نہیں کرتا۔ کم users والے چھوٹے project کے لیے server پر build کرنا ٹھیک ہے۔ بعد میں منتقل ہونا آسان رہتا ہے، اگر آپ build: section کو development-only Compose file میں رکھیں۔

Docker build cache کو میری disk بھرنے سے کیسے روکوں؟

یہ دیکھنے کے لیے docker system df چلائیں کہ آپ کی images اور build cache میں سے ہر ایک کتنی جگہ استعمال کر رہا ہے۔ docker builder prune cached layers کو remove کرتا ہے، جبکہ docker image prune پچھلی builds سے بچ جانے والی dangling images کو remove کرتا ہے۔ دونوں میں سے کسی ایک کے ساتھ -a شامل کرنا زیادہ سخت صفائی کرتا ہے اور اگلی build کو cold start سے شروع ہونے پر مجبور کرتا ہے۔ Server پر docker system prune -af --volumes schedule نہ کریں، کیونکہ --volumes ایسا volume delete کر دیتا ہے جسے اس وقت کوئی container استعمال نہیں کر رہا ہو۔ Maintenance کے لیے روکا گیا stack آپ کا database عین ایسے ہی volume میں رکھتا ہے۔