SSD Nodes Learn Hosting plans →
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-27

Ubuntu 24.04-এ Docker Compose সেটআপ করার নিয়ম

Ubuntu 24.04-এ Docker Engine এবং Compose v2 ইনস্টল করার সঠিক পদ্ধতি জানুন। UFW ফায়ারওয়াল এড়িয়ে পোর্ট পাবলিশিং এবং ডাটাবেস ভলিউম ব্যাকআপের গুরুত্বপূর্ণ টিপস এখানে দেওয়া হলো।

আপনি যা তৈরি করছেন

Docker Compose হলো এই সাইটের প্রায় সবকিছুর মূল ভিত্তি। Nextcloud, Vaultwarden, n8n, Immich, Rocket.Chat—এই প্রতিটি গাইডের শুরুতেই "এই compose ফাইলটি লিখুন" কথাটি থাকে, আর এই পৃষ্ঠায় ব্যাখ্যা করা হয়েছে সেই ফাইলের প্রকৃত অর্থ কী। আপনি Ubuntu 24.04-এ Docker-এর নিজস্ব apt repository থেকে Docker Engine এবং Compose v2 plugin ইনস্টল করবেন। এরপর আপনি দুটি সার্ভিসের একটি বাস্তব stack তৈরি করবেন: একটি ছোট RSS reader, Miniflux এবং তার সাথে PostgreSQL। কারণ এই জুটিটি বড় অ্যাপগুলোতে ব্যবহৃত প্রতিটি প্যাটার্নকে কার্যকর করে: pinned images, healthcheck সহ একটি ডাটাবেস, একটি named volume, .env ফাইলে secrets, এবং শুধুমাত্র localhost-এ প্রকাশিত একটি port।

ইনস্টলেশন সম্পন্ন করতে পাঁচ মিনিট সময় লাগবে। এই গাইডের বাকি অংশে এমন বিষয়গুলো নিয়ে আলোচনা করা হয়েছে যা পরবর্তীতে সমস্যার কারণ হতে পারে: docker group-এর root হিসেবে কাজ করা, ufw রুল উপেক্ষা করে সরাসরি port প্রকাশ করা, এবং docker compose down-এর সেই একটি flag যা কোনো নিশ্চিতকরণ বার্তা ছাড়াই আপনার ডাটাবেস মুছে ফেলে।

পূর্বশর্ত: একটি নতুন Ubuntu 24.04 KVM VPS, sudo সুবিধা সম্পন্ন একজন ব্যবহারকারী, এবং কমপক্ষে এক গিগাবাইট RAM। যদি আগে থেকেই Docker ইনস্টল করা থাকে তবে তাও চলবে, প্রথম অংশে কী কী মুছে ফেলতে হবে তা আলোচনা করা হয়েছে।

Ubuntu-এর রিপোজিটরি নয়, Docker-এর রিপোজিটরি থেকে ইনস্টল করুন

প্রথম কমান্ডটি চালানোর আগে দুটি ভুল পথ এড়িয়ে চলতে হবে। Ubuntu-এর নিজস্ব docker.io প্যাকেজটি কাজ করলেও এটি Docker-এর নতুন রিলিজের তুলনায় পিছিয়ে থাকে এবং এতে সেই প্লাগইন লেআউটটি থাকে না যা অন্যান্য সবকিছুর জন্য প্রয়োজন। এছাড়া, হাইফেনযুক্ত স্ট্যান্ডঅ্যালোন docker-compose বাইনারিটি হলো Compose v1: এটি Python-ভিত্তিক, 2023 সাল থেকে এর সাপোর্ট বন্ধ হয়ে গেছে এবং পুরনো টিউটোরিয়ালগুলো কাজ না করার এটিই প্রধান কারণ। বর্তমানের Compose হলো docker compose (স্পেসসহ), যা একটি CLI প্লাগইন এবং এটি ইঞ্জিন যে রিপোজিটরি থেকে আসে সেখান থেকেই ইনস্টল করতে হয়।

যদি এর মধ্যে কোনোটি আগে থেকেই সার্ভারে থাকে, তবে তা প্রথমে মুছে ফেলুন। এর মধ্যে Ubuntu-এর নিজস্ব প্যাকেজ docker-compose-v2-ও অন্তর্ভুক্ত, যাতে সবকিছু একটি মাত্র রিপোজিটরি থেকে আসে:

sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc

একটি নতুন VPS-এ Package 'docker.io' is not installed, so not removed আউটপুট আসা স্বাভাবিক। এরপর Docker-এর রিপোজিটরি যোগ করুন এবং ইনস্টল করুন:

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

তিনটি লেয়ারই যাচাই করুন:

docker --version
docker compose version
sudo docker run --rm hello-world

