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

Docker Compose stack backup ও upgrade করার নিয়ম

Compose stack-এর backup-এ compose file, .env, volume contents এবং database dump রাখুন। restore প্রমাণ ও upgrade-এর আগে নিরাপদে যাচাই করার কমান্ডসহ পূর্ণ গাইড।

Docker Compose stack-এর backup-এ যা থাকতে হবে

একটি Docker Compose stack-এর backup-এ 4টি পৃথক জিনিস থাকতে হবে। এর যেকোনো একটি হারালে app আর চালু হবে না: compose file, তার পাশে থাকা .env, প্রতিটি volume-এর সম্পূর্ণ contents এবং database-এর নিজস্ব client দিয়ে লেখা database dump। Container চলার সময় database-এর file copy করা backup নয়। Upgrade-এর ক্ষেত্রেও একই তালিকা প্রযোজ্য, সঙ্গে একটি নিয়ম আছে: pull করার আগে backup নিন, কারণ schema migration সাধারণত সামনের দিকে চালানোর জন্য লেখা হয় এবং অধিকাংশ project-এ আগের অবস্থায় ফেরার কোনো ব্যবস্থা থাকে না।

নিচের সবকিছু ধরে নেওয়া হয়েছে যে stack ইতিমধ্যে deployed এবং docker compose ps-এ সেটি running দেখাচ্ছে। উদাহরণগুলোতে /srv/myapp-এ একটি project directory এবং appdb নামে service ব্যবহার করা হয়েছে। আপনার নিজস্ব নাম বসান। Commands ইচ্ছাকৃতভাবে generic রাখা হয়েছে, কারণ গুরুত্বপূর্ণ অংশগুলো—volume এবং database—app যাই হোক একইভাবে কাজ করে।

আপনার stack আসলে কী সংরক্ষণ করে তা নির্ধারণ করুন

cd /srv/myapp
docker compose ps
docker compose config --volumes
docker volume ls --filter label=com.docker.compose.project=myapp

docker compose config --volumes আপনার file-এ ঘোষিত named volume-গুলোর সংক্ষিপ্ত নাম দেখায়। docker volume ls disk-এ ওই volume-গুলোর প্রকৃত নাম দেখায়। দুটি তালিকা আলাদা, কারণ Compose-এর আগে project name যুক্ত হয়: file-এ db_data হিসেবে লেখা volume disk-এ myapp_db_data নামে থাকে। Project name ডিফল্টভাবে directory name হয়। তাই directory-এর নাম পরিবর্তন করলে stack নতুন, খালি volume-গুলোর দিকে নির্দেশ করে এবং পুরোনোগুলো আপনার সব data-সহ disk-এ পড়ে থাকে। নিচের প্রতিটি command-এ docker volume ls থেকে পাওয়া প্রকৃত নাম ব্যবহার করতে হবে।

Bind mount কোনো তালিকাতেই দেখা যায় না। Compose file-এ colon-এর বাম পাশে host path থাকা entry-গুলোই bind mount, যেমন ./config:/app/config। এগুলো host-এর সাধারণ directory, তাই সাধারণ tool দিয়েই এগুলোতে পৌঁছানো যায়। Named volume /var/lib/docker/volumes/-এর অধীনে থাকে, এবং docker volume inspect --format '{{.Mountpoint}}' myapp_db_data একটি volume-এর সঠিক path দেখায়। আপনার stack কোন ধরনের volume ব্যবহার করে, তার ওপর copy করার পদ্ধতি বদলে যায়। bind mount এবং named volume-এর তুলনা অংশে এই trade-off বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

এখন পাওয়া তথ্য দুটি দলে ভাগ করুন। কিছু volume এমন state সংরক্ষণ করে যা কোনো কিছুই পুনরায় তৈরি করতে পারে না: uploaded file, generated key, database নিজে এবং user app-এ যা typed করেছে। অন্য volume-এ thumbnail ও search index-এর মতো derived data থাকে, যা app নিজেই পুনর্নির্মাণ করতে পারে। দ্বিতীয় দলটির backup রাখতে disk space ও restore time লাগে, কিন্তু এতে কোনো বাস্তব সুবিধা নেই। Redis cache volume এর সবচেয়ে স্পষ্ট উদাহরণ: এটি হারালে প্রথম request ধীর হওয়ার খরচ বহন করতে হবে।

compose file এবং .env file-এর backup নিন

