Docker একটি সমস্যার এত ভালো সমাধান করেছে যে সে গোটা মাঠ দখল করে নিয়েছে: "আমার মেশিনে তো চলছে"।

সে এটি করে আপনার অ্যাপ্লিকেশনকে তার দরকারি সবকিছুর সঙ্গে — রানটাইম, লাইব্রেরি, সিস্টেম প্যাকেজ, কনফিগারেশন — একটি আর্টিফ্যাক্টে মুড়ে দিয়ে, যা Docker যেখানেই চলুক হুবহু একইভাবে চলে।

নতুনরা যত ভুল করেন তার প্রায় সবগুলোর পেছনে একটিমাত্র অনুপস্থিত ধারণা। তাই আগে মডেল, পরে কমান্ড।

ইমেজ আর কনটেইনার

ধারণাটা এটাই। এটা ধরতে পারলে বাকি সব আপনাআপনি আসে।

ইমেজ একটি কেবল-পাঠ টেমপ্লেট। একটি রেসিপি। এতে আপনার অ্যাপ্লিকেশন আর তার নির্ভরতাগুলো থাকে, আর নিজে থেকে এটি কিছুই করে না।

কনটেইনার হলো একটি ইমেজের চলমান রূপ। একটি ইমেজ থেকে অনেকগুলো কনটেইনার চালু করা যায়, আর প্রত্যেকটি অন্যগুলো থেকে আলাদা।

যে উপমাটি খাটে: ইমেজ একটি ক্লাস, কনটেইনার একটি অবজেক্ট। অথবা: ইমেজ একটি ইনস্টলার, কনটেইনার হলো ইনস্টল হওয়া, চলমান প্রোগ্রামটি।

এটি কেন সঙ্গে সঙ্গেই গুরুত্বপূর্ণ: কনটেইনার ফেলে দেওয়ার জিনিস। সেটাই এর উদ্দেশ্য — আপনি ফেলে দেন আর নতুন একটা চালু করেন। আর এ কারণেই চলমান কনটেইনারের ভেতরে লেখা যেকোনো কিছু কনটেইনারটি সরানোর সঙ্গে সঙ্গে ধ্বংস হয়ে যায় — নতুনদের সঙ্গে ঘটা সবচেয়ে ভয়ংকর ঘটনা এটাই। সমাধান ভলিউম, নিচে আছে, আর কোনো ডেটাবেস কনটেইনারে বসানোর আগে ওটাই পড়া উচিত।

শেখার মতো কি?

সৎ উত্তর: আপনি কী ডিপ্লয় করেন তার ওপর নির্ভর করে।

শেখার মতো, যদি:

  • আপনার অ্যাপের নির্দিষ্ট ভার্সন লাগে — বিশেষ কোনো Node, বিশেষ কোনো Python, বিশেষ ভার্সনের ডেটাবেস।
  • একাধিক মানুষ এতে কাজ করেন, বা আপনি কখনো "আমার মেশিনে তো চলছে" বলেছেন।
  • আপনি এমন কয়েকটি সার্ভিস চালান যাদের একে অন্যের সঙ্গে কথা বলতে হয়।
  • আপনি ভিপিএস বা ক্লাউড প্ল্যাটফর্মে ডিপ্লয় করেন আর চান ডিপ্লয়টা পুনরুৎপাদনযোগ্য হোক।

সম্ভবত শেখার মতো নয়, যদি:

  • আপনি শেয়ার্ড হোস্টিংয়ে WordPress সাইট ডিপ্লয় করেন। Docker এখানে প্রযোজ্যই নয় — ওখানে এটি চালানো যায় না, আর আপনার দরকারও নেই।
  • ম্যানেজড প্ল্যাটফর্মে আপনার একটি স্ট্যাটিক সাইট আছে।

প্রথম বাদ দেওয়াটা স্পষ্ট করে বলা দরকার, কারণ এটি অহরহ আসে: শেয়ার্ড হোস্টিংয়ে Docker চলে না। কনটেইনারের জন্য কার্নেল-স্তরের এমন সুবিধা লাগে, যা রুট ছাড়া পাওয়া যায় না। ভিপিএসে যাওয়ার সবচেয়ে স্পষ্ট কারণগুলোর একটি এটি — দেখুন শেয়ার্ড বনাম ভিপিএস বনাম ডেডিকেটেড, আর রুট আছে এমন যেকোনো আনম্যানেজড ভিপিএস দিয়েই হবে, আমাদেরটাসহ।