প্রথম দুটি ভার্সন স্ট্রিং প্রিন্ট করবে, Docker Compose version v2.x.x নিশ্চিত করবে যে আপনার কাছে প্লাগইনটি আছে, পুরনো v1 বাইনারিটি নেই। hello-world কমান্ডটি চালালে শেষে Hello from Docker! আসা উচিত। প্যাকেজটি বুট হওয়ার সময় সার্ভিসটি চালু করে দেয়; systemctl is-enabled docker কমান্ডটি enabled প্রিন্ট করবে।

docker গ্রুপটি root-এর সমতুল্য, তাই জেনে-বুঝে সিদ্ধান্ত নিন

বর্তমানে প্রতিটি docker কমান্ডের জন্য sudo প্রয়োজন হয়, কারণ /var/run/docker.sock-এ থাকা ডেমনের সকেটটির মালিকানা root এবং docker গ্রুপের। এই গ্রুপের সদস্য না হলে আপনি Docker-এর সবচেয়ে বেশিবার সার্চ করা এই ত্রুটিটির সম্মুখীন হবেন:

permission denied while trying to connect to the Docker daemon socket at
unix:///var/run/docker.sock

সাধারণ সমাধানটি হলো:

sudo usermod -aG docker $USER

গ্রুপের সদস্যপদ লগইন করার সময় কার্যকর হয়, তাই আপনার বর্তমান শেলে ত্রুটিটি থেকেই যাবে। এই সেশনের জন্য newgrp docker চালান, অথবা লগ আউট করে পুনরায় লগ ইন করুন; এরপর id কমান্ডটি দিলে আপনার গ্রুপগুলোর তালিকায় docker দেখা যাওয়ার কথা।

এখন বিষয়টি পরিষ্কারভাবে বলা যাক: docker গ্রুপের সদস্যপদ থাকা মানেই হোস্ট মেশিনে root হওয়া। এটি "root-এর মতো" বা "উন্নত ক্ষমতা" নয়, এটি সরাসরি root। এই গ্রুপের যে কেউ docker run --rm -it -v /:/host alpine chroot /host কমান্ডটি চালিয়ে পুরো ফাইলসিস্টেমের মালিকানা নিতে পারে, কোনো পাসওয়ার্ড ছাড়াই। এই গ্রুপটি সুবিধার জন্য তৈরি করা হয়েছে, নিরাপত্তার জন্য নয়।

Docker-এর rootless mode হলো এর প্রকৃত বিকল্প, যেখানে ডেমোনটি আপনার সাধারণ ব্যবহারকারী হিসেবে চলে। এর কিছু সীমাবদ্ধতা আছে: 1024-এর নিচের পোর্টগুলোর জন্য বাড়তি সেটআপ প্রয়োজন, নেটওয়ার্কিং একটি ইউজারস্পেস শিমের মাধ্যমে চলে যার কিছুটা ওভারহেড আছে, এবং কিছু ইমেজ প্রকৃত root ছাড়া ঠিকমতো কাজ করে না। একজন অ্যাডমিন থাকা VPS-এ, যেখানে লগইন করা ব্যবহারকারীর কাছে ইতিমধ্যেই sudo ক্ষমতা আছে, সেখানে এই গ্রুপ পরিবর্তনের ফলে বাস্তবে তেমন কোনো পার্থক্য তৈরি হয় না। এই গাইডের প্রতিটি ধাপে এটিই ধরে নেওয়া হয়েছে, তবে মনে রাখবেন, এটি sudo-এর চেয়ে কম কিছু নয়।

Compose file-এর গঠন

প্রতিটি stack-এর জন্য আলাদা ডিরেক্টরি রাখুন। ডিরেক্টরির নামটিই project-এর নাম হিসেবে গণ্য হয়, যা কন্টেইনার, নেটওয়ার্ক এবং ভলিউমের নামের শুরুতে যুক্ত হয়:

sudo mkdir -p /opt/miniflux && sudo chown $USER /opt/miniflux && cd /opt/miniflux

compose.yml তৈরি করুন (এটি আধুনিক নাম; docker-compose.yml এখনও কাজ করে)। পুরনো version: কী (key) এড়িয়ে চলুন, এটি এখন অকেজো এবং Compose এটি দেখলে সতর্কবার্তা দেয়।

services:
  miniflux:
    image: miniflux/miniflux:2.2.9
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:8080"
    environment:
      - DATABASE_URL=postgres://miniflux:${POSTGRES_PASSWORD}@db/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=${ADMIN_PASSWORD}
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=miniflux
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux", "-d", "miniflux"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  db-data:

উপরের প্রতিটি লাইন একটি সিদ্ধান্ত। একটি একটি করে সিদ্ধান্ত নিন।

ইমেজ ভার্সন নির্দিষ্ট করুন, :latest এবং pull করা মানেই অনিয়ন্ত্রিত আপডেট