দুটি file host-এ পাশাপাশি থাকে, এবং কোনোটিই কোনো volume-এর ভিতরে থাকে না। .env-এ database password, application secret এবং API token থাকে। তাই একগুচ্ছ volume-কে আবার সচল app-এ পরিণত করার জন্য এই file-টিই সবচেয়ে গুরুত্বপূর্ণ। সাধারণত এটিকে .gitignore-এও তালিকাভুক্ত করা থাকে। ফলে “আমার configuration git-এ আছে” ধরনের পরিকল্পনা সবচেয়ে গুরুত্বপূর্ণ একক file-টিকে বাদ দেয়। env file-এ secret রাখা সঠিক পদ্ধতি। তাই backup-এও এই file অন্তর্ভুক্ত করা আবশ্যক।

sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/backups/myapp
cp -a compose.yaml .env /srv/backups/myapp/
chmod 600 /srv/backups/myapp/.env

Stack যে সব compose file ব্যবহার করে, সেগুলোর প্রতিটি কপি করুন। শুধু প্রথম file কপি করবেন না। -f compose.yaml -f compose.prod.yaml দিয়ে চালু করা stack একইভাবে ফিরিয়ে আনতে দুটি file-ই প্রয়োজন। একাধিক compose file কীভাবে merge হয় তা নির্ধারণ করে কোন value শেষ পর্যন্ত container-এ পৌঁছাবে।

একটি সতর্কতা .env-কে volume-এর সঙ্গে যুক্ত করে। Official Postgres image খালি data directory initialize করার সময়ই শুধু POSTGRES_PASSWORD পড়ে। পরে ওই value পরিবর্তন করলে database-এর ভিতরের password পরিবর্তিত হয় না। গত মাসের volume আজকের .env-এর পাশে restore করলে app FATAL: password authentication failed for user "appuser" দিয়ে connect করতে ব্যর্থ হয়, যদিও পরীক্ষা করে দুটি file-ই সঠিক মনে হয়। একই সময়ের .env এবং volume একই backup-এ একসঙ্গে রাখুন।

নিজস্ব client দিয়ে database dump করুন

একটি database server তার file-গুলোতে নিয়মিত লিখতে থাকে। Server চলার সময় নেওয়া tar-এর /var/lib/postgresql/data write-এর আগের কিছু page এবং write-এর পরের কিছু page কপি করতে পারে। ফলে archive-এ এমন সময়ের মিশ্রণ থাকে যা পরে সঠিকভাবে replay নাও হতে পারে। Dump tool একটি single transaction-এর ভেতর থেকে পড়ে। তাই file-এ একটি consistent মুহূর্তের অবস্থা থাকে। এই পার্থক্যই backup এবং copy-কে আলাদা করে।

docker compose exec -T db sh -c \
  'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
  > /srv/backups/myapp/db-$(date +%F).dump

-T অপরিবর্তিত রাখুন। এটি TTY allocation বন্ধ করে। TTY সংযুক্ত থাকলে Docker আপনার shell-এ output stream পাঠানোর আগে সেটিকে রূপান্তর করে। এতে binary dump নষ্ট হয়ে যায়। Restore ব্যর্থ না হওয়া পর্যন্ত আপনি বিষয়টি বুঝতে পারবেন না। Single quotes-ও গুরুত্বপূর্ণ। এগুলো host shell-কে $POSTGRES_USER expand করা থেকে বিরত রাখে। ফলে container-এর ভেতরের shell এটি expand করে এবং compose file-এ সেখানে নির্ধারিত value ব্যবহার করে। -Fc custom format লিখে। এটি লেখার সময়ই compress করে এবং পরে pg_restore ব্যবহার করে এর ভেতর থেকে নির্দিষ্ট object বেছে নেওয়া যায়।

Role এবং তাদের password কোনো একক database-এর বাইরে সংরক্ষিত থাকে। তাই সেগুলিও dump করুন:

docker compose exec -T db sh -c 'pg_dumpall -U "$POSTGRES_USER" --globals-only' \
  > /srv/backups/myapp/globals.sql

এরপর পরীক্ষা করুন, file-টি dump কি না, error message নয়:

ls -lh /srv/backups/myapp/
head -c 5 /srv/backups/myapp/db-$(date +%F).dump

Custom-format dump PGDMP-এর পাঁচটি byte দিয়ে শুরু হয়। Zero byte-এর file, অথবা pg_dump: দিয়ে শুরু হওয়া file বোঝায় যে command ব্যর্থ হয়েছে। Command চলার আগে shell output file তৈরি করে। তাই dump ব্যর্থ হলেও একটি গ্রহণযোগ্য নাম এবং গ্রহণযোগ্য timestamp-সহ file থেকে যায়। এটিই সবচেয়ে সাধারণ নীরব backup failure।