যে কমান্ডগুলো গুরুত্বপূর্ণ

কয়েকশ আছে। আপনার লাগবে আটটার মতো।

bash
docker run -d -p 8080:80 --name web nginx   # ব্যাকগ্রাউন্ডে কনটেইনার চালু
docker ps                                    # কী কী চলছে
docker ps -a                                 # থেমে যাওয়াগুলোসহ
docker logs -f web                           # লগ দেখুন — আপনার প্রধান ডিবাগিং টুল
docker exec -it web sh                       # চলমান কনটেইনারের ভেতরে শেল
docker stop web && docker rm web             # থামান আর সরান
docker images                                # ডিস্কে কী কী ইমেজ আছে
docker system prune -a                       # জায়গা ফেরত নিন (সাবধান: অব্যবহৃত ইমেজ মুছে দেয়)

যেটি মাথায় গেঁথে নেওয়া দরকার তা হলো -p 8080:80: হোস্ট পোর্ট : কনটেইনার পোর্ট। কনটেইনারের ভেতরে আপনার অ্যাপ ৮০-তে শোনে; বাইরে থেকে আপনি ৮০৮০-তে পৌঁছান। এটি উল্টো করাই "সংযোগ করতে পারছি না কেন" মুহূর্তের সবচেয়ে সাধারণ কারণ।

ডিবাগিংয়ের বেশিরভাগ সময় যাবে docker logs-এ। এটি আগেভাগেই শিখে নিন।

Dockerfile লেখা

Dockerfile বলে দেয় আপনার ইমেজ কীভাবে বানাতে হবে। একটি Node অ্যাপের জন্য বাস্তবসম্মত উদাহরণ, দুটি গুরুত্বপূর্ণ জিনিস চিহ্নিত করে:

dockerfile
# ১. বিল্ড স্টেজ — টুলচেইন এখানে
FROM node:22-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# ২. রানটাইম স্টেজ — কেবল চালানোর জন্য যা লাগে
FROM node:22-alpine
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/dist ./dist
USER node
EXPOSE 3000
CMD ["node", "dist/server.js"]

দুটি জিনিস এটিকে কেবল "চলে" থেকে "ভালো" বানায়:

লেয়ার ক্যাশিং। Docker প্রতিটি নির্দেশ ক্যাশ করে আর ওপরের কিছু না বদলালে সেই ক্যাশ পুনর্ব্যবহার করে। package*.json কপি ও ইনস্টল করা হয় বাকি সোর্সের আগে, তাই একটি সোর্স ফাইল সম্পাদনা করলে npm ci আবার চলে না। আগে সবকিছু কপি করলে প্রতিটি বিল্ডে প্রতিটি নির্ভরতা আবার ইনস্টল হয় — কেবল এই ক্রমের সিদ্ধান্তটাই প্রায়ই ২০ সেকেন্ডের বিল্ড আর চার মিনিটের বিল্ডের পার্থক্য।

মাল্টি-স্টেজ বিল্ড। প্রথম স্টেজে কম্পাইলার আর ডেভ নির্ভরতা; দ্বিতীয়টি কেবল বানানো আউটপুট কপি করে। পাঠানো ইমেজটি আকারে ভগ্নাংশ, আর তাতে আপনার বিল্ড টুলচেইন থাকে না — যা আকারের পাশাপাশি নিরাপত্তারও উন্নতি।

USER node-ও লক্ষ করুন। কনটেইনার ডিফল্টে রুট হিসেবে চলে, যা অপ্রয়োজনীয় আর প্রতিটি নিরাপত্তা অডিটে ধরা পড়ে।

.dockerignore

এই ফাইলটি বানান। এখনই, প্রথম বিল্ডের আগেই:

text
node_modules
.git
.env
*.log
dist

এটি ছাড়া COPY . . আপনার গোটা node_modules, git ইতিহাস আর — এটাই গুরুত্বপূর্ণ — গোপন তথ্যসহ আপনার .env ফাইলটি ইমেজে কপি করে ফেলে। যে-ই সেই ইমেজ নামাবে, তার কাছে আপনার ক্রেডেনশিয়াল থাকবে। এটি একটি প্রচলিত আর সত্যিই গুরুতর ভুল।

