SSD Nodes Learn 🎉 VPS $5.50/মাস থেকে
নির্দেশিকা Matt Connorদ্বারা Matt Connor

VPS-এ Docker Compose-এর build বনাম image

image প্রকাশিত tag pull করে, আর build VPS-এ Dockerfile থেকে image তৈরি করে। Dockerfile বদলালেও compose up কেন পুরোনো image চালায় এবং সমাধান কী, তা জানুন।

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 করা যায়।

এটাই মূল পার্থক্য। নিচের অংশে সার্ভারে এর ব্যবহারিক অর্থ ব্যাখ্যা করা হয়েছে। এখানে ধরে নেওয়া হয়েছে যে Docker Engine এবং Compose plugin আগে থেকেই ইনস্টল করা আছে; VPS-এ Docker চালানো অংশে সেই প্রক্রিয়া ব্যাখ্যা করা হয়েছে।

সম্পূর্ণ তিনটি পদ্ধতি

প্রকাশিত একটি tag pull করে সেটি চালান। কোনো পর্যায়েই 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 সেই নির্দিষ্ট tag-টি registry-তে পাঠাতে পারে।

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

context হলো builder-এর কাছে পাঠানো directory। dockerfile-এর path এই context-এর relative হিসেবে নির্ধারিত হয়। তাই context: .-এর সঙ্গে dockerfile: docker/prod.Dockerfile ব্যবহার করা স্বাভাবিক এবং সঠিক। প্রতিটি service container-এর পেছনে থাকা image name এবং image ID দেখতে docker compose images চালান। আপনি এই তিনটি পদ্ধতির কোনটি আসলে লিখেছেন, তা নিশ্চিত করার এটিই দ্রুততম উপায়।

Dockerfile পরিবর্তন করার পর docker compose up কেন পুনর্নির্মাণ করে না?

কারণ up image বর্তমান কি না তা নয়, image আছে কি না তা পরীক্ষা করে।

Compose যখন একটি service চালু করে এবং তাতে build: section থাকে, তখন এটি local image store-এ image খোঁজে। ওই নামে কোনো image আগে থেকেই থাকলে Compose সেটিই ব্যবহার করে। এটি আপনার Dockerfile পড়ে না, source file-এর সঙ্গে তুলনা করে না এবং কোনো timestamp-ও পরীক্ষা করে না। Compose specification-এ এই নিয়মটি pull_policy attribute হিসেবে নির্ধারিত আছে। Default আচরণ হলো image না থাকলেই image build করা। Image উপস্থিত থাকাকে যথেষ্ট ধরা হয়।

তাই আপনি app.py সম্পাদনা করে docker compose up -d চালান, Compose container-কে running দেখায়, কিন্তু পুরোনো code-ই পরিবেশন হয়। কোনো কিছু ব্যর্থ হয়নি, তাই কোনো সতর্কতাও দেখানো হয়নি। Compose ব্যবহারের ক্ষেত্রে “আমার পরিবর্তন কার্যকর হয়নি” সমস্যার এটি সবচেয়ে সাধারণ উদাহরণ। Compose container name-এর পাশে যে status word দেখায়, সেটিই মূল ইঙ্গিত: Compose যে container প্রতিস্থাপন করেছে, সেটি recreated বা started হিসেবে দেখায়; আর Compose যে container অপরিবর্তিত রাখার সিদ্ধান্ত নিয়েছে, সেটি running হিসেবে দেখায়।

দুটি পরীক্ষা করলেই বিষয়টি নিশ্চিত করা যায়। docker compose images প্রতিটি container যে image ID ব্যবহার করছে তা দেখায়। তাই deploy-এর আগে ID লিখে রাখুন এবং পরে তার সঙ্গে তুলনা করুন। docker image ls-এ CREATED column থাকে। আপনার সর্বশেষ commit-এর আগেই তৈরি হওয়া image deploy script যা-ই দেখাক, সেটি stale image।