MariaDB বা MySQL-এর ক্ষেত্রে client বদলায়, কিন্তু পদ্ধতির কাঠামো একই থাকে:

docker compose exec -T db sh -c \
  'mariadb-dump -u root -p"$MARIADB_ROOT_PASSWORD" --single-transaction --databases "$MARIADB_DATABASE"' \
  > /srv/backups/myapp/db-$(date +%F).sql

--single-transaction writer-দের block না করে InnoDB table-এর consistent dump তৈরি করে। MySQL image-এ command হলো mysqldump এবং variable হলো MYSQL_ROOT_PASSWORDMYSQL_DATABASE। বর্তমান MariaDB image-এ mysqldump এখনও mariadb-dump-এর compatibility name হিসেবে কাজ করে। মনে রাখবেন, command line-এ দেওয়া password dump চলার পুরো সময় container-এর process list-এ দেখা যেতে পারে।

SQLite-এর জন্য আলাদা সতর্কতা দরকার। Database একটি file হলেও সাম্প্রতিক transaction এখনও তার পাশে থাকা আলাদা -wal file-এ থাকতে পারে। তাই শুধু .db copy করলে database-এর সর্বশেষ write বাদ পড়তে পারে। Image-এর সঙ্গে client থাকলে app চলার সময় sqlite3 /data/app.db ".backup '/data/app-backup.db'" একটি consistent copy তৈরি করে। Client না থাকলে container বন্ধ করুন এবং .db file-টি তার -wal-shm companion file-সহ copy করুন।

Database যদি stack-এর ভেতরে না থেকে host-এ চলে, তাহলে docker compose exec prefix ছাড়া একই command প্রযোজ্য হবে। পরবর্তী rebuild-এর আগে Docker-এ বা host-এ database চালানো পড়ে নেওয়া উপকারী।

ভলিউম ক্যাপচার করুন

Named volume-এর এমন কোনো host path নেই, যা হাতে সম্পাদনা করা উচিত। তাই এটি একটি সাময়িক container-এ mount করে সেখান থেকেই archive তৈরি করুন।

docker run --rm \
  -v myapp_uploads:/data:ro \
  -v /srv/backups/myapp:/backup \
  alpine:3 tar czf /backup/uploads.tar.gz -C /data .

Helper container-টি volume-কে read-only অবস্থায় /data-এ এবং আপনার backup directory-কে /backup-এ mount করে। এরপর এটি host-এর দিকে archive লিখে দেয়। --rm, tar শেষ হওয়ার সঙ্গে সঙ্গে helper-টিকে সরিয়ে দেয়। :ro গুরুত্বপূর্ণ, কারণ ভুল করে লেখা tar command তখন source-এর ক্ষতি করতে পারে না। -C /data . restore সঠিক জায়গায় হওয়ার কারণ। এটি প্রতিটি path volume root-এর সাপেক্ষে সংরক্ষণ করে। এর বদলে tar czf /backup/uploads.tar.gz /data লিখলে প্রতিটি path-এর শুরুতে data/ যুক্ত হয়। ফলে restore-এর সময় volume-এর ভিতরে /data/data তৈরি হয় এবং app একটি খালি directory দেখতে পায়। Archive-এর মালিক root হবে, কারণ container-এর ভিতরে tar root হিসেবে চলেছে। এতে সমস্যা হলে sudo chown "$USER" /srv/backups/myapp/uploads.tar.gz চালান। Restored file app পড়তে না পারলে PUID এবং PGID কীভাবে file ownership নির্ধারণ করে তা পড়ুন

প্রতিটি named volume-এর জন্য এটি একবার চালান। Bind mount-এর জন্য কোনো container দরকার নেই: host-এ tar czf /srv/backups/myapp/config.tar.gz -C /srv/myapp/config . একই কাজ করে।

প্রতিটি volume-এর ক্ষেত্রে app বন্ধ করা প্রয়োজন কি না, তা নির্ধারণ করুন। App যদি volume-এর file সরাসরি পরিবর্তন করে, তাহলে চলমান অবস্থায় tar নিলে কোনো file লেখার মাঝপথে ধরা পড়তে পারে। Uploads directory-তে file একবার লেখা হয় এবং পরে শুধু পড়া হয়, তাই সেখানে এই ঝুঁকি কম। অন্য সব ক্ষেত্রে copy চলাকালীন সেই service-টি docker compose stop app দিয়ে বন্ধ করুন, তারপর docker compose start app চালান। stop container এবং volume রেখে দেয়, যা এই কাজের জন্য ঠিক প্রয়োজন। তাই যেকোনো একটি চালানোর আগে down এবং stop-এর পার্থক্য নিশ্চিতভাবে বুঝে নিন।

