Setup
এই page-এর কাজ একবারই করতে হয়। এরপর আর কখনো এখানে ফিরে আসতে হবে না। শেষে GitHub-এর কাছে আপনার server-এ ঢোকার অনুমতি থাকবে, আর server registry থেকে image টানতে পারবে।
Assumptions
এই page ধরে নিচ্ছে নিচের জিনিসগুলো আপনার server-এ আগে থেকেই আছে:
- Docker এবং Docker Compose install করা
- আপনার app হাতে চলছে —
docker compose up -dদিলে site খোলে - একটি non-root user যা দিয়ে আপনি SSH করেন এবং
dockercommand চালাতে পারেন curlএবংbashআছে
এগুলো কীভাবে করতে হয় তা এই section-এর বিষয় নয়। এখানে শুধু এই চলমান setup-টিকে automatic করা হবে।
শেষ লাইনটি নিয়ে চিন্তার কিছু নেই — Ubuntu 24.04-এ curl আর bash দুটোই আগে থেকেই থাকে। তবু কেন লাগে জেনে রাখুন:
curl— deploy-এর শেষে pipeline এটি দিয়ে দেখে site সত্যিই সাড়া দিচ্ছে কিনাbash— deploy script-এtrapব্যবহার করা হয়, যা fail করলে নিজে থেকে rollback করে।trap ... ERRশুধুbash-এ চলে, সাধারণsh-এ নয়
Minimal বা Alpine-ভিত্তিক server ব্যবহার করলে দুটোই আগে বসিয়ে নিন।
What CI/CD Needs
মাত্র চারটি জিনিস:
| # | কী | কেন |
|---|---|---|
| 1 | দুটি file রাখার একটি folder | Server-এ আর code থাকবে না, শুধু compose.yaml আর .env |
| 2 | শুধু Actions-এর জন্য একটি SSH key | নিজের key বা .pem দেওয়া বিপজ্জনক |
| 3 | তিনটি GitHub Secret | GitHub এগুলো দিয়ে server-এ ঢুকবে |
| 4 | Registry থেকে image টানার অনুমতি | না থাকলে docker compose pull fail করবে |
One Decision First
ধাপে যাওয়ার আগে একটি সিদ্ধান্ত নিতে হবে, কারণ এর উপর নির্ভর করে শেষ ধাপটি আপনার লাগবে কিনা।
আপনার image private রাখবেন, নাকি public করবেন?
GitHub-এ দুটি আলাদা setting আছে। অনেকে ভাবেন একটাই।
Repository visibility → কে আপনার source code দেখতে পাবে
Package visibility → কে আপনার image টানতে পারবেএই দুটি একে অপরের সাথে যুক্ত নয়। Repository private রেখেও package public করা যায়।
| Private package | Public package | |
|---|---|---|
| Source code | কেউ দেখবে না | কেউ দেখবে না |
| Image টানা | token লাগবে | যে কেউ পারবে |
| GHCR সীমা | ৫০০ MB, মাসে ১ GB | সীমাহীন |
| Server-এ login | লাগবে | লাগবে না |
Public package-এ কেউ image টেনে আপনার build করা app দেখতে পাবে — compiled file আর runtime node_modules। আপনার .env, source file বা git history যাবে না।
কখন public করা নিরাপদ: আপনার site যদি এমনিতেই সবার জন্য খোলা থাকে — documentation, blog, portfolio — তাহলে image-এ যা আছে তা তো browser-এ সবাই দেখছেই।
কখন নয়: client-এর app, ভেতরে business logic আছে এমন কিছু, বা যেকোনো private product।
একটি জিনিস আগে জেনে নিন — package তৈরিই হয় প্রথম push-এর পরে। এখন GitHub-এ গিয়ে খুঁজলে কিছু পাবেন না। তাই আপনার সিদ্ধান্ত অনুযায়ী পথ দুটি আলাদা।
Private রাখবেন — নিচের শেষ ধাপে token বানিয়ে server-কে login করান। প্রথম deploy-ই কাজ করবে।
Public করবেন — শেষ ধাপটি বাদ দিন। তখন প্রথম deploy-এর deploy job fail করবে, কারণ package তখনো private অবস্থায় তৈরি হয়েছে আর server-এর অনুমতি নেই। Log-এ দেখাবে:
Error response from daemon: error from registry: unauthorizedভয় পাবেন না, এটাই স্বাভাবিক। এখন package তৈরি হয়ে গেছে, তাই visibility বদলাতে পারবেন:
GitHub → আপনার profile → Packages → package-টি → Package settings → Change visibility → Public
তারপর Actions-এ গিয়ে Re-run failed jobs চাপুন। এবার চলবে।
Restructure the Server Folder
এখন আপনার server-এ সম্ভবত পুরো project clone করা আছে — code, Dockerfile, সব। নতুন পদ্ধতিতে এর কিছুই লাগবে না।
Server-এ শুধু দুটি file থাকবে:
/opt/myapp/
├── compose.yaml ← image টানে, build করে না
└── .env ← secret এবং কোন version চলবেServer-এ SSH করে folder তৈরি করুন এবং নিজের user-কে মালিকানা দিন:
sudo mkdir -p /opt/myapp
sudo chown -R $USER:$USER /opt/myappchown লাইনটি বাদ দেবেন না। Folder-টি root-এর থাকলে GitHub Actions সেখানে .env file লিখতে পারবে না, আর deploy Permission denied দিয়ে fail করবে।
এখন শুধু folder-টি তৈরি রাখুন। ভেতরে কী লিখবেন তা পরের page-এ আছে, কারণ তখন আপনার image-এর নাম জানা থাকবে।
পুরোনো clone করা folder-টি এখনই মুছবেন না। নতুন pipeline কাজ করা শুরু করলে তারপর মুছবেন। ততক্ষণ সেটিই আপনার backup।
Create a Key for GitHub Actions
এখন GitHub-কে server-এ ঢোকার অনুমতি দিতে হবে। এর জন্য একটি নতুন SSH key তৈরি করব — শুধু এই কাজের জন্য।
আপনার ব্যক্তিগত SSH key বা cloud provider-এর দেওয়া .pem file কখনো GitHub-এ দেবেন না। ওই key দিয়ে আপনি নিজে সব server-এ ঢোকেন। একবার ফাঁস হলে সব হারাবেন।
আলাদা key-র সুবিধা: সন্দেহ হলে শুধু authorized_keys থেকে ওই এক লাইন মুছে দিলেই GitHub-এর access বন্ধ, আপনার নিজের কিছুই ভাঙে না।
আপনার নিজের computer-এ key তৈরি করুন:
ssh-keygen -t ed25519 -C "github-actions" -f ~/.ssh/gh_actions -N ""-t ed25519— key-এর ধরন। এখনকার সবচেয়ে ভালো ধরন এটি-C "github-actions"— একটি label, পরে চিনতে সুবিধা হবে-f ~/.ssh/gh_actions— এই নামে save হবে, আপনার আসল key-র উপরে লিখবে না-N ""— কোনো passphrase নেই। Actions একটি machine, সে passphrase টাইপ করতে পারে না
দুটি file তৈরি হলো:
| File | কী | কোথায় যাবে |
|---|---|---|
gh_actions | Private key | GitHub Secrets-এ |
gh_actions.pub | Public key | Server-এ |
এবার আপনার computer থেকে public key server-এ পাঠান:
cat ~/.ssh/gh_actions.pub | ssh USER@YOUR_SERVER_IP \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"authorized_keys একটি তালিকা। এই file-এ যার public key লেখা আছে, সে server-এ ঢুকতে পারে। আমরা সেখানে GitHub-এর জন্য একটি লাইন যোগ করলাম।
ওই দুটি chmod বাদ দেবেন না।
কী করে
chmod 700 ~/.ssh— এই folder-এ শুধু আপনি ঢুকতে পারবেনchmod 600 authorized_keys— এই file শুধু আপনি পড়তে ও লিখতে পারবেন
কেন দরকার
এই file-এ অন্য কেউ লিখতে পারলে সে নিজের key যোগ করে দিতে পারবে। তারপর সে-ও আপনার server-এ ঢুকে যাবে।
SSH কী করে
SSH server এই ঝুঁকিটা জানে। তাই key ব্যবহারের আগে সে permission দেখে নেয়। অন্য কেউ ওখানে লিখতে পারে দেখলে সে key-টি ব্যবহার করে না।
আপনি কী দেখবেন
Permission denied (publickey)SSH কারণ বলে না। আপনি ভাববেন key ভুল হয়েছে। আসলে key ঠিক আছে, permission ভুল।
Test the Key
তিনটি ছোট পরীক্ষা। তিনটিই আপনার নিজের computer থেকে চালাবেন।
এখানে পাশ করা মানে pipeline-ও পাশ করবে। তাই এই ধাপটি বাদ দেবেন না।
১. Key দিয়ে ঢোকা যায়?
ssh -i ~/.ssh/gh_actions USER@YOUR_SERVER_IPঢুকতে পারলে ঠিক আছে। exit লিখে বেরিয়ে আসুন।
২. Docker চলে?
ssh -i ~/.ssh/gh_actions USER@YOUR_SERVER_IP "docker compose version"Version দেখালে ঠিক আছে।
৩. Folder-এ লেখা যায়?
ssh -i ~/.ssh/gh_actions USER@YOUR_SERVER_IP "touch /opt/myapp/ok && rm /opt/myapp/ok && echo done"done দেখালে ঠিক আছে।
২ আর ৩ নম্বরে command-টি quote-এর ভেতরে লেখা। এভাবে চালালে server আপনাকে কোনো terminal দেয় না — শুধু ওই command চালিয়ে ফল ফেরত দেয়। GitHub Actions ঠিক এভাবেই কাজ করে।
এই পার্থক্যটা গুরুত্বপূর্ণ। Terminal না খুললে ~/.bashrc file-টি চলে না, আর সেখানে সাজিয়ে রাখা command গুলো পাওয়া যায় না। command not found এলে এটাই কারণ।
তখন server-এ which docker চালিয়ে সম্পূর্ণ path বের করুন, আর workflow-এ সেই path ব্যবহার করুন।
Add Three GitHub Secrets
Secret হলো GitHub-এর ভেতরে লুকিয়ে রাখা তথ্য — password, key, এই ধরনের জিনিস।
দুটি সুরক্ষা পাবেন।
এক — কেউ পড়তে পারে না। একবার save করার পরে আপনি নিজেও আর value-টি দেখতে পাবেন না। শুধু workflow সেটি ব্যবহার করতে পারে।
দুই — log-এ লুকিয়ে যায়। Workflow যদি ভুল করে secret-টি log-এ লিখে ফেলে, GitHub আসল লেখাটি সরিয়ে তারকা চিহ্ন বসিয়ে দেয়:
আপনার secret → ghp_a1b2c3d4e5f6
Log-এ দেখাবে → ***তাই কেউ Actions-এর log পড়লেও আসল value জানতে পারবে না।
GitHub-এ যান:
Your Repository → Settings → Secrets and variables → Actions → New repository secretতিনটি secret তৈরি করুন:
| Name | Value |
|---|---|
SERVER_HOST | Server-এর IP address, যেমন 13.234.56.78 |
SERVER_USER | SSH username, যেমন ubuntu |
SSH_PRIVATE_KEY | gh_actions file-এর সম্পূর্ণ content |
আপনার নিজের computer-এ private key-র content দেখতে:
cat ~/.ssh/gh_actions-----BEGIN OPENSSH PRIVATE KEY----- থেকে -----END OPENSSH PRIVATE KEY----- পর্যন্ত সবটুকু copy করুন। শেষ লাইনের পরের খালি লাইনটিও রাখুন। একটি অক্ষর বাদ পড়লে error in libcrypto নামের একটি বিভ্রান্তিকর error আসবে।
Registry-র জন্য কোনো secret লাগবে না। GitHub Actions নিজে থেকেই একটি অস্থায়ী token পায় (GITHUB_TOKEN) যা দিয়ে সে GHCR-এ image পাঠাতে পারে। এটি প্রতিটি run-এর শেষে মুছে যায়।
Registry Access
Package public করবেন ঠিক করে থাকলে এই পুরো ধাপটি বাদ দিন। উপরের callout-এ লেখা পথে যাবেন — প্রথম deploy fail করবে, তারপর package public করে job আবার চালাবেন।
Private package-এর image টানতে server-এর একটি token লাগবে। GitHub-এ গিয়ে বানান:
GitHub → Settings (আপনার profile-এর) → Developer settings
→ Personal access tokens → Tokens (classic) → Generate new tokenশুধু একটি অনুমতি টিক দিন — read:packages। আর কিছু নয়।
write:packages বা repo টিক দেবেন না। Server-এর কাজ শুধু image পড়া। এই token যদি কোনোদিন ফাঁস হয়, তাহলে সে দিয়ে শুধু image পড়া যাবে — কিছু মুছে দেওয়া বা code বদলে দেওয়া যাবে না।
এবার server-কে GHCR-এ login করাতে হবে। Server-এ SSH করে এই command চালান:
echo "YOUR_TOKEN" | docker login ghcr.io -u YOUR_GITHUB_USERNAME --password-stdinLogin Succeeded দেখালে হয়ে গেল। একবারই করতে হয় — Docker credential মনে রাখে।
এখানে আপনি server-এ login করছেন না — সেখানে তো আগে থেকেই আছেন। এই command server-এর Docker-কে GitHub-এর registry-তে login করায়।
Common Blockers
দুটি সমস্যা খুব সাধারণ। দুটোই চুপচাপ pipeline ভেঙে দেয়, আর error message পড়ে কারণ বোঝা যায় না।
Firewall দিয়ে SSH আটকানো থাকলে
অনেকে নিরাপত্তার জন্য শুধু নিজের বাসার IP থেকে SSH allow করে রাখেন। কিন্তু GitHub runner-এর IP প্রতিবার বদলায়, আর সেটি হাজার হাজার IP-র একটি বিশাল তালিকা। আগে থেকে allow করে রাখার উপায় নেই।
সমাধান হলো IP দিয়ে নয়, key দিয়ে নিরাপত্তা দেওয়া। Port 22 সবার জন্য খোলা রাখুন, কিন্তু password দিয়ে login বন্ধ করে দিন। Server-এ SSH করে চালান:
printf 'PasswordAuthentication no\nPermitRootLogin no\n' | \
sudo tee /etc/ssh/sshd_config.d/99-hardening.conf
sudo sshd -t && sudo systemctl restart sshদুটি জিনিস খেয়াল করুন।
Ubuntu-তে সরাসরি /etc/ssh/sshd_config-এ লিখবেন না। ওই file একই setting দুইবার পেলে প্রথমটি মানে, আর cloud provider-এর রাখা file আগে পড়া হয় — তাই আপনার লেখা চুপচাপ অগ্রাহ্য হবে। এজন্যই উপরে sshd_config.d/ folder ব্যবহার করা হয়েছে।
sudo sshd -t অংশটি config-এ ভুল আছে কিনা দেখে নেয়। এটি ছাড়া restart করলে ভুল config-এর কারণে আপনি নিজেই server থেকে বেরিয়ে যেতে পারেন। Restart-এর পরে বর্তমান terminal বন্ধ না করে আরেকটি terminal থেকে SSH করে দেখুন।
fail2ban runner-কে ban করে দিলে
Deploy বারবার fail করলে fail2ban সেই IP ব্যান করতে পারে। তখন পরের deploy টাইমআউট হবে আর error message কিছুই বোঝাবে না। সন্দেহ হলে server-এ গিয়ে দেখুন:
sudo fail2ban-client status sshdReal-World Note
আপনার pipeline এখন server-এর একটি চাবি ধরে আছে।
এর মানে GitHub repository-তে যার push করার ক্ষমতা আছে, সে workflow file বদলে server-এ যেকোনো command চালাতে পারে। Repository-র access এখন আর শুধু code-এর access নয় — এটি server-এর access।
চারটি নিয়ম মানুন:
rootকখনো নয়।SERVER_USERসবসময় সাধারণ user হবে।- Branch protection চালু করুন।
main-এ সরাসরি push বন্ধ করুন, review ছাড়া merge বন্ধ করুন। Settings → Branches → Add rule। এটি একটিমাত্র সবচেয়ে কার্যকর পদক্ষেপ। - Fork থেকে আসা pull request-এ secrets দেবেন না। GitHub এটি default-ই বন্ধ রাখে — চালু করার লোভ সামলান।
- Key rotate করুন। কেউ team ছাড়লে
~/.ssh/authorized_keysথেকে পুরোনো লাইন মুছে নতুন key বানান।
আরেকটি কথা জেনে রাখুন। docker login করার পরে আপনার token ~/.docker/config.json file-এ জমা থাকে — সেটি encrypt করা নয়, শুধু base64 করা। যে কেউ ওই file পড়তে পারলেই token পেয়ে যাবে। এই কারণেই token-এ শুধু read:packages অনুমতি রাখা এত জরুরি।
একটি দ্রুত পরীক্ষা: আপনার repository-তে যাদের write access আছে তাদের list দেখুন। ওই সবার হাতেই এখন আপনার production server আছে।
Quick Check
পরের page-এ যাওয়ার আগে সব মিলিয়ে নিন:
-
/opt/myappfolder আছে এবং আপনার user-এর মালিকানায়? -
ssh -i ~/.ssh/gh_actions USER@IPদিয়ে ঢোকা যাচ্ছে? -
ssh -i ~/.ssh/gh_actions USER@IP "docker compose version"কাজ করছে? -
/opt/myapp-এ file লেখার test পাশ করেছে? - GitHub-এ তিনটি secret যোগ করা হয়েছে?
- Package private রাখলে — server-এ
docker login ghcr.ioসফল হয়েছে? (public করলে এটি লাগবে না)
পরবর্তী → First Pipeline