কোন 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-এর বাকি অংশ চালু থাকে।
  • docker compose build --no-cache web সব cached layer বাদ দিয়ে প্রথম instruction থেকে rebuild করে।
  • docker compose build --pull FROM-এ base image-এর নতুন সংস্করণ pull করার চেষ্টা করে। তাই node:22-এর মতো moving tag ব্যবহার করলে March-এ download করা copy-এর বদলে তার বর্তমান contents ব্যবহৃত হয়।
  • docker compose up -d --force-recreate container-গুলো বর্তমানে যে image ব্যবহার করছে, সেই image থেকে সেগুলো পুনরায় তৈরি করে। এটি কখনো build করে না। --build ব্যবহার করার কথা থাকলেও এটি ব্যবহার করা একটি সাধারণ dead end।

আপনি এই সিদ্ধান্ত file-এর মধ্যেও নির্ধারণ করতে পারেন। Compose specification-এর ভাষায়, pull_policy: build অর্থ Compose image build করবে এবং image আগে থেকে থাকলে সেটি rebuild করবে। ফলে প্রতিবার up চালালে build করতে সময় লাগে। Laptop-এ এটি প্রয়োজনীয় হতে পারে, কিন্তু server-এ সাধারণত নয়।

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

আরেকটি পারস্পরিক প্রভাব জানা দরকার। যেসব service-এ build section আছে, docker compose pull সেগুলোর image-ও pull করার চেষ্টা করে। সেই pull ব্যর্থ হলে এটি জানায় যে image-টি build করতে হবে। ওই service-গুলো নীরবে বাদ দিতে --ignore-buildable দিন।

build cache কীভাবে deploy-এর সময় নির্ধারণ করে

Dockerfile-এর প্রতিটি instruction একটি layer তৈরি করে। সেই instruction এবং তার input অপরিবর্তিত থাকলে builder cached layer পুনরায় ব্যবহার করে। COPY-এর ক্ষেত্রে input হলো copy করা file-গুলোর contents। কোনো একটি layer cache miss করলে তার পরের প্রতিটি layer আবার build করা হয়। কারণ প্রতিটি layer তার আগের layer তৈরি করা filesystem-এর ওপর build হয়।

এই একটি নিয়মই নির্ধারণ করে আপনার deploy কয়েক সেকেন্ডে হবে, নাকি কয়েক মিনিট লাগবে। 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 cache-এ থাকে এবং build copy step থেকে আবার শুরু হয়। এই দুই line-এর ক্রম বদলালে একটি character পরিবর্তনের জন্যও প্রতিটি dependency আবার install হয়। কারণ COPY . ., যে layer-এর ওপর npm ci build হয় সেটিকে invalid করে। একই কাঠামো pip install -r requirements.txt এবং go mod download-এর ক্ষেত্রেও প্রযোজ্য।

পুরোনো কোনো layer আপনার fix আড়াল করছে বলে সন্দেহ হলে --no-cache ব্যবহার করাই সঠিক উপায়। তবে এটিকে default হিসেবে ব্যবহার করা ঠিক নয়। কারণ Dockerfile-এর ক্রম ঠিক করে যে layer reuse পাওয়ার কথা, এটি তা বাতিল করে দেয়।

Image সেট করে, আর Compose তা override করতে পারে। Dockerfile-এর CMD নির্ধারণ করে image ডিফল্টভাবে কী চালাবে। Service-এ থাকা command: key সেটিকে প্রতিস্থাপন করে। command এবং entrypoint কীভাবে একসঙ্গে কাজ করে এখানে গুরুত্বপূর্ণ। কারণ Compose override করলে সদ্য build করা image-ও পুরোনোটির মতো আচরণ করতে পারে।

Build context এবং .dockerignore