Database volume-এর tar-কে database backup হিসেবে বিবেচনা করবেন না। Dump-ই backup। বন্ধ করা database-এর volume archive দ্রুত rebuild করার জন্য উপযোগী, এর বেশি নয়।

কাজের ধাপগুলোর ক্রম

  1. Compose ফাইলগুলো এবং .env backup directory-তে কপি করুন।
  2. Database চালু থাকা অবস্থায় dump নিন।
  3. App container-এর volume সরাসরি পরিবর্তিত হলে container-টি বন্ধ করুন।
  4. প্রতিটি named volume এবং প্রতিটি bind-mount directory archive করুন।
  5. যেগুলো বন্ধ করেছিলেন সেগুলো আবার চালু করুন, তারপর docker compose ps দিয়ে নিশ্চিত করুন।
  6. Stack যে image tag এবং digest দিয়ে চলছে, সেগুলো লিখে রাখুন।
  7. পুরো backup directory এই server-এর বাইরে কপি করুন।

Step 7-ই সাধারণত পরে করার জন্য রেখে দেওয়া হয়।

সার্ভারের বাইরে কপি রাখুন

Stack-এর একই disk-এ রাখা backup আপনাকে শুধু নিজের ভুল থেকে সুরক্ষা দেয়। অন্য কোনো সমস্যা থেকে এটি সুরক্ষা দেয় না। একটি volume নষ্ট হওয়া, একটি server মুছে যাওয়া বা একটি account হারিয়ে যাওয়ার ফলে একই সময়ে উভয় কপিই নষ্ট হতে পারে। নির্ধারিত সময়সূচি অনুযায়ী directory-টি এই VPS নয় এমন storage-এ পাঠান এবং retention policy ব্যবহার করুন। VPS থেকে restic backup repository setup, retention flag এবং check command ব্যাখ্যা করে। তাই এখানে সেগুলো পুনরাবৃত্তি করার প্রয়োজন নেই।

restic pipe থেকে সরাসরি dump পড়তেও পারে। এতে plaintext database পুরোপুরি disk-এর বাইরে রাখা যায়:

docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc' \
  | restic backup --stdin --stdin-filename db.dump

আপনি যে tool-ই ব্যবহার করুন, schedule-টি একটি systemd timer বা cron job-এ রাখুন। Job ব্যর্থ হলে এমন কোথাও report করার ব্যবস্থা করুন, যেখানে আপনি তা দেখতে পাবেন। যে backup script-এর output কোথাও যায় না, সেটি ছয় মাস কাজ বন্ধ রেখেও কারও নজরে না পড়তে পারে।

ব্যাকআপ কাজ করে কি না restore drill দিয়ে যাচাই করুন

যে ব্যাকআপ কখনও restore করা হয়নি, সেটি কেবল একটি ধারণা। নিচের drill-এ প্রথম stack-এর পাশে চলমান একটি দ্বিতীয় stack-এ restore করা হবে। তাই production পরিবেশন চালিয়ে যাবে এবং আপনার টাইপ করা কোনো command সেখানে পৌঁছাবে না।

এর ভিত্তি হলো project name। Compose এটি directory name থেকে নেয় এবং তৈরি করা প্রতিটি container ও volume-এ সেটি যুক্ত করে। ব্যাকআপটি একটি নতুন directory-তে কপি করলে restored stack স্বয়ংক্রিয়ভাবে নিজস্ব volume পায়।

sudo install -d -m 700 -o "$USER" -g "$(id -gn)" /srv/myapp-restore
cd /srv/myapp-restore
cp /srv/backups/myapp/compose.yaml /srv/backups/myapp/.env .

কপি করা compose file সম্পাদনা করুন, যাতে প্রকাশিত host port চলমান stack-এর port-এর সঙ্গে সংঘর্ষ না করে। 18080:8080-এর জায়গায় 8080:8080 বসান, অথবা কপি করা .env-এ port নির্ধারণকারী variable পরিবর্তন করুন। এরপর কোনো কিছু start না করে container এবং তাদের খালি volume তৈরি করুন:

docker compose create
docker volume ls --filter label=com.docker.compose.project=myapp-restore

দ্বিতীয় command-এ production-এর মতো একই volume name দেখাবে, তবে সামনে myapp-restore_ থাকবে। Volume-গুলোতে ডেটা দিন, শুধু database start করুন, এবং dump load করুন:

docker run --rm -v myapp-restore_uploads:/data -v /srv/backups/myapp:/backup \
  alpine:3 tar xzf /backup/uploads.tar.gz -C /data
docker compose up -d db
docker compose exec -T db sh -c \
  'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists' \
  < /srv/backups/myapp/db-2026-08-16.dump

--clean --if-exists প্রতিটি object পুনরায় তৈরি করার আগে সেটি মুছে দেয়। ফলে restore বারবার চালানো যায়। এটি ছাড়া, ইতিমধ্যে ওই table থাকা database-এ দ্বিতীয়বার চালালে pg_restore: error: could not execute query: ERROR: relation "users" already exists দিয়ে থেমে যায়।

এরপর বাকি service-গুলো start করুন এবং ব্যবহারকারী যেভাবে পরীক্ষা করবে, সেভাবেই যাচাই করুন:

docker compose up -d --wait
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" -d "$POSTGRES_DB" -c "\dt"'
docker compose logs --tail=50

docker compose up -d --wait প্রতিটি service running বা healthy অবস্থার কথা না বলা পর্যন্ত অপেক্ষা করে। কোনো service কখনও সেই অবস্থায় না পৌঁছালে এটি non-zero exit করে। এই কারণেই ধাপটি script-এ ব্যবহার করা যায়। কোনো service কখনও healthy না হলে docker compose ps তার অবস্থা দেখায়। Compose healthcheck-এ ওই column কী দেখাচ্ছে তা ব্যাখ্যা করা হয়েছে। এরপর alternate port-এ app খুলে একটি বাস্তব account দিয়ে login করুন। একটি record লিখুন এবং volume-এ থাকা একটি file খুলুন। এই দুটি কাজই প্রমাণ দেয়: dump restore হয়েছে, volume restore হয়েছে, এবং দুটির ডেটা পরস্পরের সঙ্গে সামঞ্জস্যপূর্ণ। শুধু login page প্রদর্শিত হচ্ছে—এমন প্রমাণে আপনার ডেটা সম্পর্কে কিছুই নিশ্চিত হয় না।

drill সফল হলে এটি সরিয়ে ফেলুন:

docker compose down -v

এই একমাত্র ক্ষেত্রে -v সঠিক flag। Production directory-তে একই command চালালে আপনি যে volume-গুলো রক্ষা করতে চাইছেন, সেগুলোও মুছে যাবে।

Compose stack আপগ্রেড করা

আপনি বর্তমানে যে version চালাচ্ছেন এবং যে version-এ যেতে চান, তার মধ্যবর্তী প্রতিটি version-এর release notes পড়ুন। সেখানে breaking এবং migration শব্দ খুঁজুন। কয়েকটি major version এড়িয়ে upgrade সমর্থন না করলে projects সাধারণত release notes-এই তা উল্লেখ করে। কোনো migration চালানোর সময় ব্যর্থ হলে, schema-এর কিছু অংশ পরিবর্তন করার পরেই সেটি আপনাকে জানাতে পারে।

কিছু পরিবর্তন করার আগে বর্তমানে কী চলছে তা নথিবদ্ধ করুন:

docker compose images
docker image inspect --format '{{index .RepoDigests 0}}' postgres:16.4

docker compose images বর্তমানে প্রতিটি service যে image এবং tag ব্যবহার করছে, তা দেখায়। কোনো image-কে নির্দিষ্টভাবে শনাক্ত করার একমাত্র নির্ভরযোগ্য মান হলো digest। কারণ tag যেকোনো সময় অন্য image-এর দিকে সরিয়ে দেওয়া যেতে পারে।

উপরের sections-এ নেওয়া backup-টি box-এর বাইরে অন্য স্থানে copy করুন। Patch release-এর ক্ষেত্রেও এটি করুন। যেসব upgrade সহজ, সেগুলোর প্রস্তুতি নেওয়া মানুষ সাধারণত বন্ধ করে দেয়।

এরপর compose file-এ version নির্দিষ্ট করে দিন। কারণ latest কোনো version নয়:

services:
  db:
    image: postgres:16.4

image: postgres:latest ব্যবহার করলে docker compose pull আজ ওই tag যে image-এর দিকে নির্দেশ করছে, সেটিই fetch করবে। গতকাল আপনি কোন image চালাচ্ছিলেন, তা নির্দিষ্ট করার কোনো উপায় থাকবে না। নির্দিষ্ট tag ব্যবহার করলে upgrade একটি এক-লাইনের edit-এ পরিণত হয়। git diff-এ সেই edit পড়া যায় এবং আরেকটি edit করে তা ফিরিয়ে নেওয়া যায়। Application image-এর ক্ষেত্রেও একইভাবে নির্দিষ্ট version ব্যবহার করুন। Project-এর release page থেকে exact version নিন।