postgres:16-alpine ব্যবহার করুন, postgres:latest নয়। একটি ট্যাগ মানেই স্থির কিছু নয়: :latest প্রতিবার pull করার সময় মেইনটেইনার সর্বশেষ যা পুশ করেছেন, সেটিই পুনরায় রি-সলভ করে। এর সাথে আপনার নিয়মিত আপডেট করার অভ্যাস, docker compose pull && docker compose up -d, যোগ করলে :latest-এর মানে দাঁড়ায় যে, যখনই upstream নতুন ভার্সন রিলিজ করবে, আপনার সিস্টেমে বড় ধরনের ভার্সন পরিবর্তন (major-version jump) চলে আসবে, আপনার ইচ্ছা থাকুক বা না থাকুক। PostgreSQL-এর ক্ষেত্রে এটি কেবল তাত্ত্বিক নয়: 16 থেকে 17-তে হঠাৎ পরিবর্তন হলে কন্টেইনারটি অসামঞ্জস্যপূর্ণ ডেটা ডিরেক্টরির কারণে ক্র্যাশ-লুপে পড়ে যায়, কারণ Postgres-এর বড় আপগ্রেডের জন্য রিস্টার্ট নয়, বরং dump এবং restore প্রয়োজন।

অন্তত major version নির্দিষ্ট করুন (postgres:16-alpine, 16.x প্যাচ রিলিজ অনুসরণ করে), এবং অ্যাপ্লিকেশনগুলোকে নির্দিষ্ট রিলিজ যেমন miniflux/miniflux:2.2.9-এ লক করে রাখুন। প্রজেক্টের রিলিজ পেজ চেক করুন এবং ফাইলটি লেখার সময় যা বর্তমান, সেটি ব্যবহার করুন। তখন আপগ্রেড করা মানে হবে আপনার ইচ্ছাকৃত একটি লাইনের পরিবর্তন, যা git diff-এ দেখা যাবে।

127.0.0.1-এ পাবলিশ করুন, কারণ Docker ufw-কে এড়িয়ে চলে

"127.0.0.1:8080:8080", হোস্ট অ্যাড্রেস, হোস্ট পোর্ট, কন্টেইনার পোর্ট। বেশিরভাগ টিউটোরিয়ালে "8080:8080" লেখা থাকে, যা মূলত 0.0.0.0:8080:8080-এর সংক্ষিপ্ত রূপ: এটি পাবলিক ইন্টারফেসসহ সব ইন্টারফেসে লিসেন করে।

এখানেই সেই ফাঁদটি, যা প্রায় সবাই একবার না একবার ভোগেন। Docker একটি পোর্ট পাবলিশ করার সময় DNAT রুল লেখে, যা ফিল্টারিংয়ের আগেই প্যাকেটের গন্তব্যকে কন্টেইনারের অভ্যন্তরীণ IP-তে পরিবর্তন করে। ফলে প্যাকেটটি FORWARD পাথ দিয়ে চলে যায় এবং কখনোই INPUT-কে স্পর্শ করে না, যেখানে আপনার ufw রুলগুলো থাকে। sudo ufw deny 8080 সফল হওয়ার রিপোর্ট দেয়, ufw status দেখায় যে পোর্টটি ডিনাই করা হয়েছে, অথচ সার্ভিসটি পুরো ইন্টারনেটের কাছে উত্তর দিচ্ছে। আপনার ফায়ারওয়াল নষ্ট হয়নি; এটি ডিজাইন অনুযায়ীই বাইপাস হচ্ছে। কেন Docker ufw বাইপাস করে এবং কীভাবে কন্টেইনার ট্রাফিক সঠিকভাবে ফিল্টার করবেন নিবন্ধে এই মেকানিজম এবং যেসব পোর্ট পাবলিক রাখা জরুরি, সেগুলোর জন্য DOCKER-USER সমাধান নিয়ে আলোচনা করা হয়েছে।

যে অভ্যাসটি এই সমস্যা পুরোপুরি দূর করে: পাবলিশ করা পোর্টগুলোকে 127.0.0.1-এ বাইন্ড করুন (যদি না আপনার অন্য কোনো বিশেষ কারণ থাকে) এবং বাইরের জগতের জন্য উন্মুক্ত যেকোনো কিছুর সামনে একটি reverse proxy রাখুন। এই পৃষ্ঠার পরবর্তী ধাপে Traefik reverse proxy guide-এ ঠিক এটিই তৈরি করা হয়েছে—একটি কন্টেইনার যা 80 এবং 443 পোর্ট নিয়ন্ত্রণ করে এবং TLS-সহ হোস্টনাম অনুযায়ী বাকি সবকিছুর কাছে ট্রাফিক রাউট করে। (পুরনো Traefik v2 সেটআপ থেকে আসছেন? Traefik v2 থেকে v3 মাইগ্রেশন গাইড-এ নাম পরিবর্তন এবং রুল পরিবর্তনের বিষয়গুলো কভার করা হয়েছে।)

