How It Works
এই page-এ কোনো setup নেই। এখানে শুধু বুঝবেন pipeline-টি ভেতরে কীভাবে কাজ করে — যাতে পরের page-এ YAML লেখার সময় প্রতিটি লাইনের মানে জানা থাকে।
What Is a Pipeline
এই section-এ শব্দটি বারবার আসবে, তাই আগে মানে পরিষ্কার করে নেওয়া দরকার।
Pipeline হলো কতগুলো ধাপের একটি সারি, যেগুলো একের পর এক নিজে থেকে চলে। আপনি শুরু করিয়ে দিলেন, বাকিটা নিজে হয়।
আপনি এখন হাতে যা করেন সেটিও আসলে একটি pipeline — শুধু প্রতিটি ধাপ নিজে চালান:
code লিখুন → push → ssh → git pull → docker compose up -d --buildCI/CD-র কাজ হলো এই সারিটিকে একটি file-এ লিখে রাখা, যাতে একটি machine প্রতিবার হুবহু একইভাবে চালায়।
দুটি নিয়ম মনে রাখুন:
- ধাপগুলো উপর থেকে নিচে চলে, একটা শেষ হলে পরেরটা
- কোনো ধাপ fail করলে সেখানেই সব থেমে যায়
দ্বিতীয় নিয়মটিই pipeline-কে নিরাপদ করে। Build fail করলে deploy-এর ধাপ পর্যন্ত পৌঁছানোই যাবে না, তাই ভাঙা code কখনো server-এ যেতে পারবে না।
CI and CD
দুটি আলাদা কাজ, একসাথে লেখা হয় বলে অনেকে গুলিয়ে ফেলেন।
CI — Continuous Integration প্রতিটি code change build করে দেখা হয় ঠিক আছে কিনা। কিছু ভাঙলে সেখানেই ধরা পড়ে। Server-এ কিছু হয় না।
CD — Continuous Deployment Build পাশ করলে সেই জিনিস নিজে থেকে server-এ চলে যায়।
| CI | CD | |
|---|---|---|
| কখন চলে | প্রতিটি pull request-এ | শুধু main-এ push হলে |
| কী করে | build করে check করে | server-এ deploy করে |
| Server ছোঁয় | না | হ্যাঁ |
| Fail করলে | merge আটকে যায় | deploy আটকে যায় |
What Is a Runner
Runner হলো একটি অস্থায়ী Ubuntu machine যা GitHub আপনাকে প্রতিবার নতুন করে দেয়। আপনার সব build এই machine-এ হবে।
তিনটি কথা মনে রাখুন:
- প্রতিবার সম্পূর্ণ নতুন — আগের run-এর কিছুই থাকে না
- কাজ শেষ হলে মুছে যায়
- আপনার server-এর সাথে এর কোনো সম্পর্ক নেই
শেষ কথাটি জরুরি। Runner আপনার server চেনে না, তার কাছে কোনো password বা key নেই। আপনি না দিলে সে ঢুকতে পারবে না। এই কারণেই পরের page-এ SSH key আর GitHub Secrets নিয়ে কাজ করতে হবে।
Where the Build Runs
এটিই এই page-এর সবচেয়ে গুরুত্বপূর্ণ প্রশ্ন। উত্তরটি পুরো pipeline-এর আকার ঠিক করে দেয়।
পথ ১ — Server নিজে build করে
push → server → git pull → docker compose up -d --buildঅনেকেই এভাবে করেন। কাজ করে, কিন্তু production-এ তিনটি সমস্যা:
- Build-এ RAM আর CPU লাগে। ছোট VPS-এ Next.js build প্রায়ই
Killedহয়ে যায়, আর build চলার সময় live site ধীর হয়ে থাকে - Server প্রতিবার নতুন করে package টানে। একই commit থেকে দুইবার build করলে দুটি আলাদা জিনিস তৈরি হতে পারে
- Source code server-এ পড়ে থাকে। কেউ ঢুকে পড়লে আপনার পুরো codebase আর git history তার হাতে
পথ ২ — Runner build করে, server শুধু চালায়
push → runner image বানায় → registry-তে রাখে → server টেনে চালায়Server আর কিছু বানায় না। সে একটি তৈরি image টেনে এনে চালায়, ব্যস।
| Server build করে | Runner build করে | |
|---|---|---|
| Build-এর খরচ | আপনার server-এর RAM আর CPU | GitHub-এর runner-এর |
| Server-এ code | আছে | নেই |
| একই commit, একই ফল | নিশ্চিত নয় | নিশ্চিত |
| Deploy-এ যা যায় | code | তৈরি image |
| Rollback | আবার build, ৩-৫ মিনিট | পুরোনো image, ১০ সেকেন্ড |
| Server-এ যা লাগে | Docker, git, আর অনেকটা RAM | শুধু Docker |
এই section-এ পথ ২ শেখানো হবে। VPS-এ Docker দিয়ে deploy করার standard পদ্ধতি এটিই।
Runner-এর সময় public repository-তে সীমাহীন এবং বিনামূল্যে। Private repository-তে GitHub-এর free plan-এ মাসে ২,০০০ মিনিট পাওয়া যায়।
একটি Next.js app-এর build-এ ২-৩ মিনিট লাগে, তাই মাসে কয়েকশ deploy করলেও এতে কুলিয়ে যায়। তবু হিসাব রাখতে চাইলে দেখুন — Settings → Billing → Actions।
What Is a Registry
Runner image বানাল। কিন্তু runner তো কিছুক্ষণ পরে মুছে যাবে। তাহলে image-টি রাখব কোথায়?
Registry হলো image রাখার গুদাম। GitHub-এর নিজের registry-র নাম GHCR — GitHub Container Registry। আপনার repository-র সাথেই আসে, আলাদা account লাগে না।
Runner image বানায়
↓ docker push
GHCR
↓ docker pull
Server image টেনে চালায়Docker Hub-ও ব্যবহার করা যায়। কিন্তু GHCR আপনার repository-র সাথে যুক্ত থাকায় GitHub Actions নিজে থেকেই login করে নিতে পারে — আলাদা কোনো password বা secret তৈরি করতে হয় না।
Image Tags
একটি image-এর নামের শেষে একটি tag থাকে। এই tag দিয়েই কোন version চলছে তা বোঝা যায়।
ghcr.io/your-username/myapp:latest ← কোনটা? বলা যাচ্ছে না
ghcr.io/your-username/myapp:a3f9c21 ← এই commit-এর, নিশ্চিতআমরা প্রতিটি image-কে তার commit-এর নাম দিয়ে tag করব। GitHub Actions-এ এটি github.sha নামে পাওয়া যায়।
এতে তিনটি লাভ:
- Production-এ ঠিক কোন code চলছে তা এক নজরে বলা যায়
- পুরোনো প্রতিটি version registry-তে জমা থাকে
- Rollback মানে শুধু পুরোনো tag-টি আবার চালানো
শুধু latest tag ব্যবহার করবেন না। latest আজ একটা জিনিস, কাল আরেকটা — কোনো নির্দিষ্ট version-কে সে বোঝায় না। তখন rollback করার কিছুই থাকে না, কারণ পুরোনো image-টির আর কোনো নাম নেই।
আমরা দুটি tag একসাথে দেব — a3f9c21 (নির্দিষ্ট) আর latest (সুবিধার জন্য)। Deploy-এ সবসময় নির্দিষ্টটি ব্যবহার হবে।
Two Kinds of Compose File
Docker section-এ আপনি compose.yaml দেখেছেন code-এর পাশে, project-এর root folder-এ। এই section-এ সেটি থাকবে server-এ, একা।
একই নামের file, কিন্তু ভেতরে একটি শব্দ আলাদা — আর ওই শব্দটিই ঠিক করে দেয় file-টি কোথায় থাকবে।
build: — compose নিজে image বানায়
services:
web:
build:
context: ./webbuild: মানে compose-কে বলা হচ্ছে “তুমি image বানাও”। বানাতে code লাগে, Dockerfile লাগে। তাই এই file-টিকে code-এর পাশে থাকতেই হবে।
image: — compose শুধু চালায়
services:
web:
image: ghcr.io/your-username/myapp:${TAG}image: মানে “বানানোর দরকার নেই, registry থেকে এই তৈরি জিনিসটি এনে চালাও”। Code লাগে না, তাই file-টি server-এ একা থাকতে পারে।
build: | image: | |
|---|---|---|
| Image কে বানায় | compose নিজে | আগেই বানানো হয়ে গেছে |
| পাশে code লাগে | হ্যাঁ | না |
| কোথায় রাখা হয় | code-এর folder-এ | যেকোনো জায়গায় |
| কোথায় কাজে লাগে | নিজের computer-এ | production server-এ |
এখানে একটি জিনিস অনেকে গুলিয়ে ফেলেন, তাই পরিষ্কার করে নিন — এই pipeline-এ compose কখনো কিছু বানায় না।
Runner image বানায় সরাসরি Dockerfile দিয়ে, compose দিয়ে নয়। তাই runner-এ কোনো compose file-ই থাকে না।
তিনটি জায়গা, তিনটি আলাদা কাজ:
Runner → BUILD করে → লাগে: code + Dockerfile (compose নেই)
Registry → জমা রাখে → লাগে: কিছুই না
Server → RUN করে → লাগে: compose.yaml + .env (code নেই)Server-এ কোনো Dockerfile নেই, runner-এ কোনো compose.yaml নেই। দুটি কখনো একসাথে থাকে না।
তাই এই section-এ compose-এর কাজ শুধু চালানো, কখনো বানানো নয়।
The Full Flow
সব মিলিয়ে push করার পর যা ঘটে:
আপনার Computer
│ git push origin main
▼
GitHub Repository
│ workflow file দেখে trigger হয়
▼
GitHub Runner (নতুন খালি Ubuntu machine)
│ ১. code clone করে
│ ২. docker build ← fail করলে এখানেই থেমে যায়
│ ৩. docker push → GHCR
▼
GHCR Registry
│ image জমা হলো, নাম: commit-এর sha
▼
আপনার Server (SSH দিয়ে ঢোকে)
│ ৪. .env-এ নতুন TAG লিখে দেয়
│ ৫. docker compose pull
│ ৬. docker compose up -d
│ ৭. curl দিয়ে check করে
▼
Site Live (fail করলে পুরোনো TAG ফিরিয়ে আনে)The Workflow File
Pipeline-টি আসলে আপনার project-এ রাখা একটি YAML file। GitHub সেটি পড়ে কাজটি করে।
File-টি একটি নির্দিষ্ট folder-এ থাকতে হয়:
myapp/
├── .github/
│ └── workflows/
│ ├── ci.yml ← pull request-এ build check
│ └── deploy.yml ← main-এ push হলে deploy
├── Dockerfile
├── .dockerignore
└── package.jsonFolder-এর নাম হুবহু .github/workflows হতে হবে। ভুল হলে GitHub file-টি খুঁজেই পাবে না, আর কোনো error ও দেখাবে না।
এখানে কোনো compose.yaml নেই — এটি ইচ্ছাকৃত। Compose file আর .env শুধু server-এ থাকে, repository-তে নয়। কারণ ওই দুটিতে server-এর নিজস্ব তথ্য আর secret থাকে।
একটি ছোট workflow দেখে প্রতিটি অংশ বুঝে নিই:
name: CI
on:
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
uses: docker/build-push-action@v6
with:
context: .
push: false| Key | মানে |
|---|---|
name | Actions tab-এ যে নাম দেখাবে |
on | কখন চলবে — এখানে main-এ আসা pull request-এ |
jobs | কাজের group। এক workflow-এ একাধিক job থাকতে পারে |
build | job-এর নাম, আপনি নিজে দেন |
runs-on | কোন OS-এর runner লাগবে |
steps | job-এর ভেতরে ধাপগুলো, উপর থেকে নিচে চলে |
uses and run
Step দুই ধরনের হয়।
uses — অন্যের লেখা তৈরি action চালায়:
- uses: actions/checkout@v4actions/checkout হলো GitHub-এর নিজের লেখা একটি action, যা repo-র code runner-এ clone করে। নিজে লিখতে গেলে অনেক লাইন লাগত।
run — সরাসরি shell command চালায়, runner-এর ভেতরে:
- run: docker image lsএই guide-এর দুটি workflow-এ প্রায় সব step-ই uses — কারণ Docker-এর কাজগুলো তৈরি action দিয়েই হয়ে যায়।
run লাগে যখন নিজের কোনো command চালাতে হয় — যেমন test চালানো, বা log-এ কিছু লিখে রাখা।
with
কোনো action-কে extra তথ্য দিতে with ব্যবহার হয়। উপরের example-এ docker/build-push-action-কে বলা হয়েছে কোথা থেকে build করবে (context) আর registry-তে পাঠাবে কিনা (push)।
Why Two Separate Jobs
Deploy workflow-এ দুটি job থাকবে, একটি নয়।
jobs:
build:
runs-on: ubuntu-latest
steps:
# image build করে registry-তে পাঠায়
deploy:
needs: build # ← মূল লাইন
runs-on: ubuntu-latest
steps:
# server-এ SSH করেneeds: build লাইনটি বলছে — build job পাশ না করলে deploy job শুরুই হবে না।
এর মানে আপনার server তখনই ছোঁয়া হবে যখন image ইতিমধ্যে তৈরি হয়ে registry-তে পৌঁছে গেছে। Build fail করলে server আগের image নিয়েই চলতে থাকবে, কেউ টেরও পাবে না।
needs ছাড়া লিখলে দুটি job একসাথে চলে। তখন server এমন একটি image টানতে যাবে যা এখনো তৈরিই হয়নি — deploy fail করবে, আর কেন করল তা log দেখে বোঝা কঠিন।
Reading a Failed Run
Pipeline fail করলে GitHub-এ Actions tab-এ যাবেন।
- লাল X মানে কোনো job fail করেছে
- সেটিতে click করলে step-গুলো, আবার click করলে পুরো log খুলবে
- Log-এর শেষ ২০ লাইন পড়ুন — আসল error প্রায় সবসময় সেখানেই
একটি step fail করলে পরেরগুলো চলেই না। তাই প্রথম লাল X-টিই আসল সমস্যা।
Real-World Note
Third-party action মানে অন্যের code আপনার pipeline-এ চলছে।
uses: someone/some-action@main লিখলে আপনি একজন অচেনা মানুষের code-কে আপনার secrets-এর কাছে যাওয়ার অনুমতি দিচ্ছেন। @main মানে “সবসময় সর্বশেষ version” — অর্থাৎ সে যখন খুশি code বদলাতে পারে, আর আপনার pipeline সেই বদলানো code চালাবে।
এটি একটি সত্যিকারের attack path। ২০২৫ সালের ১৪ মার্চ tj-actions/changed-files নামের একটি action-এ ঠিক এই ঘটনা ঘটে। Action-টি ২৩,০০০-এরও বেশি repository ব্যবহার করত। Attacker ভেতরে একটি malicious script ঢুকিয়ে দেয় যা runner-এর memory থেকে secrets টেনে build log-এ লিখে দিত। Public repository-র log সবাই পড়তে পারে, তাই সেগুলোর secrets প্রকাশ্যে চলে আসে।
সবচেয়ে জরুরি শিক্ষাটি অনেকে বুঝতে ভুল করেন। Attacker শুধু code বদলায়নি — সে পুরোনো version tag গুলোও ওই malicious commit-এর দিকে ঘুরিয়ে দিয়েছিল। অর্থাৎ যাঁরা @v44 লিখে রেখেছিলেন, তাঁদের কোনো লাভ হয়নি।
যাঁরা বেঁচে গিয়েছিলেন তাঁরা commit SHA দিয়ে pin করেছিলেন। SHA বদলানো যায় না — সেটিই একমাত্র আসল সুরক্ষা।
তিনটি নিয়ম:
@mainকখনো নয়। এখানে attack করারও দরকার নেই — maintainer নিজেই যেকোনো সময় code বদলাতে পারেন@v4সর্বনিম্ন মান। বেশিরভাগ ক্ষেত্রে যথেষ্ট, তবে উপরের ঘটনা দেখাল যে tag-ও সরানো যায়- সত্যিকারের সুরক্ষা commit SHA। সম্পূর্ণ ৪০ অক্ষর লিখতে হবে, ছোট করে লিখলে GitHub চিনতে পারে না:
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2পাশে comment হিসেবে version লিখে রাখুন, না হলে ছয় মাস পরে বোঝা যাবে না কোনটা কী।
actions/ এবং docker/ দিয়ে শুরু হওয়া action-গুলো যথাক্রমে GitHub আর Docker-এর নিজের — ঝুঁকি তুলনামূলক কম, তাই এই guide-এ @v4 ধরনের tag ব্যবহার করা হয়েছে।
Quick Check
- Pipeline-এর কোনো ধাপ fail করলে পরেরগুলোর কী হয়?
- Build কেন server-এ না করে runner-এ করা উচিত?
- Registry কী এবং কেন লাগে?
- Image-এ শুধু
latesttag দিলে কী হারাবেন? - compose-এ
build:আরimage:-এর পার্থক্য কী? - এই pipeline-এ image কে বানায় — compose, নাকি Dockerfile?
-
needs: buildনা থাকলে কী সমস্যা হয়?
পরবর্তী → Setup