Image pull করে container পুনরায় তৈরি করুন:

docker compose pull
docker compose up -d --wait

docker compose up -d file-টিকে চলমান container-গুলোর সঙ্গে তুলনা করে। যেসব service-এর image বা configuration পরিবর্তিত হয়েছে, শুধু সেগুলোই পুনরায় তৈরি করে। এটি named volume পরিবর্তন করে না। তাই নতুন container বিদ্যমান data ব্যবহার করেই start হয়। এটাই এই কাজের উদ্দেশ্য। একই সঙ্গে এটিই ঝুঁকি, কারণ নতুন version প্রথমবার start হওয়ার সময়ই সাধারণত schema migration চলে।

কী ঘটছে তা monitor করুন:

docker compose ps
docker compose logs -f --tail=100 app

কোনো container ব্যর্থ হলে docker compose ps-এর STATUS column-এ Exited (1) দেখা যায়। এর কারণ log-এর শেষ কয়েকটি লাইনে থাকে। Migration error সেখানে স্পষ্টভাবে দেখা যায়, কিন্তু অন্য কোথাও নাও দেখা যেতে পারে। Log স্থির হলে login করুন এবং এক মিনিট app ব্যবহার করে দেখুন।

docker compose pull যদি no space left on device দিয়ে থেমে যায়, তবে পুরোনো image layer-ই সাধারণত কারণ। ব্যবহার না করা Docker image prune করা হলে disk space ফিরে পাওয়া যায়। Upgrade সফলভাবে কাজ করছে নিশ্চিত হওয়ার পরে pruning করুন, আগে নয়। কারণ দ্রুত rollback করার সময় ওই পুরোনো layer-গুলোর ওপরই নির্ভর করতে হয়।

আপগ্রেডে সমস্যা হলে কীভাবে rollback করবেন

এখানে দুটি পরিস্থিতি আছে, এবং এগুলোর জন্য প্রয়োজনীয় কাজের পরিমাণে বড় পার্থক্য রয়েছে। নতুন version schema পরিবর্তন না করলে rollback এক লাইনের কাজ: compose file-এ পুরোনো tag ফিরিয়ে দিন এবং docker compose up -d চালান। Container প্রতিস্থাপিত হবে, volume আগের অবস্থানে থাকবে, এবং পুরোনো code তার লেখা data পড়তে পারবে।

নতুন version schema migrate করলে পুরোনো code আর সেই data পড়তে পারবে না। Migration সাধারণত সামনে এগোনোর জন্য লেখা হয়, এবং অধিকাংশ project কোনো downgrade script release করে না। তাই পুরোনো version start হওয়ার পর renamed বা dropped column-এ প্রথম query চালাতেই ব্যর্থ হয়, এবং ERROR: column "avatar_url" does not exist ধরনের error দেখায়। ফিরে যাওয়ার উপায় হলো pull করার আগে নেওয়া dump: পুরোনো tag ফিরিয়ে দিন, database volume সরিয়ে নিন, সেটি খালি অবস্থায় আবার তৈরি করুন, dump restore করুন, তারপর start করুন। সেই dump না থাকলে আর কোনোভাবেই ফিরে যাওয়া যায় না। এ কারণেই pull করার আগে backup নিতে হয়।

Postgres major version-এ এই সমস্যা সবচেয়ে প্রকট। এখানে অনেকেই বিভ্রান্ত হন, কারণ ব্যর্থতা rollback-এর সময় নয়, upgrade-এর সময় দেখা দেয়। প্রতিটি major release-এর সঙ্গে disk-এর data format পরিবর্তিত হয়। postgres:16.4-কে postgres:17.2-এ পরিবর্তন করে docker compose up -d চালালে নতুন server start হতে অস্বীকার করে:

FATAL:  database files are incompatible with server
DETAIL:  The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.2.

Image নিজে থেকে pg_upgrade চালায় না। Compose stack-এর ভিতরে সমর্থিত পদ্ধতি হলো dump, replace, restore: পুরোনো version চালু থাকা অবস্থায় dump নিন, docker compose down চালান, database volume সরিয়ে নিন, নতুন tag নির্ধারণ করুন, নতুন ও খালি data directory-এর জন্য docker compose create চালান, database start করুন, dump restore করুন, তারপর বাকি service-গুলো start করুন। নতুন major version এক দিন বাস্তব network traffic সামলানো পর্যন্ত পুরোনো dump রেখে দিন। একই major version-এর মধ্যে minor upgrade, যেমন 16.4 থেকে 16.9, এসব কাজের প্রয়োজন হয় না, কারণ ওই version-গুলোর মধ্যে format স্থিতিশীল থাকে এবং container সরাসরি start হয়।