context: . ওই directory-টি প্যাকেজ করে এবং প্রথম instruction চালানোর আগে builder-এর কাছে পাঠায়। এর ভেতরের সবকিছু পাঠানো হয়, যার মধ্যে .git এবং source-এর পাশে রাখা যেকোনো data directory-ও রয়েছে। অপরিবর্তিত project-এর build যদি transferring-context ধাপে থেমে থাকে, তাহলে বুঝতে হবে context অতিরিক্ত বড়।

Context-এর root-এ থাকা একটি .dockerignore file এই transfer থেকে path বাদ দেয়। এর syntax .gitignore-এর কাছাকাছি।

.git
node_modules
*.log
data/
.env

এতে দুটি সুবিধা হয়। Transfer-এর আকার কমে, তাই প্রতিটি build দ্রুত শুরু হয়। আর COPY . . এখন আর .env image-এর মধ্যে copy করতে পারে না; ফলে যে কেউ image pull করলেও সেখান থেকে এটি পড়ে নিতে পারবে না।

সময়ের সঙ্গে ধীর হতে থাকা build-এর একটি সাধারণ কারণ হলো bind mount। Named volume আপনার project directory-এর বাইরে থাকে। কিন্তু ./data:/var/lib/postgresql/data-এর মতো bind mount build context-এর ভেতরে থাকে। তাই database বড় হওয়ার সঙ্গে সঙ্গে প্রতি সপ্তাহে build ধীর হয়। .dockerignore-এ একটি লাইন যোগ করলেই এটি ঠিক হয়। বিস্তৃত trade-off-এর জন্য Named volume-এর পরিবর্তে bind mount দেখুন।

Build argument-এ একই ঝুঁকির ছোট সংস্করণ থাকে। args:-এর মাধ্যমে পাঠানো value image ধরে রাখা যেকোনো ব্যক্তির কাছে image history-তে দৃশ্যমান থাকে। তাই সেখানে version number দিন, token কখনো দেবেন না। Credential কোথায় রাখা উচিত, তা জানতে Compose-এ Env file এবং secret দেখুন।

VPS-এ build করবেন, নাকি অন্যত্র build করে pull করবেন?

আপনার network traffic পরিবেশনকারী একই box-এ build করাই default, কারণ এটিই সবচেয়ে সংক্ষিপ্ত পথ: git pull, তারপর docker compose up -d --build। এমন ছোট server-এ এটি ঠিক আছে, যার ওপর এখনো কেউ নির্ভর করছে না। তবে পরিমাপ করা যায় এমন 2টি কারণে এবং খারাপ দিনে প্রকাশ পায় এমন আরও 1টি কারণে এটি আর উপযুক্ত থাকে না।

Memory। একটি build live application-এর পাশে compiler ও bundler চালায়, এবং অধিকাংশ stack-এ এগুলোই বেশি memory ব্যবহার করে। 1 GB VPS-এ JavaScript bundler বা Rust compile সাধারণত box-এর সবচেয়ে বড় process হয়। kernel-এর memory শেষ হয়ে গেলে এটি সবচেয়ে বড় process-টি kill করে: হয় build Killed এবং exit status 137 দেখিয়ে থেমে যায়, অথবা database-ই kill হয় এবং deploy চলাকালীন site বন্ধ হয়ে যায়। dmesg -T | grep -i oom process name-সহ kill হওয়ার line দেখায়। ফলে অনুমান না করে 2টির মধ্যে কোনটি ঘটেছে তা বুঝতে পারবেন।

Disk। প্রতিটি build কিছু layer রেখে যায়, এবং builder আপনার image থেকে আলাদাভাবে নিজের cache সংরক্ষণ করে। docker system df উভয়ই দেখায়, এবং build cache row ক্রমশ বড় হয়। dangling image-এর জন্য docker image prune এবং cached layer-এর জন্য docker builder prune ব্যবহার করে স্থান পুনরুদ্ধার করুন। Disk পূর্ণ হলে শুধু build বন্ধ হয় না। Database-ও লেখা বন্ধ করে, এবং এই ব্যর্থতার খরচ ধীর deploy-এর চেয়ে অনেক বেশি।