ভলিউম: আপনার ডেটা যেখানে থাকে

ফেলে দেওয়ার প্রসঙ্গে ফিরি।

কনটেইনার নিজের ফাইলসিস্টেমে যা কিছু লেখে, কনটেইনারটি সরানোর সঙ্গে সঙ্গে তা হারিয়ে যায়। স্টেটবিহীন ওয়েব অ্যাপের জন্য এটাই কাঙ্ক্ষিত। ডেটাবেসের জন্য এটি বিপর্যয়, আর প্রথম চেষ্টাতেই মানুষ এভাবেই ডেটা হারায়।

ভলিউম হলো এমন স্টোরেজ, যা কনটেইনারের জীবনচক্রের বাইরে থাকে:

bash
docker run -d \
  -v pgdata:/var/lib/postgresql/data \
  -e POSTGRES_PASSWORD=secret \
  --name db postgres:17

pgdata হলো Docker-এর ব্যবস্থাপনায় থাকা একটি নামযুক্ত ভলিউম। কনটেইনারটি যত খুশি সরান আর নতুন করে বানান; ডেটা থেকে যাবে।

নিয়ম: হারালে সমস্যা হবে এমন কিছু ভলিউমে যায়। ডেটাবেস, আপলোড, তৈরি হওয়া ফাইল।

ভলিউম ব্যাকআপ নয়। এটি কনটেইনারের সঙ্গে একই মেশিনেই থাকে। docker system prune --volumes স্বচ্ছন্দে সেটি মুছে দেবে। আলাদাভাবে ব্যাকআপ নিন — দেখুন Linux সার্ভার সুরক্ষিত করার গাইড

Docker Compose

একটি কনটেইনার সহজ। সত্যিকারের অ্যাপ্লিকেশন তিন-চারটি — অ্যাপ, ডেটাবেস, ক্যাশ, প্রক্সি। Compose সবগুলোকে একটি ফাইলে বর্ণনা করে:

yaml
services:
  app:
    build: .
    ports:
      - "3000:3000"
    environment:
      DATABASE_URL: postgres://app:secret@db:5432/app
    depends_on:
      - db

  db:
    image: postgres:17
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: secret
    volumes:
      - pgdata:/var/lib/postgresql/data

volumes:
  pgdata:

তারপর docker compose up -d, আর গোটা স্ট্যাক চালু।

চমৎকার অংশটা নেটওয়ার্কিং: সার্ভিসগুলো একে অন্যের কাছে পৌঁছায় নাম ধরে। অ্যাপটি db:5432-এ যুক্ত হয় — localhost নয়, কোনো আইপি নয়। Compose এমন একটি নেটওয়ার্ক বানায়, যেখানে প্রতিটি সার্ভিসের নাম তার কনটেইনারে রিজলভ হয়। এটি বুঝে গেলে বহু-সার্ভিসের ব্যবস্থা হঠাৎ করেই সহজ হয়ে যায়।

depends_on কনটেইনার চালু হওয়া পর্যন্ত অপেক্ষা করে, ডেটাবেস সংযোগ নেওয়ার জন্য প্রস্তুত হওয়া পর্যন্ত নয়। আপনার অ্যাপকে প্রথম সংযোগ আবার চেষ্টা করতে হবে, নইলে প্রায় অর্ধেক সময় চালু হওয়ার সময়ই ক্র্যাশ করবে।

ভিপিএসে ডিপ্লয়

সবচেয়ে সরল ডিপ্লয়মেন্ট, যেটি লজ্জাজনক নয়:

bash
# ভিপিএসে, একবার
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER   # লগআউট করে আবার লগইন করুন

# তারপর, প্রতি ডিপ্লয়ে
git pull
docker compose build
docker compose up -d

সামনে একটি রিভার্স প্রক্সি বসান — Caddy বা Nginx — যা TLS সামলাবে আর কনটেইনারে রুট করবে। দুটির মধ্যে Caddy সহজ, কারণ এটি নিজেই সার্টিফিকেট নেয় ও রিনিউ করে।