স্ট্যাক শুরু করার পর বাইন্ড যাচাই করুন: sudo ss -tlnp | grep 8080-এ 127.0.0.1:8080 দেখা উচিত, 0.0.0.0:8080 বা *:8080 নয়।

Named volumes বনাম bind mounts

db-data:/var/lib/postgresql/data হলো একটি named volume: Docker /var/lib/docker/volumes/-এর অধীনে একটি ডিরেক্টরি তৈরি ও পরিচালনা করে এবং সেটিকে কন্টেইনারের ভেতর মাউন্ট করে। এর বিকল্প হলো bind mount, ./data:/var/lib/postgresql/data, যা হোস্টের আপনার বেছে নেওয়া একটি পাথকে ম্যাপ করে।

বাস্তবে যে বিভাজনটি কার্যকর: শুধুমাত্র কন্টেইনার যে ডেটা ব্যবহার করে তার জন্য named volumes ব্যবহার করুন, বিশেষ করে ডেটাবেসের ক্ষেত্রে। কারণ Docker ইমেজ অনুযায়ী ভলিউমের মালিকানা (ownership) সেট করে দেয় এবং ফাইলের অনুমতিগুলো (permissions) সঠিকভাবে কাজ করে। হোস্ট থেকে আপনি যেসব ফাইল পরিবর্তন করেন তার জন্য bind mounts ব্যবহার করুন, যেমন কনফিগারেশন ফাইল যা আপনি টেক্সট এডিটরে এডিট করেন, অথবা মিডিয়া লাইব্রেরি যা আপনি rsync করেন—যেকোনো কিছু যার পাথ আপনার কাছে স্পষ্ট থাকা প্রয়োজন। Bind-mount ব্যর্থ হওয়ার সাধারণ কারণ হলো মালিকানা: কন্টেইনারটি UID 999 হিসেবে চলে, আপনার হোস্ট ডিরেক্টরির মালিক UID 1000, ফলে অ্যাপটি স্টার্টআপের সময় লগ-এ permission denied এরর দিয়ে বন্ধ হয়ে যায়। Named volumes এই ধরনের বাগ দূর করে, তবে এর বিনিময়ে ডেটা Docker-পরিচালিত পাথে থাকে, যা নিচে আলোচনা করা হয়েছে।

environment এবং .env, গিট (git) থেকে সিক্রেট দূরে রাখুন

${POSTGRES_PASSWORD} আপনার শেল থেকে পড়া হয় না; Compose এটিকে compose.yml-এর পাশে থাকা .env নামক ফাইল থেকে ইন্টারপোলেট করে। এটি তৈরি করুন:

cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignore

openssl rand -hex 24 দিয়ে আসল ভ্যালু তৈরি করুন। ইচ্ছাকৃতভাবেই Hex ব্যবহার করুন, base64 নয়: এই পাসওয়ার্ডটি DATABASE_URL কানেকশন স্ট্রিংয়ের ভেতরে থাকে এবং base64-এর উৎপাদিত /, +, এবং = ক্যারেক্টারগুলো URL পার্সিং নষ্ট করে দেয়। এটি এমন একটি ব্যর্থতা যা সিনট্যাক্স এরর হিসেবে নয়, বরং অথেন্টিকেশন এরর হিসেবে ধরা দেয় এবং আপনার একটি সন্ধ্যা নষ্ট করতে পারে। প্রথম কমিটের আগেই .gitignore লাইনটি যোগ করুন: compose ফাইলটি পাবলিশ বা ভার্সন করা নিরাপদ, কিন্তু .env ফাইলটি কখনোই নয়। গিট হিস্ট্রিতে একবার কোনো সিক্রেট চলে গেলে সেটি আর নিরাপদ নয়, তখন সেটি পরিবর্তন (rotate) করতে হয়। যদি কোনো ভেরিয়েবল ছাড়াই স্ট্যাক শুরু করেন, Compose উচ্চস্বরে সতর্কবার্তা দেয় এবং খালি স্ট্রিং নিয়ে কাজ চালিয়ে যায়, যা Postgres পাসওয়ার্ডের ক্ষেত্রে একটি অকার্যকর ডেপ্লয়মেন্ট তৈরি করে:

WARN[0000] The "POSTGRES_PASSWORD" variable is not set. Defaulting to a blank string.

docker compose config কমান্ডটি পুরোপুরি ইন্টারপোলেট করা ফাইলটি প্রিন্ট করে, যা কন্টেইনারগুলো আসলে কী পাবে তা যাচাই করার দ্রুততম উপায়; মনে রাখবেন এর আউটপুটে আপনার সিক্রেটগুলোও অন্তর্ভুক্ত থাকে।

depends_on কোনো কিছুর জন্য অপেক্ষা করে না, যদি না আপনি healthcheck যোগ করেন