Reproducibility। server-এ build করা image শুধু সেই server-এই থাকে। Rollback করতে পুরনো commit checkout করে আবার build করতে হয়। সেই build আগের ফল দেবে, এমন নিশ্চয়তা নেই, কারণ base tag এবং তার সঙ্গে package mirror-ও পরিবর্তিত হয়েছে। অন্যত্র build করে একটি tag push করলে rollback শুধু একটি edit-এ পরিণত হয়: image:-কে আগের tag-এ নির্দেশ করে docker compose up -d চালান।

টেকসই arrangement-টি সরল। আপনার continuous integration build চালিয়ে registry.example.com/acme/web:<git-sha> push করবে, এবং VPS-এর Compose file-এ image: থাকবে, কিন্তু কোনো build: key থাকবে না। এরপর deployment-এর জন্য প্রায় কোনো memory প্রয়োজন হয় না—শুধু 2টি command লাগে।

docker compose pull
docker compose up -d

server-এ একবার docker login registry.example.com চালান। এরপর Compose private tag pull করতে পারবে।

build section মুছে না ফেলে development-এর জন্য রাখুন। এটি আপনার পছন্দের নামে একটি 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-এ ভুল করে একটি override copy করা থাকলে সেখানে আবার নীরবে build শুরু হয়ে যাবে। একাধিক Compose file স্তরীকরণ প্রতিটি key-এর merge কীভাবে নির্ধারিত হয় তা ব্যাখ্যা করে।

আপনি অন্য পরিবেশে build করলে architecture-সংক্রান্ত সমস্যা

একটি image যে CPU architecture-এর জন্য build করা হয়েছে, সেটি image-এর মধ্যেই নির্ধারিত থাকে। Apple Silicon laptop-এ build করে image push করার পর সেই tag x86_64 VPS-এ pull করলে Docker সতর্ক করে জানায় যে অনুরোধ করা image platform শনাক্ত করা host platform-এর সঙ্গে মেলে না। এরপর process exec format error দিয়ে বন্ধ হয়ে যায়। বার্তাটি corrupt binary বোঝালেও আসলে binary corrupt নয়। Target architecture স্পষ্টভাবে নির্ধারণ করে build করুন:

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

আপনার laptop x86 হলে এবং x86 VPS-এর বদলে ARM VPS ব্যবহার করলে একই অমিল উল্টো দিকেও ঘটবে। আপনি যে architecture-এ deploy করবেন, CI-কে সেই architecture-এই build করতে দিলে এই সমস্যা এড়ানো যায়।

ডিপ্লয় করার পরে যা পরীক্ষা করবেন

  • docker compose images প্রতিটি চলমান container-এর পেছনে থাকা image এবং tag দেখায়। image ID পরিবর্তিত হওয়া প্রমাণ করে যে নতুন build চালু হয়েছে।
  • docker compose config variable substitution-এর পরে merge করা file দেখায়। তাই কিছু চালানোর আগে Compose যে চূড়ান্ত image name ব্যবহার করবে, তা পড়ে যাচাই করতে পারবেন।
  • swap করার পর প্রথম 30 সেকেন্ড docker compose logs -f web করুন। কোনো container চালু হয়ে আবার বন্ধ হলে সেটি স্থিরভাবে চলার বদলে একটি loop-এ restart হয়। আপনি পরীক্ষা না করলে এই loop নীরবে চলতে থাকে।
  • docker image ls একটি CREATED column দেখায়। আপনার সর্বশেষ commit-এর চেয়ে পুরোনো image কখনো rebuild করা হয়নি।

আপনি যদি এখনও এই check-গুলো যে file-এর ওপর চলবে সেটি তৈরি করে থাকেন, VPS-এ Compose file-এর মৌলিক বিষয়গুলো-তে সংশ্লিষ্ট key-গুলোর ব্যাখ্যা আছে। Compose command cheat sheet-এ বাকি subcommand-গুলোর তালিকা রয়েছে।