চারটি জিনিস মানুষ বাদ দেয় আর পরে আফসোস করে:

  1. রিস্টার্ট নীতি বসান। restart: unless-stopped, নইলে রিবুটের পর আপনার অ্যাপ বন্ধই থাকবে।
  2. Compose ফাইলে গোপন তথ্য রাখবেন না, যদি সেটি git-এ থাকে। gitignore করা একটি .env ফাইল ব্যবহার করুন, নয়তো প্ল্যাটফর্মের সিক্রেট স্টোর।
  3. লগে সীমা বসান। কনটেইনারের লগ সীমাহীনভাবে বাড়ে আর ডিস্ক ভরে ফেলে — রহস্যজনকভাবে মরে যাওয়া সার্ভারের একটি সত্যিই প্রচলিত কারণ।
  4. হোস্ট হার্ডেন করুন। Docker এমন কোনো নিরাপত্তা সীমানা নয় যার ওপর একা ভরসা করা যায়, আর এটি সরাসরি iptables বদলে ufw-তে ফুটো করে — প্রকাশ করা কোনো কনটেইনার পোর্টে পৌঁছানো যেতে পারে, যদিও আপনার ফায়ারওয়াল অন্য কথা বলছে। বাইরে থেকে যাচাই করুন।

যে পাঁচটি ভুল সবাই করে

  1. .dockerignore না থাকা, যাতে গোপন তথ্য আর node_modules ইমেজে ঢুকে যায়।
  2. নির্ভরতা ইনস্টলের আগে সোর্স কপি করা, যাতে ক্যাশ কখনো কাজে লাগে না আর প্রতিটি বিল্ড ধীর।
  3. ভলিউম ছাড়া ডেটা রাখা, আর প্রথমবার কনটেইনার বদলাতেই সেটি হারানো।
  4. রুট হিসেবে চালানো, কারণ ওটাই ডিফল্ট আর কেউ অভিযোগ করে না।
  5. :latest ব্যবহার করা। এর মানে "যখন নামানো হয়েছিল তখন যেটি নতুন ছিল", যা পুনরুৎপাদনযোগ্য নয় আর পুরো উদ্দেশ্যটাই নষ্ট করে। ভার্সন পিন করুন: postgres:latest নয়, postgres:17

কনটেইনার চালু না হলে ডিবাগ করা

নতুনদের সবচেয়ে প্রচলিত অভিজ্ঞতা হলো এমন একটি কনটেইনার যা সঙ্গে সঙ্গেই বন্ধ হয়ে যায় — আর স্বভাবত যা করতে ইচ্ছে করে, আবার চালানো, তাতে কখনোই কাজ হয় না। যে পদ্ধতিতে হয়:

bash
docker ps -a                 # ওটা আছে, আর "Exited (1)"
docker logs <name>           # আসল কারণ, প্রায় সবসময়ই

থেমে যাওয়া কনটেইনারেও docker logs কাজ করে, আর উত্তরটা সাধারণত শেষ তিন লাইনে। সাধারণত যা পাবেন:

এক্সিট কোড 1 — আপনার অ্যাপ্লিকেশন ক্র্যাশ করেছে। এনভায়রনমেন্ট ভেরিয়েবল নেই, ডেটাবেসে পৌঁছানো যাচ্ছে না, বা একটি সিনট্যাক্স এরর। লগ পড়ুন; এটি সাধারণ একটি অ্যাপ্লিকেশন এরর, যা ঘটনাচক্রে কনটেইনারের ভেতরে।

এক্সিট কোড 127 — কমান্ড পাওয়া যায়নি। আপনার CMD এমন কিছুকে নির্দেশ করছে যা ইমেজে নেই। এক প্রকল্প থেকে আরেক প্রকল্পে Dockerfile কপি করলে, বা বাইনারিটি আপনার মেশিনে থাকলে আর বেস ইমেজে না থাকলে এটি সাধারণ।

এক্সিট কোড 137 — মেমোরি ফুরিয়ে মেরে ফেলা হয়েছে। কনটেইনার তার সীমা ছাড়িয়েছে, নয়তো হোস্টেরই মেমোরি ফুরিয়েছে। সীমা বাড়ান, নয়তো প্রসেসটি যা করে তা কমান।