একটি সাধারণ depends_on: [db] শুধুমাত্র স্টার্ট হওয়ার ক্রম নিয়ন্ত্রণ করে: Compose প্রথমে Postgres চালু করে এবং কিছুক্ষণ পর অ্যাপটি চালু করে, যখন Postgres কানেকশন গ্রহণ করার জন্য তখনও কয়েক সেকেন্ড দূরে থাকে। অ্যাপটি ডেটাবেসে হিট করে, ব্যর্থ হয় এবং অ্যাপটি কীভাবে লেখা হয়েছে তার ওপর ভিত্তি করে ক্র্যাশ করে অথবা পুনরায় চেষ্টা করে।

নির্ভরযোগ্য সংস্করণটি হলো যা উপরের ফাইলে ব্যবহার করা হয়েছে: db সার্ভিসটি একটি healthcheck ডিফাইন করে (Postgres ঠিক এই কাজের জন্যই pg_isready প্রদান করে), এবং অ্যাপটি condition: service_healthy-সহ depends_on ঘোষণা করে। Compose ডেটাবেস চালু করে, প্রতি 10 সেকেন্ডে চেকটি পোল করে এবং চেকটি পাস হওয়ার পরেই কেবল Miniflux চালু করে। যদি ডেটাবেস কখনোই হেলদি না হয় (ভুল পাসওয়ার্ড, করাপ্ট ভলিউম), অ্যাপটি কখনোই চালু হয় না এবং Compose আপনাকে জানিয়ে দেয় কোন ডিপেন্ডেন্সি ব্যর্থ হয়েছে:

dependency failed to start: container miniflux-db-1 is unhealthy

সেই বার্তাটি আপনাকে docker compose logs db-এর দিকে নির্দেশ করে, যেখানে আসল এররটি থাকে।

restart: unless-stopped

উভয় সার্ভিসে restart: unless-stopped থাকার মানে হলো, ক্র্যাশ হওয়ার পর এবং VPS রিবুট হওয়ার পর কন্টেইনারগুলো ফিরে আসবে, কিন্তু আপনি যদি ইচ্ছাকৃতভাবে docker compose stop রান করেন তবে সেগুলো বন্ধই থাকবে। এর বিকল্প always ম্যানুয়াল স্টপের পরেও কন্টেইনারগুলোকে পুনরুজ্জীবিত করে, যা সাধারণত আপনার উদ্দেশ্য নয়। কোনো রিস্টার্ট পলিসি না থাকলে, ভোর 4টায় কার্নেল আপডেটের জন্য রিবুট হলে আপনার সার্ভিসগুলো চুপচাপ বন্ধ হয়ে যাবে যতক্ষণ না আপনি তা লক্ষ্য করছেন।

দৈনন্দিন কাজ

প্রতিদিনের কাজের জন্য পাঁচটি কমান্ড রয়েছে, যা প্রজেক্ট ডিরেক্টরি থেকে চালাতে হয়।

docker compose up -d        # create and start; idempotent, recreates only what changed
docker compose ps           # status, ports, and health of this project's containers
docker compose logs -f miniflux   # follow one service's logs; --tail 100 for recent history
docker compose pull && docker compose up -d   # upgrade to the pinned tags
docker compose down         # stop and remove containers and the network

up -d বারবার চালানো নিরাপদ। এটি বর্তমান ফাইলের সাথে সিস্টেমের অবস্থার তুলনা করে এবং শুধুমাত্র সেই সার্ভিসগুলো আপডেট করে যেগুলোর কনফিগারেশন বা ইমেজ পরিবর্তিত হয়েছে। আপগ্রেড করার সময়, এটি আপনার পিন করা ট্যাগ অনুযায়ী নতুন ভার্সন নিয়ে আসে: postgres:16-alpine এর অধীনে প্যাচ রিলিজগুলো আপডেট হয়, কিন্তু নির্দিষ্ট ভার্সন পিন করা থাকলে তা পরিবর্তিত হয় না, যা এর মূল উদ্দেশ্য। আপগ্রেডের পর পুরনো ইমেজগুলো জমা হতে থাকে; ডিস্ক খালি করতে docker image prune -f ব্যবহার করুন।

এখন ধ্বংসাত্মক কমান্ডটির কথা বলা যাক, যা সতর্কতার সাথে ব্যবহার করা উচিত: docker compose down চালানো নিরাপদ, কারণ কন্টেইনার এবং নেটওয়ার্কগুলো পুনরায় তৈরি করা যায় এবং আপনার ডেটা ভলিউমে সংরক্ষিত থাকে। docker compose down -v কমান্ডটি নামযুক্ত ভলিউমগুলোও মুছে ফেলে। এর ফলে আপনার ডেটাবেস তাৎক্ষণিকভাবে মুছে যাবে, কোনো নিশ্চিতকরণ বার্তা বা পূর্বাবস্থায় ফেরার সুযোগ ছাড়াই। -v ফ্ল্যাগটি শুধুমাত্র পরীক্ষামূলক কাজের জন্য ব্যবহৃত হয়; বাস্তব ডেটা রয়েছে এমন স্ট্যাকের ক্ষেত্রে এটি ব্যবহারের সময় rm -rf এর মতোই সতর্ক থাকুন। /var/lib/docker/volumes/ এর অধীনে কোনো ট্র্যাশ ক্যান বা রিসাইকেল বিন নেই।