FAQ

build এবং image কি একই service-এ ব্যবহার করা যায়?

হ্যাঁ, এবং নিজে build করা কোনো project-এর জন্য এটিই স্বাভাবিক setup। Compose build: section থেকে build করে এবং ফলাফলকে image:-এর value দিয়ে tag করে। docker compose push registry-তে পাঠানোর সময় এই tag ব্যবহার করে, এবং অন্য machine-ও এই tag pull করে। image: key না থাকলেও Compose build করে, তবে project ও service-এর নাম মিলিয়ে image-এর নাম দেয় এবং সতর্ক করে যে অনুপস্থিত attribute-এর কারণে image push করা যাবে না।

আমার Dockerfile পরিবর্তন করলে docker compose up তা গ্রহণ করে না কেন?

কারণ up শুধু ওই নামে কোনো image আছে কি না তা পরীক্ষা করে। image থাকলে Compose সেটিই start করে এবং আপনার Dockerfile বা source file-এর সঙ্গে তার তুলনা করে না। docker compose up -d --build চালান, অথবা একটি নির্দিষ্ট service প্রতিস্থাপনের জন্য docker compose build web-এর পরে docker compose up --no-deps -d web চালান। service-এ pull_policy: build সেট করলে প্রতিটি up rebuild হয়, যা development machine-এর জন্য উপযোগী।

--build এবং --force-recreate-এর মধ্যে পার্থক্য কী?

--build image আবার build করে, তারপর যেসব container-এর image পরিবর্তিত হয়েছে সেগুলো recreate করে। --force-recreate container-গুলোতে থাকা আগের image ব্যবহার করেই recreate করে, তাই code পরিবর্তন কখনোই এতে ধরা পড়ে না। পরিবর্তনটি source বা Dockerfile-এ হলে --build হলো প্রয়োজনীয় flag। --force-recreate container নিজেই reset করার জন্য ব্যবহার করা হয়, যেমন একই image রেখে তার writable layer পরিষ্কার করতে।

Docker image কি VPS-এ build করব, নাকি অন্য কোথাও?

অন্য কোথাও build করুন এবং server-এ network traffic পরিবেশন শুরু হলে tag pull করুন। build আপনার application-এর সঙ্গে memory নিয়ে প্রতিযোগিতা করে। ছোট VPS-এ kernel সবচেয়ে বড় process kill করে এই প্রতিযোগিতা সামাল দিতে পারে। সেটি build process-ও হতে পারে, আবার database-ও হতে পারে। build disk-এ cache-ও রেখে যায়, যা স্বয়ংক্রিয়ভাবে পরিষ্কার হয় না। কোনো user না থাকা ছোট project-এর ক্ষেত্রে server-এ build করা ঠিক আছে। পরে স্থানান্তরের খরচও কম থাকে, যদি build: section-টি development-only Compose file-এ রাখেন।

Docker build cache যাতে disk পূর্ণ না করে, তা কীভাবে বন্ধ করব?

আপনার image এবং build cache কতটা space ব্যবহার করছে তা দেখতে docker system df চালান। docker builder prune cached layer সরায় এবং docker image prune আগের build-এর পর থাকা dangling image সরায়। যেকোনোটির সঙ্গে -a যোগ করলে আরও জোরালোভাবে cleanup হয় এবং পরবর্তী build শূন্য cache থেকে শুরু করতে বাধ্য হয়। server-এ docker system prune -af --volumes schedule করবেন না, কারণ --volumes বর্তমানে কোনো container ব্যবহার করছে না এমন যেকোনো volume মুছে দেয়। Maintenance-এর জন্য বন্ধ রাখা stack-এ আপনার database ঠিক এমন একটি volume-এই থাকতে পারে।