VPS snapshot কি backup?

এগুলো backup-এর বিকল্প নয়; বরং backup-এর পরিপূরক। দুটিই ভিন্নভাবে ব্যর্থ হয়। Snapshot hypervisor স্তরে পুরো disk কপি করে। তাই আপনি backup করতে ভুলে যাওয়া অংশসহ পুরো machine কয়েক মিনিটে আগের অবস্থায় ফিরিয়ে আনতে পারেন। নির্দিষ্ট একটি কাজের জন্য এটিই সঠিক উপায়: upgrade-এর কারণে server নষ্ট হয়েছে এবং আপনি সেটিকে বিশ মিনিট আগের অবস্থায় ফিরিয়ে নিতে চান।

অন্য সব কাজের জন্য এটি দুর্বল পদ্ধতি। এর granularity পুরো machine, তাই মুছে যাওয়া একটি table উদ্ধার করতে হলে আগে কোনো জায়গায় পুরো server restore করতে হবে, তারপর সেখান থেকে table বের করতে হবে। Retention সাধারণত অল্প সময়ের হয়। কপিগুলো সাধারণত server-এর একই provider account-এ থাকে। তাই account হারালে server এবং তার snapshot দুটিই একসঙ্গে হারাতে পারেন। চলমান machine-এর snapshot নেওয়া হলে database মাঝপথে চলা write অবস্থায় ধরা পড়ে। ফলে database প্রথমবার চালু হওয়ার সময় crash recovery করে, এবং তখনও সম্পন্ন না হওয়া যেকোনো transaction হারিয়ে যায়।

দুটিই ব্যবহার করুন। Snapshot হলো upgrade window-এর জন্য undo button। Dump হলো এমন কপি, যা মুছে যাওয়া account-এর পরেও টিকে থাকে। snapshot ও backup-এর পার্থক্য কোন failure-এর ক্ষেত্রে কোনটি কার্যকর, তা ব্যাখ্যা করে। একই backup directory একটি stack নতুন VPS-এ স্থানান্তর করাকেও স্মৃতি থেকে নতুন করে তৈরি করার বদলে নিয়মিত কাজে পরিণত করে।

কী ভুল হতে পারে এবং আপনি কী দেখতে পাবেন

down-এ volumes flag। docker compose down -v ফাইলে ঘোষিত named volume-গুলো সরিয়ে দেয়, এবং Compose Volume myapp_db_data Removed লেখা একটি লাইনের মাধ্যমে তা নিশ্চিত করে। এটি পূর্বাবস্থায় ফেরানোর কোনো উপায় নেই। সাধারণ docker compose down এগুলো অপরিবর্তিত রাখে। দীর্ঘ রূপ docker compose down --volumes লিখুন, যাতে ধ্বংসাত্মক flag-টি আপনাকে সম্পূর্ণ লিখে দিতে হয়।

magic string ছাড়া dump। pg_restore: error: did not find magic string in file header বোঝায় যে ফাইলটি archive নয়। সাধারণত docker compose exec-এ -T না থাকার কারণে এটি ঘটে, কারণ TTY সংযুক্ত থাকলে stream আপনার shell-এ যাওয়ার পথে রূপান্তরিত হয় এবং binary dump নষ্ট হয়ে পৌঁছায়। -T ব্যবহার করে আবার dump নিন, তারপর head -c 5 দিয়ে প্রথম পাঁচটি byte পরীক্ষা করুন।

যে password পরিবর্তন হয় না। restore-এর পরে FATAL: password authentication failed for user "appuser" দেখা গেলে বুঝবেন .env এবং data directory ভিন্ন সময়ের। image শুধু খালি data directory তৈরি করার সময় ওই password সেট করে, তাই পরে .env সম্পাদনা করলে database-এর ভেতরে কোনো পরিবর্তন হয় না। সামঞ্জস্যপূর্ণ .env restore করুন, অথবা ALTER USER দিয়ে database-এর ভেতরে password পরিবর্তন করুন।

দ্বিতীয়, খালি volume। Docker প্রয়োজন হলে volume তৈরি করে। তাই s না থাকলে docker run -v myapp_upload:/data একটি নতুন খালি volume-এ লিখে এবং সফলতার বার্তা দেয়। এরপর docker volume ls উভয় নাম দেখায়; একটির মধ্যে কোনো data থাকে না। নাম মনে করে লেখার বদলে docker volume ls থেকে volume-এর নাম কপি করুন।