চলমান কন্টেইনারের ভেতরে একবারের জন্য শেল অ্যাক্সেস করতে: docker compose exec db psql -U miniflux আপনাকে সরাসরি ডেটাবেসে নিয়ে যাবে এবং docker compose exec miniflux sh আপনাকে অ্যাপের ভেতরে একটি শেল প্রদান করবে।

আপনার ডেটা যেখানে থাকে

Named volumes-এর সাথে প্রজেক্টের প্রিফিক্স যুক্ত হয়, তাই db-data একটি ডিরেক্টরিতে যার নাম miniflux, সেটি হয়ে যায় miniflux_db-data:

docker volume ls
docker volume inspect miniflux_db-data

inspect আউটপুটে প্রয়োজনীয় লাইনটি দেখা যায়:

"Mountpoint": "/var/lib/docker/volumes/miniflux_db-data/_data"

এই ডিরেক্টরিটি হলো ডেটাবেস, যার মালিক root এবং এটি হোস্ট ফাইলসিস্টেমে থাকে। এটি down, আপগ্রেড এবং কন্টেইনার রিবিল্ডের পরেও টিকে থাকে। আপনার ব্যাকআপে অবশ্যই এই অংশটি অন্তর্ভুক্ত করতে হবে।

একটি named volume ব্যাকআপ করা

এর আদর্শ পদ্ধতি হলো একটি অস্থায়ী কন্টেইনার ব্যবহার করা, যা ভলিউমটিকে হোস্ট ডিরেক্টরির পাশে রিড-অনলি মোডে মাউন্ট করে এবং tar কমান্ডের মাধ্যমে ফাইলগুলো কপি করে:

docker run --rm \
  -v miniflux_db-data:/data:ro \
  -v "$PWD":/backup \
  alpine:3.22 tar czf /backup/miniflux-db-$(date +%F).tar.gz -C /data .

এতে কোনো কিছু ইনস্টল করার প্রয়োজন হয় না, ব্যাকগ্রাউন্ডে কিছু চলতে থাকে না এবং রিস্টোর করার সময় একই মাউন্টগুলো উল্টোভাবে ব্যবহার করে একটি নতুন খালি ভলিউমে tar xzf কমান্ডটি চালালেই কাজ হয়ে যায়।

ডেটাবেসের ক্ষেত্রে একটি সতর্কতা: চলমান Postgres ডেটা ডিরেক্টরি tar করলে এমন একটি অবস্থা তৈরি হতে পারে যখন ডেটা রাইট হওয়ার মাঝামাঝি সময়ে ব্যাকআপ নেওয়া হয়েছে, যা পরবর্তীতে সঠিকভাবে চালু নাও হতে পারে। হয় tar চলাকালীন কয়েক সেকেন্ডের জন্য docker compose stop ব্যবহার করুন, অথবা আরও ভালো উপায় হলো একটি লজিক্যাল ডাম্প নেওয়া, যা গঠনগতভাবেই সামঞ্জস্যপূর্ণ:

docker compose exec -T db pg_dump -U miniflux miniflux | gzip > miniflux-$(date +%F).sql.gz

-T ফ্ল্যাগটি Compose-এর ডিফল্ট সিউডো-টার্মিনাল (pseudo-terminal) বরাদ্দ করা বন্ধ করে, কারণ ডাম্প আউটপুট TTY-এর মধ্য দিয়ে পাঠালে তা নষ্ট হয়ে যেতে পারে। এর মধ্যে যেকোনো একটি কমান্ড cron-এ সেট করুন এবং ফলাফলটি VPS থেকে অন্য কোথাও কপি করে রাখুন; যে ডিস্কে ডেটা আছে সেই একই ডিস্কে রাখা ব্যাকআপ আসলে কোনো ব্যাকআপ নয়, এটি কেবল একটি কপি। Nextcloud guide-এ ঠিক এই দুটি পদ্ধতি ব্যবহার করে একটি পূর্ণাঙ্গ শিডিউলড ব্যাকআপ রুটিন তৈরি করা হয়েছে।

ব্যর্থতার ধরন এবং যে বার্তাগুলো আপনি দেখবেন

permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock, আপনি এখনও docker গ্রুপে নেই, অথবা থাকলেও আপনার বর্তমান সেশনটি গ্রুপে যোগ দেওয়ার আগের। id আপনার কার্যকর গ্রুপগুলো দেখায়; newgrp docker বর্তমান শেলের জন্য এটি ঠিক করে, তবে লগ আউট করে পুনরায় লগ ইন করলে সব সেশনের জন্য এটি কার্যকর হয়।

Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?, এটি ভিন্ন সমস্যা: ডেমোনটি নিজেই বন্ধ আছে। sudo systemctl status docker এবং sudo journalctl -u docker -n 50 এর কারণ জানায়। একটি VPS-এ এর সাধারণ কারণ হলো ডিস্ক পূর্ণ হয়ে যাওয়া, তাই df -h /var/lib/docker দিয়ে আগে এটি পরীক্ষা করুন।

Bind for 127.0.0.1:8080 failed: port is already allocated, অন্য কোনো কন্টেইনার ইতিমধ্যে সেই হোস্ট পোর্টটি ব্যবহার করছে। docker ps দেখায় কোনটি ব্যবহার করছে; সাধারণত কয়েক docker run আগে চালানো কোনো পুরনো কন্টেইনার এর জন্য দায়ী। যদি docker ps খালি থাকে, তবে ডকার নয় এমন কোনো প্রসেস পোর্টটি দখল করে আছে: sudo ss -tlnp | grep 8080 দিয়ে সেটি শনাক্ত করুন।

yaml: line 14: did not find expected key, উল্লেখিত লাইনে বা তার ঠিক উপরে ইনডেন্টেশন ত্রুটি আছে। Compose ফাইলগুলো YAML ফরম্যাটের: এতে দুই-স্পেস ইনডেন্টেশন ব্যবহার করতে হয়, শুধু স্পেস ব্যবহার করবেন, কোনো ট্যাব ক্যারেক্টার থাকলে তা কাজ করবে না। docker compose config কোনো কিছু শুরু না করেই ফাইলটি যাচাই করে, তাই প্রতিবার এডিট করার পর এটি চালানো একটি ভালো অভ্যাস।

ufw এর চমক কোনো ত্রুটি দেখায় না, যা একে বিপজ্জনক করে তোলে: ডিপ্লয়মেন্ট সফল হয়, ufw status দেখে মনে হয় সব ঠিক আছে, কিন্তু বাইরে থেকে পোর্ট স্ক্যান করলে আপনার ডাটাবেস তবুও উন্মুক্ত পাওয়া যায়। উপরের পোর্ট সেকশনটি পুনরায় পড়ুন, প্রতিটি ports: এন্ট্রিতে 127.0.0.1: প্রিফিক্স বাদ পড়েছে কি না তা পরীক্ষা করুন এবং অন্য একটি মেশিন থেকে curl http://your-vps-ip:8080 দিয়ে নিশ্চিত করুন যে আপনি connection refused বার্তাটি পাচ্ছেন কি না, যা আপনার কাঙ্ক্ষিত ফলাফল।

এখান থেকে, Traefik গাইড এই একক স্ট্যাকটিকে একটি HTTPS এন্ট্রি পয়েন্টের পেছনে অনেকগুলো অ্যাপে রূপান্তর করে, এবং 2026 সালে যা যা সেলফ-হোস্ট করা যায় হলো সেই তালিকা যা আপনি এর মাধ্যমে চালাতে পারেন। যখন এমন কয়েকটি স্ট্যাক চলতে শুরু করবে এবং প্রতিটির নিজস্ব লগইন ফর্ম থাকবে, তখন Authentik এর মতো একটি সেলফ-হোস্টেড SSO সার্ভার সেগুলোকে একই প্রক্সির পেছনে একটি অ্যাকাউন্টের অধীনে নিয়ে আসে।

VPS-এ একটি Minecraft server

-এর মতো game server অনুশীলনের জন্য একটি সহজ প্রথম Compose project হতে পারে। আপনি যদি প্রতিদিন খোলেন এমন কিছু দিয়ে শিখতে চান, তাহলে openGym, self-hosted workout tracker একটি ছোট stack। এটি image tag-এর বদলে git tag-এ নির্দিষ্ট করা থাকে। প্রথম passkey নিবন্ধন করার আগে এর সামনে TLS সেটআপ করতে হবে। অন্য কারও cloud থেকে নিজের photos ফিরিয়ে আনা সাধারণত মানুষের প্রথম চাহিদা। PhotoPrism এবং Immich-এর তুলনা করলে যেকোনো একটির জন্য volume নির্ধারণের আগে প্রয়োজনীয় RAM-এর ন্যূনতম পরিমাণ এবং যে backup routine গ্রহণ করছেন, তা স্পষ্ট হয়। দুটি service যথেষ্ট মনে না হলে Notion-ধাঁচের workspace হিসেবে AFFiNE চালু করা একই pattern চারটি container-এ প্রয়োগ করে। উপরের pinned tag, healthcheck এবং named volume ব্যবহারের অভ্যাস সত্যিই তৈরি হয়েছে কি না যাচাই করার জন্য এটিও একটি উপযুক্ত পরীক্ষা।

FAQ

কেন আমি "permission denied while trying to connect to the Docker daemon socket" ত্রুটিটি পাচ্ছি?