চালু হয়ে সঙ্গে সঙ্গে 0 দিয়ে বেরিয়ে যায় — কনটেইনারের মূল প্রসেসটি শেষ হয়ে গেছে। কনটেইনার ততক্ষণই বাঁচে যতক্ষণ তার ফোরগ্রাউন্ড প্রসেস চলে; যে CMD ব্যাকগ্রাউন্ডে কিছু চালু করে ফিরে আসে, সেটি সঙ্গে সঙ্গেই বেরিয়ে যাবে। প্রসেসটি ফোরগ্রাউন্ডে চালান।

লগে যথেষ্ট না হলে একটি কাজ করা ইমেজের ভেতরে ঢুকে দেখুন:

bash
docker run --rm -it --entrypoint sh myimage   # CMD এড়িয়ে ভেতরে ঘুরুন
docker exec -it <running> sh                  # অথবা চলমান একটিতে দেখুন

--entrypoint sh মনে রাখার মতো। এটি আপনার অ্যাপ্লিকেশন না চালিয়েই ইমেজটি চালু করে, যাতে দেখতে পারেন প্রত্যাশিত ফাইলগুলো সত্যিই প্রত্যাশিত জায়গায় আছে কি না — আর উত্তরটা প্রায়ই "না", কোনো COPY পথ বা .dockerignore-এর কোনো এন্ট্রির কারণে।

সচরাচর জিজ্ঞাসা

ইমেজ আর কনটেইনারের পার্থক্য কী? ইমেজ একটি কেবল-পাঠ টেমপ্লেট — রেসিপি। কনটেইনার তার চলমান রূপ। একটি ইমেজ থেকে অনেক কনটেইনার হতে পারে, আর প্রতিটি কনটেইনারের নিজস্ব ফাইলসিস্টেম সেটি সরানোর সঙ্গে সঙ্গে মুছে যায়।

শেয়ার্ড হোস্টিংয়ে কি Docker চালানো যায়? না। কনটেইনারের জন্য এমন কার্নেল সুবিধা লাগে যার জন্য রুট অ্যাক্সেস দরকার, আর শেয়ার্ড হোস্টিং তা দেয় না। ভিপিএস বা ডেডিকেটেড সার্ভার লাগবে।

কনটেইনার রিস্টার্ট করলে আমার ডেটা হারিয়ে যায় কেন? কারণ নকশা অনুযায়ীই কনটেইনারের ফাইলসিস্টেম ক্ষণস্থায়ী। যা টিকে থাকতে হবে তা একটি নামযুক্ত ভলিউমে যায়, অ্যাপ্লিকেশন যে পথে লেখে সেখানে মাউন্ট করা।

সঙ্গে কি Kubernetes-ও লাগবে? প্রায় নিশ্চিতভাবেই না। Kubernetes অনেক মেশিনজুড়ে অর্কেস্ট্রেশনের সমস্যা সমাধান করে। হাতেগোনা কয়েকটি সার্ভিস চালানো একটি সার্ভারের জন্য Docker Compose-ই সঠিক টুল, আর Kubernetes বিনা লাভে বিপুল জটিলতা।

Docker কি নিরাপদ? যত্ন নিলে মোটামুটি। কনটেইনার রুট হিসেবে চালাবেন না, :latest ব্যবহার করবেন না, বেস ইমেজ হালনাগাদ রাখুন, আর কখনো ইমেজে গোপন তথ্য ঢোকাবেন না। মনে রাখবেন Docker সরাসরি iptables বদলায় আর UFW নিয়ম পেরিয়েও প্রকাশ করা পোর্ট উন্মুক্ত করতে পারে — সবসময় মেশিনের বাইরে থেকে যাচাই করুন।

ইমেজ ছোট করব কীভাবে? মাল্টি-স্টেজ বিল্ড ব্যবহার করুন যাতে টুলচেইন না যায়, Alpine বা slim বেস ইমেজ থেকে শুরু করুন, একটি .dockerignore যোগ করুন, আর যেসব RUN ধাপ ফাইল বানিয়ে পরে মোছে সেগুলো একসঙ্গে করুন — কারণ পরের লেয়ার ফাইল মুছে দিলেও প্রতিটি লেয়ার স্থায়ীভাবে জমা থাকে।