Ubuntu 24.04-এ Docker Compose সেটআপ করার নিয়ম
Ubuntu 24.04-এ Docker Engine ও Compose plugin ইনস্টল করুন। Miniflux ও PostgreSQL দিয়ে একটি stack তৈরি করুন এবং ufw পোর্ট কনফিগারের সাধারণ ভুলগুলো এড়িয়ে চলার উপায় জানুন।
আপনি যা তৈরি করছেন
Docker Compose হলো এই সাইটের প্রায় সবকিছুর মূল ভিত্তি। Nextcloud, Vaultwarden, n8n, Immich, Rocket.Chat—এই গাইডগুলোর প্রতিটিই "এই compose ফাইলটি লিখুন" দিয়ে শুরু হয়, এবং এই পেজটি ব্যাখ্যা করবে যে সেই ফাইলটির প্রকৃত অর্থ কী। আপনি Ubuntu 24.04-এ Docker-এর নিজস্ব apt repository থেকে Docker Engine এবং Compose 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-এ লেখা এবং Docker কর্তৃক এর মেয়াদ শেষ (end of life) ঘোষণা করা হয়েছে; পুরনো টিউটোরিয়ালগুলো কাজ না করার মূল কারণ এটিই। Docker এখনও বর্তমান Go কোড থেকে তৈরি একটি স্বতন্ত্র docker-compose বাইনারি প্রকাশ করে, তবে তাদের নিজস্ব ইনস্টল পেজে এই পদ্ধতিটি ব্যবহার না করার পরামর্শ দেওয়া হয়েছে এবং এটি শুধুমাত্র পুরনো ভার্সনের সাথে সামঞ্জস্য বজায় রাখার জন্য রাখা হয়েছে। বর্তমানে Compose হলো docker compose, যা একটি CLI প্লাগইন এবং এটি ইঞ্জিনের একই রিপোজিটরি থেকে ইনস্টল করা হয়।
যদি সার্ভারে এর কোনোটি আগে থেকেই থাকে, তবে প্রথমে তা মুছে ফেলুন, যার মধ্যে docker-compose-v2 এবং docker-buildx (Ubuntu-এর নিজস্ব প্যাকেজ করা প্লাগইন) অন্তর্ভুক্ত, যাতে সবকিছু একটি মাত্র রিপোজিটরি থেকে আসে:
sudo apt remove -y docker.io docker-compose docker-compose-v2 docker-doc docker-buildx 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 v5.x.x নিশ্চিত করবে যে আপনি প্লাগইনটি ব্যবহার করছেন, পুরনো v1 বাইনারি নয়। পুরনো কম্পোজ ফাইল ফরম্যাটের ভার্সনের সাথে বিভ্রান্তি এড়াতে এই প্লাগইনটি ডিসেম্বর 2025-এ v2 থেকে সরাসরি v5-এ উন্নীত হয়েছে (v3 এবং v4 বাদ দেওয়া হয়েছে), তাই যেসব গাইডে v2.x.x দেখানো হয়েছে সেগুলো একই টুলকে নির্দেশ করে। 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/minifluxcompose.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 করার সময় মেইনটেইনার সর্বশেষ যা পুশ করেছেন, সেটিই পুনরায় সমাধান (re-resolve) করে। এর সাথে আপনার শেখা নিয়মিত আপডেটের অভ্যাস docker compose pull && docker compose up -d যোগ করলে, :latest মানে হলো যখনই আপস্ট্রিম থেকে নতুন ভার্সন আসবে, তখনই তা আপনার সিস্টেমে চলে আসবে, আপনার ইচ্ছা অনুযায়ী নয়। PostgreSQL-এর ক্ষেত্রে এটি কেবল তাত্ত্বিক নয়: 16 থেকে 17 ভার্সনে হঠাৎ পরিবর্তন হলে কন্টেইনারটি ক্র্যাশ-লুপে পড়ে যায় কারণ ইনকমপ্যাটিবল ডেটা ডিরেক্টরির কারণে এটি আর চলতে পারে না। Postgres-এর বড় ধরনের আপগ্রেডের জন্য dump এবং restore প্রয়োজন, শুধু রিস্টার্ট যথেষ্ট নয়।
অন্তত মেজর ভার্সনটি নির্দিষ্ট করুন (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) এবং ফাইল পারমিশন স্বয়ংক্রিয়ভাবে সেট করে দেয়। হোস্ট থেকে যে ফাইলগুলো আপনি এডিট করবেন সেগুলোর জন্য Bind mounts ব্যবহার করুন, যেমন কনফিগারেশন ফাইল যা আপনি টেক্সট এডিটরে পরিবর্তন করেন, অথবা মিডিয়া লাইব্রেরি যা আপনি rsync করেন—যেকোনো কিছুর পাথ যা আপনি স্পষ্টভাবে দেখতে চান। Bind-mount-এর সাধারণ ব্যর্থতা হলো মালিকানা সংক্রান্ত সমস্যা: ইমেজটি একটি নির্দিষ্ট UID-তে প্রসেস চালায় (Miniflux ইমেজ UID 65534-এ চলে, postgres:16-alpine UID 70-এ চলে), কিন্তু আপনার হোস্ট ডিরেক্টরি UID 1000-এর মালিকানাধীন। ফলে অ্যাপটি স্টার্টআপের সময় লগ-এ permission denied এরর দিয়ে বন্ধ হয়ে যায়। Named volumes এই ধরনের বাগ দূর করে, তবে এর বিনিময়ে ডেটা Docker-পরিচালিত পাথে থাকে, যা নিচে আলোচনা করা হয়েছে।
environment এবং .env, git থেকে সিক্রেট দূরে রাখুন
Compose ${POSTGRES_PASSWORD}-কে compose.yml-এর পাশে থাকা .env ফাইল থেকে পূরণ করে, কিন্তু শুধুমাত্র তখনই যখন আপনার শেল সেই ভেরিয়েবলটি আগে থেকে ডিফাইন না করে। আপনার শেল এনভায়রনমেন্টে এক্সপোর্ট করা ভ্যালু .env ফাইলের চেয়ে বেশি প্রাধান্য পায়। তাই আগের কোনো এক্সপেরিমেন্ট থেকে ভুলে যাওয়া কোনো export POSTGRES_PASSWORD=... ফাইলটিকে নীরবে ওভাররাইড করতে পারে, এবং docker compose config (নিচে) দেখায় কোন ভ্যালুটি কার্যকর হয়েছে। ফাইলটি তৈরি করুন:
cat > .env <<'EOF'
POSTGRES_PASSWORD=change-me-to-something-long
ADMIN_PASSWORD=change-me-too
EOF
chmod 600 .env
echo ".env" >> .gitignoreopenssl rand -hex 24 দিয়ে আসল ভ্যালু তৈরি করুন। ইচ্ছাকৃতভাবে base64 নয়, Hex ব্যবহার করুন: এই পাসওয়ার্ডটি DATABASE_URL কানেকশন স্ট্রিংয়ের ভেতরে থাকে, এবং base64-এর তৈরি /, +, এবং = ক্যারেক্টারগুলো URL পার্সিংয়ে সমস্যা তৈরি করে। এটি এমন একটি ব্যর্থতা যা সিনট্যাক্স এরর হিসেবে নয়, বরং অথেন্টিকেশন এরর হিসেবে ধরা দেয় এবং অনেক সময় নষ্ট করে। প্রথম কমিট করার আগেই .gitignore লাইনটি যোগ করুন: compose ফাইলটি পাবলিশ বা ভার্সন কন্ট্রোলে রাখা নিরাপদ, কিন্তু .env ফাইলটি কখনোই নয়। কোনো সিক্রেট যদি একবার git হিস্ট্রিতে চলে যায়, তবে সেটি আর নিরাপদ নয়, সেটি পরিবর্তন (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 প্রদান করে), এবং অ্যাপটি depends_on-এর সাথে condition: service_healthy ঘোষণা করে। 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 networkup -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-datainspect আউটপুটে প্রয়োজনীয় লাইনটি দেখা যায়:
"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 সার্ভার সেগুলোকে একই প্রক্সির পেছনে একটি অ্যাকাউন্টের অধীনে নিয়ে আসে।
-এর মতো 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 কল করার পুরনো পদ্ধতি। পুরনো সার্ভারগুলোতে এটি সাধারণত Compose v1, যা পাইথনে তৈরি এবং ডকার এর সাপোর্ট বন্ধ করে দিয়েছে। ডকার এখনও বর্তমান Go কোড থেকে তৈরি একটি স্বতন্ত্র docker-compose বাইনারি প্রকাশ করে, তবে এই ইন্সটলেশন পদ্ধতিটি আর সুপারিশ করা হয় না এবং শুধুমাত্র ব্যাকওয়ার্ড কম্প্যাটিবিলিটির জন্য রাখা হয়েছে, তাই নতুন সার্ভারে এটি এড়িয়ে চলুন। docker compose (স্পেসসহ) হলো বর্তমান Compose, যা v2 থেকে শুরু এবং ডিসেম্বর 2025 থেকে v5 হিসেবে পরিচিত। এটি ডকার CLI-এর একটি Go প্লাগইন, যা ডকারের apt রিপোজিটরি থেকে docker-compose-plugin হিসেবে ইন্সটল করা হয়। কমান্ড এবং YAML ফাইল প্রায় সম্পূর্ণ সামঞ্জস্যপূর্ণ, তাই কোনো পুরনো টিউটোরিয়ালে docker-compose up লেখা থাকলে আপনি docker compose up টাইপ করুন।
ufw দিয়ে পোর্ট ব্লক করা সত্ত্বেও কেন আমি ইন্টারনেট থেকে আমার ডকার কন্টেইনারে পৌঁছাতে পারি?
কারণ ডকার iptables-এর PREROUTING চেইনে DNAT রুল ব্যবহার করে পোর্ট পাবলিশ করে এবং রিরাইট করা প্যাকেটগুলো ডকারের নিজস্ব চেইন দিয়ে FORWARD পথে চলাচল করে, তাই সেগুলো কখনোই INPUT চেইনে পৌঁছায় না যেখানে ufw-এর রুলগুলো কার্যকর হয়। ফলে পাবলিশ করা কন্টেইনার পোর্টের ওপর ufw deny 8080 কোনো প্রভাব ফেলে না। এটি সমাধানের উপায় হলো: শুধুমাত্র 127.0.0.1:-এ পোর্ট পাবলিশ করুন এবং একটি reverse proxy-এর মাধ্যমে সার্ভিসগুলো এক্সপোজ করুন।
আমার কি named volume নাকি bind mount ব্যবহার করা উচিত?
শুধুমাত্র কন্টেইনার যে ডেটা ব্যবহার করে, বিশেষ করে ডেটাবেসের জন্য named volume ব্যবহার করুন, কারণ ডকার ইমেজ অনুযায়ী মালিকানা সেট করে এবং পারমিশন ঠিকঠাক কাজ করে। হোস্ট মেশিন থেকে আপনি যে ফাইলগুলো পরিচালনা করেন, যেমন এডিট করা কনফিগারেশন, আপলোড করা মিডিয়া বা যে ফাইলের পাথ আপনি স্পষ্টভাবে দেখতে চান, সেগুলোর জন্য bind mount ব্যবহার করুন। যদি কোনো কন্টেইনার স্টার্টআপের সময় bind mount-এ permission denied ত্রুটি দেখায়, তবে হোস্ট এবং কন্টেইনারের মধ্যে UID অমিল আছে কি না তা সবার আগে পরীক্ষা করুন।