আপনার ইউজার docker গ্রুপে নেই, অথবা বর্তমান সেশন শুরু হওয়ার পরে তাকে যোগ করা হয়েছে; গ্রুপের সদস্যপদ শুধুমাত্র লগইন করার সময়ই কার্যকর হয়। sudo usermod -aG docker $USER কমান্ডটি চালান, তারপর newgrp docker ব্যবহার করুন অথবা লগ আউট করে পুনরায় লগইন করুন এবং id দিয়ে নিশ্চিত করুন। এই গ্রুপটি হোস্ট মেশিনে root-এর সমতুল্য অ্যাক্সেস প্রদান করে, তাই শুধুমাত্র সেই ইউজারদেরই যোগ করুন যাদের আপনি sudo অ্যাক্সেস দিতে ইচ্ছুক।

docker compose down কমান্ডটি কি আমার ডেটা মুছে ফেলে?

সাধারণ docker compose down কমান্ডটি ডেটা মুছে ফেলে না; এটি শুধুমাত্র কন্টেইনার এবং প্রজেক্ট নেটওয়ার্ক সরিয়ে ফেলে। named volume-গুলো অক্ষত থাকে এবং পরবর্তী up -d কমান্ডের সময় সেগুলো পুনরায় যুক্ত হয়ে যায়। docker compose down -v হলো ধ্বংসাত্মক কমান্ড: এটি named volume-গুলো মুছে ফেলে, যার অর্থ আপনার ডেটাবেসও মুছে যাবে এবং এটি নিশ্চিতকরণ ছাড়াই ঘটে, তাই এটি ফিরিয়ে আনা সম্ভব নয়। যদি আপনার কাছে যাচাইকৃত ব্যাকআপ না থাকে, তবে বাস্তব ডেটা আছে এমন কোনো স্ট্যাকে কখনোই -v চালাবেন না।

docker-compose এবং docker compose-এর মধ্যে পার্থক্য কী?

docker-compose (হাইফেনসহ) হলো Compose v1, যা একটি স্বতন্ত্র Python বাইনারি। এটি 2023 সালে তার জীবনচক্র শেষ করেছে এবং নতুন সার্ভারে এটি ইনস্টল করা উচিত নয়। docker compose (স্পেসসহ) হলো Compose v2, যা Docker CLI-এর জন্য একটি Go প্লাগইন এবং এটি Docker-এর apt রিপোজিটরি থেকে docker-compose-plugin হিসেবে ইনস্টল করা হয়। কমান্ড এবং YAML ফাইলগুলো প্রায় সম্পূর্ণ সামঞ্জস্যপূর্ণ, তাই কোনো পুরনো টিউটোরিয়ালে docker-compose up লেখা থাকলে আপনি docker compose up টাইপ করুন।

ufw দিয়ে পোর্ট ব্লক করা সত্ত্বেও কেন আমি ইন্টারনেট থেকে আমার Docker কন্টেইনারে পৌঁছাতে পারছি?

কারণ Docker তার পোর্টগুলোকে iptables-এর PREROUTING চেইনে DNAT রুল দিয়ে প্রকাশ করে। এই পরিবর্তিত প্যাকেটগুলো Docker-এর নিজস্ব চেইন হয়ে FORWARD পাথ দিয়ে প্রবাহিত হয়, ফলে সেগুলো কখনোই INPUT চেইনে পৌঁছায় না যেখানে ufw-এর রুলগুলো কার্যকর হয়। তাই ufw deny 8080 কোনো প্রকাশিত কন্টেইনার পোর্টের ওপর কাজ করে না। এর সমাধান হলো: পোর্টগুলোকে 127.0.0.1:-এ প্রকাশ করুন এবং সার্ভিসগুলোকে একটি reverse proxy-এর মাধ্যমে এক্সপোজ করুন।

আমার কি named volume নাকি bind mount ব্যবহার করা উচিত?

শুধুমাত্র কন্টেইনার যে ডেটা ব্যবহার করে, বিশেষ করে ডেটাবেসের ক্ষেত্রে, named volume ব্যবহার করুন। কারণ Docker ইমেজ অনুযায়ী মালিকানা (ownership) এবং পারমিশন স্বয়ংক্রিয়ভাবে ঠিক করে দেয়। যে ফাইলগুলো আপনি হোস্ট মেশিন থেকেও নিয়ন্ত্রণ করেন, যেমন এডিট করা কনফিগারেশন ফাইল বা আপলোড করা মিডিয়া ফাইল, সেগুলোর জন্য bind mount ব্যবহার করুন। যদি কোনো কন্টেইনার bind mount-এর কারণে permission denied ত্রুটি নিয়ে শুরু হতে ব্যর্থ হয়, তবে হোস্ট এবং কন্টেইনারের মধ্যে UID অমিল আছে কি না তা সবার আগে পরীক্ষা করুন।