production-এ restore চালানো। /srv/myapp-restore-এর বদলে /srv/myapp-এ restore command চালালে backup দিয়ে live data overwrite হয়, এবং দুই জায়গাতেই command একই রকম দেখায়। প্রতিটি restore command চালানোর আগে pwd পরীক্ষা করুন এবং drill-টি নিজের directory-তে রাখুন।

FAQ

docker compose down কি আমার ডেটা মুছে দেয়?

না। docker compose down container এবং default network সরিয়ে দেয়, কিন্তু named volume ও bind mount অপরিবর্তিত রাখে। docker compose down -v আপনার file-এ ঘোষিত named volume সরিয়ে দেয়, এবং এটি স্থায়ী। Bind mount হলো host directory, তাই Compose কখনো সেগুলো সরায় না। অন্য সবকিছু অপরিবর্তিত রেখে backup-এর সময় শুধু service-গুলো বন্ধ করতে চাইলে docker compose stop ব্যবহার করুন।

pg_dump চালানোর পরিবর্তে কি Postgres data directory কপি করতে পারি?

শুধু container বন্ধ থাকা অবস্থায়। Server চলার সময় তার file পরিবর্তিত হয়। ফলে কপিতে বিভিন্ন সময়ের মিশ্র অবস্থা থাকতে পারে, যা পুনরায় চালু করা সম্ভব নাও হতে পারে। File-level copy একটি নির্দিষ্ট Postgres major version-এর সঙ্গেও আবদ্ধ। তাই এটি অন্য version-এর অধীনে start হবে না। Container বন্ধ করুন, volume archive করুন, আবার start করুন, এবং এটিকে দ্রুত rebuild করার পদ্ধতি হিসেবে বিবেচনা করুন; একমাত্র backup হিসেবে নয়। Dump হলো portable copy এবং restore করার জন্য ব্যবহৃত কপি।

Compose-এ Postgres-কে নতুন major version-এ কীভাবে upgrade করব?

শুধু tag পরিবর্তন করলেই হবে না। নতুন server পুরোনো data directory ব্যবহার করে start হতে অস্বীকার করে এবং The data directory was initialized by PostgreSQL version 16, which is not compatible with this version 17.2 log করে। পুরোনো version চালু থাকা অবস্থায় pg_dump চালান। এরপর docker compose down চালান, database volume সরান, নতুন tag সেট করুন, খালি নতুন volume তৈরির জন্য docker compose create চালান, database start করুন এবং dump restore করুন। নতুন version বাস্তব network traffic সামলানো পর্যন্ত পুরোনো dump সংরক্ষণ করুন।

কত ঘন ঘন backup চালানো উচিত, এবং কত দিন সেগুলো সংরক্ষণ করা উচিত?

আপনি কতটা কাজ আবার করতে প্রস্তুত, তার সঙ্গে interval মিলিয়ে নিন। ব্যক্তিগত বা ছোট দলের stack-এর জন্য nightly backup উপযুক্ত। কোনো upgrade-এর ঠিক আগে একটি অতিরিক্ত manual backup নিন। Retention-এর ক্ষেত্রে এমন পর্যাপ্ত history রাখুন, যাতে দেরিতে শনাক্ত হওয়া ক্ষতিও পুনরুদ্ধার করা যায়। শুক্রবারে ধরা পড়া corrupted table-এর ক্ষেত্রে বৃহস্পতিবার রাতের copy যথেষ্ট নাও হতে পারে। restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune একটি যুক্তিসংগত প্রাথমিক policy। Schedule যাই হোক, প্রতি quarter-এ একবার সেটি থেকে restore করুন। এটি না করা পর্যন্ত আপনার backup নেই; শুধু file আছে।

Backup নেওয়ার জন্য কি পুরো stack বন্ধ করতে হবে?

সাধারণত না। Server চলাকালীন database dump consistent থাকে, তাই database-এর কোনো downtime দরকার হয় না। আসল প্রশ্ন হলো volume। App যদি শুধু file যোগ করে, যেমন uploads directory-তে, তাহলে চলমান অবস্থায় archive নেওয়া যথেষ্ট নিরাপদ। App যদি file-এর জায়গাতেই rewrite করে, তাহলে copy চলাকালীন শুধু ওই service-টি docker compose stop app দিয়ে বন্ধ করুন এবং পরে আবার start করুন। Database চালু রেখে app বন্ধ করা সাধারণত সবচেয়ে সংক্ষিপ্ত নিরাপদ window।