cPanel-এ Next.js deploy: GitHub Actions + FTP দিয়ে সম্পূর্ণ CI/CD
অনেক বাংলাদেশি hosting company এখন cPanel-এ "Setup Node.js App" দেয়। এর মানে শুধু static HTML না, পুরো Next.js app (SSR, API route, rewrite সহ) shared hosting-এই চালানো যায়। কিন্তু প্রতিবার হাতে build করে FileZilla দিয়ে শত শত file পাঠানো ঝামেলার, আর FTP-তে মাঝেমধ্যে file fail-ও হয়।
এই লেখায় দেখাব কীভাবে main branch-এ push করলেই GitHub নিজে build করে, শুধু বদলানো file FTP দিয়ে পাঠায়, আর app restart করে।
কেন GitHub-এ build, server-এ না?
Next.js build-এ ১-২ GB RAM লাগে। Shared hosting সাধারণত এর চেয়ে কম দেয়, তাই server-এ build প্রায়ই মাঝপথে বন্ধ হয়ে যায়। GitHub Actions-এর runner-এ যথেষ্ট RAM আছে, আর খরচও লাগে না (public/private দুই repo-তেই মাসে নির্দিষ্ট পরিমাণ মিনিট free)।
Server-এ তখন শুধু দরকার:
.next: build outputpublicserver.jsnext.config.mjspackage.jsonআরpackage-lock.json
app, components, lib-এর মতো source folder লাগে না। next build এগুলো compile করে .next-এ ঢুকিয়ে দেয়।
ধাপ ১: server.js (cPanel-এর startup file)
cPanel-এর Node.js App চলে Passenger দিয়ে, যার একটা startup file লাগে। Project-এর root-এ রাখুন:
// server.js
const { createServer } = require("node:http");
const next = require("next");
const port = Number(process.env.PORT) || 3000;
const app = next({ dev: false, dir: __dirname });
const handle = app.getRequestHandler();
app.prepare().then(() => {
createServer((req, res) => handle(req, res)).listen(port, () => {
console.log(`Ready on port ${port}`);
});
});
⚠️
next.config-এoutput: "export"দেবেন না। ওটা static export, তখন rewrite, redirect,[id]-এর মতো dynamic route আর API proxy কাজ করে না।
ধাপ ২: cPanel-এ Node.js App বানান
cPanel → Setup Node.js App → Create Application:
| ঘর | মান |
|---|---|
| Node.js version | ২০ বা তার বেশি (CI-তেও একই major version রাখুন) |
| Application mode | Production |
| Application root | myapp-web |
| Application URL | আপনার domain |
| Application startup file | server.js |
Create চাপলে cPanel domain-এর document root-এ (সাধারণত public_html) একটা .htaccess বানায়, যার ভেতরে PassengerAppRoot … লেখা থাকে। এই file মুছবেন না। এটাই domain-এর request আপনার app-এ পাঠায়।
প্রথমবার হাতে একবার file upload করে Run NPM Install চাপুন, তারপর Start App। node_modules cPanel নিজেই manage করে (একটা symlink হিসেবে)।
ধাপ ৩: শুধু deploy-এর জন্য আলাদা FTP account
Main cPanel password CI-তে দেবেন না। cPanel → FTP Accounts:
- Login:
deploy - Directory:
myapp-web(app root)
এই account শুধু ওই folder দেখতে পায়। তাই FTP-তে তার কাছে app root-ই হলো /।
ধাপ ৪: GitHub Secrets
Repo → Settings → Secrets and variables → Actions → Secrets tab → New repository secret:
| Secret | উদাহরণ |
|---|---|
FTP_SERVER |
ftp.example.com |
FTP_USERNAME |
deploy@example.com |
FTP_PASSWORD |
deploy account-এর password |
FTP_SERVER_DIR |
/ |
FTP_SERVER_DIR-এ server-এর পুরো path (/home/user/myapp-web) দেবেন না। FTP account-এর দৃষ্টিতে path দিতে হয়।
ধাপ ৫: Workflow
.github/workflows/deploy-web.yml:
name: Deploy website (cPanel)
on:
push:
branches: [main]
workflow_dispatch:
concurrency:
group: deploy-web
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24 # cPanel app-এর সাথে মিলিয়ে
cache: npm
- run: npm ci
- name: Build
env:
# build-এর সময়ই বসে যায় — এখানে production মান দিন
API_PROXY_TARGET: https://api.example.com
run: npm run build
- name: Assemble upload folder
run: |
mkdir -p deploy-web/tmp
cp -r .next public server.js next.config.mjs package.json package-lock.json deploy-web/
rm -rf deploy-web/.next/cache
# এই file বদলালে Passenger app restart করে
date -u > deploy-web/tmp/restart.txt
- name: Upload over FTP
uses: SamKirkland/FTP-Deploy-Action@v4.3.5
with:
server: ${{ secrets.FTP_SERVER }}
username: ${{ secrets.FTP_USERNAME }}
password: ${{ secrets.FTP_PASSWORD }}
local-dir: ./deploy-web/
server-dir: ${{ secrets.FTP_SERVER_DIR }}
exclude: |
**/.git*
**/.git*/**
node_modules/**
এর কয়েকটা সুবিধা:
- FTP-Deploy-Action শুধু বদলানো file পাঠায়। Server-এ একটা state file রেখে আগের deploy-এর সাথে মিলিয়ে নেয়। তাই দ্বিতীয়বার থেকে upload খুব দ্রুত হয়, আর পুরনো
.nextchunk-গুলো নিজে থেকেই মুছে যায়। tmp/restart.txt: Passenger এই file-এর সময় বদলেছে দেখলে app restart করে। তাই cPanel-এ ঢুকে Restart চাপতে হয় না।concurrency: দুটো push পরপর এলে দুটো deploy একসাথে চলে একে অপরের file গোলমাল করে না।
যা হাতে করতে হয়
- নতুন package:
package.json-এ নতুন package যোগ হলে deploy-এর পরে cPanel-এ একবার Run NPM Install চাপুন। SSH থাকলে এটাও workflow-তে যোগ করা যায়। - Rollback: স্বয়ংক্রিয় rollback নেই। ভুল deploy হলে আগের commit-এ ফিরে Run workflow চালান।
যেসব সমস্যায় আমরা পড়েছিলাম (আর সমাধান)
১. Error: Input required and not supplied: server Workflow secret খুঁজে পায়নি। দেখুন secret Repository secrets-এ আছে কিনা (Variables বা Environment secrets-এ না), নাম হুবহু মিলছে কিনা, আর ঠিক repo-তে বানানো কিনা। Secret যোগ করার পরে run-টা Re-run করুন।
২. Site খুললে LiteSpeed-এর "404 – The resource requested could not be found" Request app পর্যন্ত পৌঁছাচ্ছে না। দুটো জিনিস দেখুন:
- Domain-এর document root-এ Passenger-এর
.htaccessআছে কিনা। মুছে গেলে Setup Node.js App-এ app খুলে Save আর Restart চাপুন, cPanel আবার বানিয়ে দেয়। - Domains page-এ document root আসলে কোন folder।
৩. Production-এ API-র ঠিকানা localhost হয়ে গেছে next.config-এর rewrite আর NEXT_PUBLIC_* মান build-এর সময় বসে যায়। আর .env.local সবসময় .env.production-এর উপরে প্রাধান্য পায়। তাই local-এ build করলে আপনার dev-এর .env.local-এর localhost-ই production-এ চলে যেতে পারে। CI-তে env: দিয়ে মান দেওয়াই সবচেয়ে নিরাপদ। চাইলে next.config-কে phase দেখে সিদ্ধান্ত নিতে দিন:
import { PHASE_DEVELOPMENT_SERVER } from "next/constants.js";
export default (phase) => {
const target =
process.env.API_PROXY_TARGET ??
(phase === PHASE_DEVELOPMENT_SERVER ? "http://localhost:5000" : "https://api.example.com");
return {
async rewrites() {
return [{ source: "/api/:path*", destination: `${target}/api/:path*` }];
},
};
};
৪. Windows-এ বানানো zip cPanel-এ extract করলে folder না হয়ে এলোমেলো file PowerShell-এর Compress-Archive zip-এর ভেতরে \ দিয়ে path লেখে, আর Linux সেটাকে file-এর নামের অংশ ধরে। হাতে zip বানাতে হলে Windows-এর tar.exe -a -c -f site.zip -C folder . ব্যবহার করুন। CI-তে এই সমস্যা হয়ই না, কারণ সেখানে zip লাগে না।
৫. stderr.log-এ getaddrinfo ENOTFOUND api.example.com App ঠিকই চলছে, শুধু API-র domain এখনো তৈরি হয়নি বা DNS ছড়ায়নি। Website দেখানোয় এর কোনো প্রভাব নেই।
শেষ কথা
| ধাপ | একবার |
|---|---|
server.js |
✅ |
| cPanel Node.js App + প্রথম NPM Install | ✅ |
| শুধু app folder-এর জন্য FTP account | ✅ |
| ৪টা GitHub secret + workflow | ✅ |
এরপর থেকে শুধু git push। কয়েক মিনিটের মধ্যে নতুন version live হয়ে যায়, cPanel-এ ঢোকারও দরকার পড়ে না।
চাইলে এটা একটা শেয়ার করার মতো web page বা doc হিসেবেও বানিয়ে দিতে পারি।
About the author
Hi, I am Full Stack Developer. I can build web application, android application, desktop application. My core programming language is JavaScript.
Comments
Join the discussionNo comments yet. Be the first to comment.

