এই পর্বে যা শিখবেন
- চারটি পরিবেশের পার্থক্য, parity আর drift কীভাবে চুপচাপ ক্ষতি করে
- Next.js 16-এর
.envফাইলের load order, আর আসল build-এNEXT_PUBLIC_কোথায় গিয়ে বসে - Build-time আর runtime config, Vite-এর
--mode, আর একটি build দিয়ে অনেক পরিবেশ চালানো @t3-oss/env-nextjsদিয়ে ভুল env-এ build থামানো, gitleaks দিয়ে git history-তে secret খোঁজা- Key rotation, secret manager, আলাদা database, seed, feature flag আর একটি চেকলিস্ট
১ · এক কোড, চার পরিবেশ
“বাজারঘর” এখন আর একজনের ল্যাপটপের প্রজেক্ট নয়। ক্রেতারা অর্ডার দেন, টাকা আসে payment gateway দিয়ে। এই অবস্থায় নতুন কোড সরাসরি ক্রেতার সামনে পাঠানো ঝুঁকির। তাই কোড কয়েকটি ধাপ পেরিয়ে যায়। প্রতিটি ধাপ একটি environment, বাংলায় পরিবেশ: কোড যেখানে চলে, তার সাথে তার নিজের database, নিজের config আর নিজের secret.
বিয়েবাড়ির বাবুর্চির কথা ভাবুন। বাড়িতে তিনি নতুন মসলা নিয়ে যত খুশি পরীক্ষা করেন, এটি local. একটি ছোট হাঁড়িতে রেঁধে দুজন আত্মীয়কে চাখতে দেন, এটি preview. অনুষ্ঠানের আগের দিন আসল চুলা, আসল ডেকচি আর আসল পরিমাণে একবার পুরো মহড়া, এটি staging. তারপর অনুষ্ঠানের দিন পাঁচশো অতিথি, এটি production. রেসিপি একটাই। বদলায় চুলা, হাঁড়ি আর কারা খাবেন। মহড়ার চুলা যত আসল চুলার মতো, অনুষ্ঠানের দিন চমক তত কম।
| বিষয় | local | preview | staging | production |
|---|---|---|---|---|
| কে ব্যবহার করে | একজন ডেভেলপার | review-এর মানুষ, একটি pull request-এর জন্য | দল, QA, শেষ যাচাই | আসল ক্রেতা |
| আয়ু | যতক্ষণ ল্যাপটপ চালু | PR খোলা থাকা পর্যন্ত | স্থায়ী | স্থায়ী |
| Database | লোকাল, seed ডেটা | ছোট, নিজস্ব বা ভাগ করা preview database | আলাদা, production-এর মতো গঠন, নকল বা মুখোশ পরানো ডেটা | আসল ডেটা |
| Payment | gateway-এর sandbox | sandbox | sandbox | live key |
| Secret কোথায় | .env.local | হোস্টিং বা CI-এর preview সেটিং | secret manager বা হোস্টিং সেটিং | secret manager বা হোস্টিং সেটিং |
NODE_ENV | development | production | production | production |
শেষ সারিটি খেয়াল করুন। Staging-এ NODE_ENV হয় production, staging নয়। NODE_ENV বলে “কোড কোন মোডে বানানো আর চালানো হচ্ছে”: optimization চালু কি না, development-এর সতর্কবার্তা আসবে কি না। আর “কোন পরিবেশে চলছি” সেটি আলাদা প্রশ্ন, যার জন্য নিজের একটি variable রাখা ভালো, যেমন APP_ENV=staging. Next.js-এর docs অনুযায়ী NODE_ENV-এর বৈধ মান শুধু production, development আর test. সত্যিকারের run-এ NODE_ENV=staging দিয়ে build চালালে Next.js 16.4.4 সাথে সাথে সতর্ক করল:
$ NODE_ENV=staging npx next build
⚠ You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against.
▲ Next.js 16.4.4 (Turbopack)
- Environments: .env.local, .env.production, .env
নিচের ছবিতে পুরো ব্যবস্থাটি দেখুন। মাঝের তীরটিই এই পর্বের মূল কথা: একটি build staging থেকে production-এ ওঠে, কিন্তু প্রতিটি পরিবেশ নিজের config আর database পায়।
চার পরিবেশ, একটি build
Same artifact promoted; each environment injects its own config and secrets
Preview environment প্রতিটি pull request-এর জন্য আলাদা একটি URL দেয়, যাতে review-এর মানুষ কোড না নামিয়েই বদলটি চোখে দেখতে পারেন। এটি কীভাবে CI-এ বানানো হয়, তা পর্ব ৮১ (CI/CD) আর পর্ব ৮২ (কোথায় চালাবেন)-এর বিষয়। এখানে আমরা শুধু দেখব, প্রতিটি পরিবেশ তার config কোথা থেকে পায়।
২ · Parity আর drift — মহড়া কেন আসলের মতো হতে হয়
“আমার মেশিনে তো চলছিল!” কথাটি এত পরিচিত কারণ পরিবেশগুলো চুপচাপ আলাদা হয়ে যায়। পরিবেশগুলো কতটা কাছাকাছি, তার নাম parity. আর সময়ের সাথে যে অমিল জমে, তার নাম drift. Twelve-Factor App-এর দশম নীতি “Dev/prod parity” তিনটি ফাঁকের কথা বলে: সময়ের ফাঁক (কোড লেখা থেকে deploy পর্যন্ত দিন বা সপ্তাহ), মানুষের ফাঁক (যিনি লেখেন আর যিনি চালান আলাদা), আর টুলের ফাঁক (ল্যাপটপে SQLite, production-এ PostgreSQL ব্যবহার)।
| কী সরে যায় | বাজারঘরে কেমন দেখায় | কীভাবে ধরবেন |
|---|---|---|
| Runtime version | ল্যাপটপে Node 24, server-এ Node 20; একটি নতুন API নেই | package.json-এর engines, .nvmrc, CI-তে একই version (পর্ব ৫) |
| Database | লোকালে SQLite, production-এ PostgreSQL; case-sensitive খোঁজা আলাদা | সব জায়গায় একই engine আর মূল version |
| Schema | staging-এ একটি migration বাকি | prisma migrate status, deploy-এর ধাপে migration (পর্ব ৪০) |
| Env variable | production-এ নতুন SMS_API_KEY বসানো হয়নি | শুরুতেই schema দিয়ে যাচাই (পর্ব ৩১) |
| হাতে করা বদল | কেউ server-এ ঢুকে একটি সেটিং বদলেছেন, কোথাও লেখা নেই | সব config কোডে বা পরিকল্পিত সেটিংয়ে; হাতে বদল নিষেধ |
| ডেটার আকার | লোকালে ২০টি পণ্য, production-এ ৮০,০০০; একটি query ধীর | staging-এ বড় নকল ডেটা |
পর্ব ৩১-এ (Log আর config) আপনি config-কে environment-এ রাখা, node --env-file আর Zod দিয়ে চালুর মুহূর্তে যাচাই দেখেছেন। সেখানে প্রশ্ন ছিল “মান আছে কি না, ঠিক ধরনের কি না”। একাধিক পরিবেশ এলে আরও দুটি প্রশ্ন যোগ হয়: এই মান কি এই পরিবেশের জন্য বৈধ? আর কোনো গোপন মান কি ভুল করে public নামে বসেছে? নিচের যাচাই এই দুটিও ধরে। এখানেও আসল environment ছোঁয়া হচ্ছে না; stagingEnv একটি সাধারণ নকল object.
// নকল config: আসল environment-এর জায়গায় সাধারণ object
const stagingEnv = {
APP_ENV: "staging",
DATABASE_URL: "postgresql://shop:x@db-prod.bazarghar.example/shop",
PAYMENT_SECRET: "short",
NEXT_PUBLIC_API_URL: "https://staging-api.bazarghar.example",
NEXT_PUBLIC_PAYMENT_KEY: "example-secret-oops",
};
function validateEnv(env) {
const errors = [];
const need = (key, ok, why) => { if (!ok(env[key])) errors.push(`${key}: ${why}`); };
need("APP_ENV", (v) => ["local", "preview", "staging", "production"].includes(v), "unknown environment");
need("DATABASE_URL", (v) => /^postgresql:\/\//.test(v ?? ""), "must be a postgresql:// URL");
need("PAYMENT_SECRET", (v) => (v ?? "").length >= 24, "missing or shorter than 24 chars");
need("NEXT_PUBLIC_API_URL", (v) => /^https?:\/\//.test(v ?? ""), "must be a URL");
// public নামের ভেতরে secret-এর মতো শব্দ থাকলে সন্দেহ
for (const key of Object.keys(env)) {
if (key.startsWith("NEXT_PUBLIC_") && /SECRET|PASSWORD|PRIVATE|_KEY$/.test(key)) {
errors.push(`${key}: looks like a secret but would be sent to the browser`);
}
}
// staging কখনো production database-এ নয়
if (env.APP_ENV !== "production" && /db-prod\./.test(env.DATABASE_URL ?? "")) {
errors.push(`DATABASE_URL: ${env.APP_ENV} must not point at the production database`);
}
return errors;
}
const errors = validateEnv(stagingEnv);
if (errors.length > 0) {
console.log(`Invalid environment (${errors.length} problems):`);
for (const e of errors) console.log(` - ${e}`);
console.log("exit code 1: build stopped, nothing deployed");
}আউটপুটে তিনটি সমস্যা একসাথে এসেছে: PAYMENT_SECRET: missing or shorter than 24 chars, NEXT_PUBLIC_PAYMENT_KEY: looks like a secret but would be sent to the browser আর DATABASE_URL: staging must not point at the production database. শেষ লাইন exit code 1: build stopped, nothing deployed. দ্বিতীয় আর তৃতীয় নিয়ম সাধারণ ধরন-যাচাইয়ে ধরা পড়ত না, কারণ মানগুলো দেখতে একদম বৈধ: একটি ঠিকঠাক URL, একটি ঠিকঠাক string. ভুলটি প্রসঙ্গে, ধরনে নয়। নামের নিয়ম আর host-এর নিয়ম দুটিই সরল অনুমান, তাই নিজের প্রকল্পে নিজের নামকরণ অনুযায়ী মিলিয়ে নিন।
৩ · Next.js 16-এ .env ফাইল আর load order
Next.js প্রজেক্টের মূল ফোল্ডারের .env* ফাইলগুলো নিজে থেকেই পড়ে process.env-এ বসায়; কোনো package লাগে না। ফাইল কয়েকটি, আর প্রতিটির কাজ আলাদা:
| ফাইল | কখন পড়া হয় | git-এ যায়? | কী রাখবেন |
|---|---|---|---|
.env | সবসময় | দলের নিয়ম অনুযায়ী; secret থাকলে কখনো নয় | নিরীহ ডিফল্ট |
.env.development | next dev | secret না থাকলে যেতে পারে | development-এর ডিফল্ট |
.env.production | next build, next start | secret না থাকলে যেতে পারে | production build-এর ডিফল্ট |
.env.local | test ছাড়া সবসময় | কখনো নয় | এই যন্ত্রের নিজের মান, লোকাল secret |
.env.test | NODE_ENV=test | যেতে পারে | test-এর স্থির মান |
একই নাম কয়েক জায়গায় থাকলে কে জেতে? Next.js-এর docs অনুযায়ী খোঁজা হয় এই ক্রমে, আর প্রথম যেখানে পাওয়া যায় সেখানেই থামে:
অর্থাৎ shell বা হোস্টিং প্ল্যাটফর্ম যে মান দেয়, সেটি সব ফাইলের ওপরে। NODE_ENV=test হলে .env.local পড়া হয় না, যাতে সবার test একই মানে চলে। এই নিয়মগুলো আমরা সত্যিকারের একটি প্রজেক্টে যাচাই করেছি। লেখার সময়ের version: Next.js 16.4.4, React 19.3.1, Node 24.15.0. তিনটি ফাইলে নকল মান:
# .env — সবার জন্য ডিফল্ট
NEXT_PUBLIC_SHOP_NAME=Bazarghar
NEXT_PUBLIC_API_URL=http://127.0.0.1:4000
DATABASE_URL=postgresql://shop:example-pass@127.0.0.1:5432/bazarghar_dev
SUPPORT_PHONE=01700-000000
# .env.production — production build-এর ডিফল্ট
NEXT_PUBLIC_API_URL=https://api.bazarghar.example
# .env.local — শুধু এই যন্ত্রে, commit হয় না
NEXT_PUBLIC_SHOP_NAME=Bazarghar-Rahim-Laptop
PAYMENT_SECRET=example-secret-local-0123456789একটি Client Component দুটি public মান আর একটি গোপন মান পড়ার চেষ্টা করে:
"use client";
export default function ShopBadge() {
return (
<p id="badge">
shop={process.env.NEXT_PUBLIC_SHOP_NAME} api={process.env.NEXT_PUBLIC_API_URL}{" "}
secret={String(process.env.PAYMENT_SECRET)}
</p>
);
}সত্যিকারের run (ল্যাবের প্রজেক্ট ~/bazarghar):
$ npx next build
▲ Next.js 16.4.4 (Turbopack)
- Environments: .env.local, .env.production, .env
✓ Compiled successfully
…
Route (app)
┌ ƒ /
└ ○ /_not-found
$ grep -l 'Rahim-Laptop' .next/static/chunks/*.js
.next/static/chunks/2wz2_e59-gcq7.js
$ grep -o 'shop=","[^"]*"," api=","[^"]*"' .next/static/chunks/2wz2_e59-gcq7.js
shop=","Bazarghar-Rahim-Laptop"," api=","https://api.bazarghar.example"
$ grep -rl 'example-secret' .next/static | wc -l
0
দ্বিতীয় লাইনেই Next.js জানিয়ে দেয় কোন কোন ফাইল পড়া হলো। তারপর grep দেখাচ্ছে, browser-এ যাওয়া ফাইলে দুটি public মান লেখা হিসেবে বসে গেছে, আর secret কোথাও নেই। নিচের ধাপে একটি মানের পুরো যাত্রা দেখুন।
- env ফাইল
.env*পড়া হলো - Load order
.env.local জেতেমান ঠিক হলো - Compiler
NEXT_PUBLIC_*লেখা বদল - Chunk
2wz2_e59…jsজমে গেল
- Environments: .env.local, .env.production, .env .env.development পড়া হয়নি: এটি build, dev নয়
NEXT_PUBLIC_SHOP_NAME .env.local → Bazarghar-Rahim-Laptop .env → Bazarghar (খোঁজাই হলো না) NEXT_PUBLIC_API_URL .env.production → https://api.bazarghar.example .env → http://127.0.0.1:4000
process.env.NEXT_PUBLIC_SHOP_NAME
↓
"Bazarghar-Rahim-Laptop"
String(t.default.env.PAYMENT_SECRET) browser-এ এই lookup দেয় undefined grep 'example-secret' .next/static → 0
NEXT_PUBLIC_SHOP_NAME=Bazarghar-Staging next start
served chunk: shop=","Bazarghar-Rahim-Laptop"
- ফাইল পড়া।
next build-এNODE_ENVহয়production. তাই Next.js পড়ল.env.local,.env.productionআর.env. Build log-এর দ্বিতীয় লাইনেই এই তালিকা আছে। - Load order. দোকানের নাম
.env.local-এ পাওয়া গেল, তাই.env-এরBazargharআর দেখাই হলো না। API-র ঠিকানা.env.local-এ নেই, তাই এলো.env.productionথেকে। Shell-এ কোনো মান থাকলে সেটি দুজনকেই হারাত। - লেখা বদল। Compiler ShopBadge.js-এ হুবহু
process.env.NEXT_PUBLIC_SHOP_NAMEলেখাটি খুঁজে পেয়ে তার জায়গায় string বসাল। এটিকে বলে inlining. এরপর browser-এর কোডে আর কোনো env খোঁজা নেই, শুধু একটি স্থির লেখা। - Secret বাদ।
PAYMENT_SECRET-এ prefix নেই, তাই compiler এটি বদলায়নি। Browser-এ এটি একটি ফাঁকাprocess.envথেকে খোঁজা, যার ফলundefined. পুরো.next/staticফোল্ডারে secret-এর লেখা শূন্য বার। - জমে গেল। মান এখন
2wz2_e59-gcq7.jsফাইলের অংশ। পরেNEXT_PUBLIC_SHOP_NAME=Bazarghar-Stagingদিয়েnext startচালালেও server একই ফাইল পাঠাল, ভেতরে এখনো ল্যাপটপের নাম। আর খেয়াল করুন: ল্যাপটপে production build বানালে.env.local-এর মানও তাতে ঢোকে।
৪ · NEXT_PUBLIC_ build-এ জমে যায় — দুটি আসল ফাঁদ
NEXT_PUBLIC_ prefix কোনো সুরক্ষা নয়, বরং উল্টো: এটি একটি ঘোষণা যে “এই মান প্রকাশ্য, browser-এ পাঠানো যাবে”। Docs-এর ভাষায়, Next.js build-এর সময় মানটি browser-এ যাওয়া JavaScript bundle-এ “inline” করে, আর build-এর পরে এই variable বদলালে অ্যাপ আর সাড়া দেয় না। Vite-এর VITE_ নিয়ে একই কথা আপনি পর্ব ৫-এ দেখেছেন। এবার দুটি ফাঁদ, দুটিই ল্যাবের সত্যিকারের run-এ ধরা পড়েছে।
ফাঁদ ১: build-এর পরে মান বদলানো
একই build-কে staging মনে করে চালানো হলো, shell-এ নতুন দুটি মান দিয়ে। Page-টি একটি Server Component, যা connection() ডেকে নিশ্চিত করে যে এটি প্রতিটি request-এ চলবে:
import { connection } from "next/server";
import ShopBadge from "./ShopBadge";
export default async function Home() {
await connection(); // request-এর সময় চলবে, build-এর সময় নয়
const secretLength = (process.env.PAYMENT_SECRET ?? "").length;
return (
<main>
<ShopBadge />
<p id="server">
server: support={process.env.SUPPORT_PHONE} shopOnServer={process.env.NEXT_PUBLIC_SHOP_NAME} secretLength={secretLength}
</p>
</main>
);
}$ NEXT_PUBLIC_SHOP_NAME=Bazarghar-Staging SUPPORT_PHONE=01800-111111 npx next start -H 127.0.0.1 -p 4178
▲ Next.js 16.4.4
✓ Ready in 125ms
$ curl -s http://127.0.0.1:4178/ | grep -o '<p id="server">.*</p>'
<p id="server">server: support=<!-- -->01800-111111<!-- --> shopOnServer=<!-- -->Bazarghar-Rahim-Laptop<!-- --> secretLength=<!-- -->31</p>
একই লাইনে দুই রকম আচরণ। SUPPORT_PHONE নতুন মান 01800-111111 দেখাল, কারণ prefix ছাড়া মান server-এ request-এর সময় পড়া হয়। কিন্তু shopOnServer এখনো Bazarghar-Rahim-Laptop, যদিও কোডটি server-এ চলছে! Build-এর পর server-এর bundle খুলে কারণ পাওয়া গেল:
"server: support=",process.env.SUPPORT_PHONE," shopOnServer=","Bazarghar-Rahim-Laptop"," secretLength=",a
অর্থাৎ NEXT_PUBLIC_ মান শুধু browser-এর কোডে নয়, server-এর কোডেও লেখা হয়ে যায়। Docs-ও বলে, build-এর সময় সব রেফারেন্স বদলে দেওয়া হয়। তাই “staging-এ শুধু env বদলে দিলেই হবে” ভাবনা এখানে ভুল প্রমাণিত হলো।
ফাঁদ ২: Client Component-এ secret পড়া
উপরের grep দেখিয়েছে, secret browser-এর JavaScript ফাইলে নেই। তাহলে কি Client Component-এ process.env.PAYMENT_SECRET লেখা নিরাপদ? একই run-এর HTML-এ দেখুন:
<p id="badge">shop=<!-- -->Bazarghar-Rahim-Laptop<!-- --> api=<!-- -->https://api.bazarghar.example<!-- --> <!-- -->secret=<!-- -->example-secret-local-0123456789</p>
Secret HTML-এ ফাঁস হয়েছে। Client Component-ও প্রথমবার server-এ render হয়, যাতে HTML তৈরি হয় (পর্ব ৫২)। সেই মুহূর্তে server-এর process.env-এ secret আছে, তাই সেটি HTML-এ লেখা হয়ে ক্রেতার কাছে গেল। পরে browser-এ hydration-এর সময় মান হয় undefined, অর্থাৎ hydration mismatch-ও হবে (পর্ব ৫৬)। Bundle পরিষ্কার দেখে নিশ্চিন্ত হবেন না। নিয়ম সরল: Client Component-এর ফাইলে কোনো secret-এর নামই লিখবেন না। Secret পড়ার কোড একটি আলাদা ফাইলে রাখুন যার শুরুতে import "server-only"; তাহলে কোনো Client Component সেটি import করলে build ভেঙে যায়।
Inlining-এর কৌশল খুব সাধারণ: source-এর লেখায় খুঁজে বদলে দেওয়া। নিচের ছোট মডেলটি Next.js-এর আসল compiler নয়, শুধু ধারণাটি দেখায়। দুটি জিনিস লক্ষ করুন: secret-কে হাত দেওয়া হয় না, আর process.env[name] ধরনের dynamic খোঁজাও বদলায় না। Docs-ও একই কথা বলে, dynamic lookup inline হয় না।
// build-এর সময়ের মান (নকল), যেন build machine-এর environment
const buildEnv = {
NEXT_PUBLIC_SHOP_NAME: "Bazarghar",
NEXT_PUBLIC_API_URL: "https://api.bazarghar.example",
PAYMENT_SECRET: "example-secret-0123456789",
};
// একটি client component-এর source লেখা
const source = [
'const api = process.env.NEXT_PUBLIC_API_URL;',
'const title = process.env.NEXT_PUBLIC_SHOP_NAME + " shop";',
'const secret = process.env.PAYMENT_SECRET;',
'const name = "NEXT_PUBLIC_API_URL"; const dyn = process.env[name];',
].join("\n");
// শিক্ষার মডেল: শুধু process.env.NEXT_PUBLIC_X আকারের লেখা বদলায়
function inlinePublicEnv(code, env) {
let replaced = 0;
const out = code.replace(/process\.env\.(NEXT_PUBLIC_[A-Z0-9_]+)/g, (match, key) => {
replaced++;
return key in env ? JSON.stringify(env[key]) : "undefined";
});
return { out, replaced };
}
const { out, replaced } = inlinePublicEnv(source, buildEnv);
console.log(out);
console.log(`--- replaced ${replaced} references`);
console.log("secret value in output?", out.includes(buildEnv.PAYMENT_SECRET));
// build-এর পরে মান বদলালেও আউটপুট বদলায় না
buildEnv.NEXT_PUBLIC_API_URL = "https://staging-api.bazarghar.example";
console.log("after changing env, bundle still says:", out.split("\n")[0]);প্রথম দুই লাইন হয়ে গেছে const api = "https://api.bazarghar.example"; আর const title = "Bazarghar" + " shop";. তৃতীয় লাইন process.env.PAYMENT_SECRET অপরিবর্তিত, আর চতুর্থ লাইনের process.env[name]-ও। তাই --- replaced 2 references আর secret value in output? false. শেষ লাইনে buildEnv বদলানোর পরেও আউটপুট বলে const api = "https://api.bazarghar.example";, কারণ বদল ঘটেছিল একবারই, build-এর মুহূর্তে। আসল ফাঁদ ১ ঠিক এটিই।
৫ · Build-time বনাম runtime config — একটি build, অনেক পরিবেশ
এখন সমস্যাটি স্পষ্ট। Staging আর production-এর API ঠিকানা আলাদা। ঠিকানাটি NEXT_PUBLIC_ হলে প্রতিটি পরিবেশের জন্য আলাদা build লাগবে। তাহলে staging-এ যা পরীক্ষা করলেন, production-এ যাচ্ছে আরেকটি build. দুটি build একই source থেকে হলেও হুবহু এক হওয়ার নিশ্চয়তা নেই: dependency-র একটি নতুন patch, আলাদা মান, আলাদা যন্ত্র। তাই অভিজ্ঞ দলের নীতি: build once, deploy many. একবার বানানো জিনিসটিকে বলে build artifact, আর পরীক্ষা পেরিয়ে সেটিকেই পরের পরিবেশে তোলাকে বলে promotion. এর তত্ত্ব আছে Cloud & DevOps কোর্সের Artifact promotion পর্বে।
ঢাকার একটি পোশাক কারখানা একই মাপের শার্ট বানায়, কিন্তু রপ্তানির দেশ অনুযায়ী লেবেল আলাদা। লেবেল সেলাই করে দিলে প্রতিটি দেশের জন্য আলাদা লট বানাতে হয়। বুদ্ধিমান কারখানা শার্ট একবার বানায়, আর ঝোলানো ট্যাগ লাগায় চালানের সময়। Build-time config হলো সেলাই করা লেবেল; runtime config হলো ঝোলানো ট্যাগ।
| Build-time | Runtime | |
|---|---|---|
| কখন মান বসে | next build বা vite build-এর সময় | process চালু বা request-এর সময় |
| উদাহরণ | NEXT_PUBLIC_*, VITE_*, prerender করা পাতা | prefix ছাড়া server-এর process.env, dynamic render |
| বদলাতে | নতুন build | restart বা নতুন deploy, build নয় |
| Promotion | পরিবেশ ভেদে আলাদা হলে সম্ভব নয় | সম্ভব |
তাহলে browser-এর কোনো মান পরিবেশ ভেদে আলাদা হলে কী করবেন? কয়েকটি পথ আছে, সহজ থেকে কঠিন:
- আপেক্ষিক ঠিকানা। Browser যদি
/api/...ডাকে, তাহলে যে domain-এ পাতা খোলা, সেখানেই যায়। Staging আর production-এ কোড এক থাকে, কোনো env লাগেই না। বাজারঘরের বেশির ভাগ ক্ষেত্রে এটিই যথেষ্ট। - Server থেকে prop. একটি Server Component
connection()-এর পরেprocess.envপড়ে মানটি Client Component-কে prop হিসেবে দেয়। Docs এই ধরনের runtime পড়াকে একটি Docker image একাধিক পরিবেশে তোলার উপায় হিসেবে দেখায়। মনে রাখুন, prop-এ যা যায় তা browser-এ যায়, তাই শুধু public মান। - Config endpoint. একটি
/api/public-configroute চালুর সময় public মানগুলো JSON হিসেবে দেয়। Docs-ও বলে, runtime মান client-এ লাগলে নিজের API বানাতে হবে।
Docker image-এর ক্ষেত্রেও একই হিসাব। নিচের Dockerfile অংশটি শুধু উদাহরণ, এখানে চালানো হয়নি; পুরো আলোচনা পর্ব ৮০-তে আর Cloud & DevOps কোর্সের Dockerfile পর্বে।
# build stage: NEXT_PUBLIC_ মান এখানে লাগলে image-এই জমে যায়
ARG NEXT_PUBLIC_SHOP_NAME=Bazarghar
RUN npm run build
# run stage: secret এখানে নয়, চালানোর সময় দেওয়া হবে
ENV NODE_ENV=production
CMD ["npm", "start"]
# চালানোর সময় (উদাহরণ):
# docker run -e APP_ENV=staging -e DATABASE_URL=... bazarghar-web:57দুটি নিয়ম এখান থেকে আসে। এক, secret কখনো ARG বা ENV দিয়ে image-এ নয়; image-এর layer আর history থেকে তা পড়া যায়। দুই, যা কিছু পরিবেশ ভেদে বদলায়, তা docker run -e বা প্ল্যাটফর্মের সেটিং দিয়ে চালানোর সময়।
1ci: npm ci && next build # build #572deploy #57 → staging (APP_ENV=staging)3smoke test: /api/health, checkout (sandbox)4promote #57 → production (APP_ENV=production)5error rate বাড়ল? rollback → #56
| staging | |
| production | |
| API URL | |
| DB host | |
| নতুন build? |
- একবার build. CI একটি পরিষ্কার যন্ত্রে
npm ciদিয়ে lockfile অনুযায়ী dependency বসিয়ে build #57 বানাল। Browser-এর API ঠিকানা আপেক্ষিক/api, তাই bundle-এ কোনো পরিবেশের নাম জমেনি। (নমুনা নম্বর; এই stepper-এর মানগুলো ধারণা বোঝানোর জন্য, কোনো আসল deploy চালানো হয়নি।) - Staging-এ. একই artifact staging-এ গেল। চালানোর সময় প্ল্যাটফর্ম দিল
APP_ENV=stagingআর staging database-এর ঠিকানা। Build হলো না। - যাচাই। Health check আর sandbox key দিয়ে একটি checkout পরীক্ষা পাস করল। এখন যে জিনিস পাস করল, ঠিক সেটিই সামনে যাবে।
- Promotion. #57 production-এ উঠল। বদলাল শুধু চালানোর সময়ের মান:
APP_ENV=production, production database, live key. Bundle হুবহু এক। - Rollback. ধরুন error বাড়ল। আগের artifact #56 এখনো আছে, তাই নতুন build ছাড়াই মিনিটের মধ্যে ফেরা যায়। Database migration থাকলে ফেরা এত সহজ নয়; সেই নিয়ম পর্ব ৪০-এ।
Vite-এ একই গল্প: import.meta.env আর --mode
React-এর SPA, যা Vite দিয়ে বানানো (পর্ব ৫ আর ৭৯), সেখানে process.env-এর জায়গায় import.meta.env, আর prefix VITE_. Vite-এর mode ধারণাটি Next.js-এর চেয়ে নমনীয়: vite build --mode staging চালালে .env.staging পড়া হয়, অথচ build production-এর মতোই optimized থাকে। সত্যিকারের run (Vite 8.3.5; .env-এ PAYMENT_SECRET আছে, prefix ছাড়া):
$ npx vite build
dist/assets/index-DndKifQv.js 0.81 kB │ gzip: 0.47 kB
…var e={api:`https://api.bazarghar.example`,mode:`production`,prod:!0,secret:void 0};
$ npx vite build --mode staging
dist/assets/index-CtX-2e7h.js 0.82 kB │ gzip: 0.48 kB
…var e={api:`https://staging-api.bazarghar.example`,mode:`staging`,prod:!0,secret:void 0};
দুটি আলাদা ফাইল নাম, কারণ ভেতরের লেখা আলাদা। অর্থাৎ Vite-এর mode দুটি আলাদা artifact বানায়, একটি promote করা যায় না। secret:void 0 দেখাচ্ছে prefix ছাড়া মান browser-এ যায়নি। Static SPA-র সব কোডই browser-এ চলে, তাই সেখানে runtime config-এর সহজ উপায় আপেক্ষিক /api অথবা চালুর সময় একটি config.json fetch করা।
৬ · Type-safe env — ভুল মানে build-ই থেমে যাক
পর্ব ৩১-এ server চালুর মুহূর্তে Zod দিয়ে যাচাই করেছিলেন। Next.js-এ আরও আগে থামা যায়: build-এর সময়। @t3-oss/env-nextjs একটি ছোট library, যা Zod-এর মতো schema নেয়, server আর client মান আলাদা করে, আর একটি typed env object দেয়। লেখার সময়ের version 0.13.11, Zod 4.6.7.
import { createEnv } from "@t3-oss/env-nextjs";
import { z } from "zod";
export const env = createEnv({
server: {
DATABASE_URL: z.url(),
PAYMENT_SECRET: z.string().min(16),
SUPPORT_PHONE: z.string().min(1),
},
client: {
NEXT_PUBLIC_SHOP_NAME: z.string().min(1),
NEXT_PUBLIC_API_URL: z.url(),
},
// library-র docs: Next.js 13.4.4 বা পরের version-এ শুধু client মানগুলো লিখতে হয়
experimental__runtimeEnv: {
NEXT_PUBLIC_SHOP_NAME: process.env.NEXT_PUBLIC_SHOP_NAME,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
emptyStringAsUndefined: true,
});import "./env.mjs"; // build শুরুর আগেই যাচাই
/** @type {import('next').NextConfig} */
const nextConfig = {};
export default nextConfig;Client মানগুলো কেন আলাদা করে process.env.NEXT_PUBLIC_... লিখে দিতে হলো? কারণ আগের অংশেই দেখেছেন, Next.js শুধু হুবহু লেখা রেফারেন্স বদলায়। emptyStringAsUndefined খালি মান KEY=-কে “নেই” ধরে, তাই খালি secret ফাঁক গলে যায় না। এবার .env.local সরিয়ে আর API ঠিকানা ভুল দিয়ে build চালানো হলো, সত্যিকারের run:
$ NEXT_PUBLIC_API_URL=not-a-url npx next build
▲ Next.js 16.4.4 (Turbopack)
- Environments: .env.production, .env
❌ Invalid environment variables: [
{
expected: 'string',
code: 'invalid_type',
path: [ 'PAYMENT_SECRET' ],
message: 'Invalid input: expected string, received undefined'
},
{
code: 'invalid_format',
format: 'url',
path: [ 'NEXT_PUBLIC_API_URL' ],
message: 'Invalid URL'
}
]
⨯ Failed to load next.config.mjs, see more info here https://nextjs.org/docs/messages/next-config-error
> Build error occurred
Error: Invalid environment variables
at <unknown> (env.mjs:4:20)
exit=1
দুটি ভুল একসাথে, আর কোনো মানের লেখা নেই, শুধু নাম আর কারণ। CI-তে এই build ব্যর্থ হলে কোনো artifact তৈরিই হয় না, তাই ভুল config নিয়ে কিছু deploy হওয়ার সুযোগ থাকে না। কয়েকটি সতর্কতা:
- Library-র docs অনুযায়ী server আর client schema একই ফাইলে রাখলে server variable-এর নামগুলো client bundle-এ যেতে পারে। নাম প্রকাশ পাওয়া মানের মতো বিপদ নয়, তবে চাইলে দুটি ফাইলে ভাগ করুন।
- এই যাচাই ফাঁদ ২ ঠেকায় না: Client Component-এ সরাসরি
process.env.PAYMENT_SECRETলিখলে SSR-এ তা এখনো পড়া যায়।server-onlyআর code review দরকার। - CI-তে lint বা Docker build-এর মতো কাজে, যেখানে আসল secret নেই, অনেকে
SKIP_ENV_VALIDATION-এর মতো একটি সুইচ রাখেন। সেটি production deploy-এর পথে কখনো চালু রাখবেন না।
৭ · Secret git-এ নয় — আর গেলে খুঁজে বের করা
Secret মানে এমন মান, যা পেলে অন্য কেউ আপনার হয়ে কাজ করতে পারে: database password, payment gateway-এর key, JWT সই করার চাবি, SMS gateway-এর token. প্রথম বেড়া .gitignore, আর নামের তালিকার জন্য একটি .env.example:
.env
.env.*
!.env.example
# Next.js-এর ডিফল্ট টেমপ্লেটও .env ফাইলগুলো ignore করেকিন্তু .gitignore শুধু ভবিষ্যৎ আটকায়। একটি ভুল git add . আগেই হয়ে থাকলে? ল্যাবে একটি আলাদা scratch repository-তে ঠিক এই ভুলটি ইচ্ছা করে করা হলো (নকল key, লেখকের নাম Rahim). প্রথম commit-এ .env চলে গেল, দ্বিতীয় commit-এ সেটি সরিয়ে .gitignore যোগ হলো:
$ git log --oneline
8246005 chore: remove .env, add .gitignore
189a5e1 feat: add server and config
$ git ls-files
.env.example
.gitignore
server.js
এখন repository দেখতে পরিষ্কার। পরীক্ষার জন্য gitleaks নেওয়া হলো, একটি open-source secret scanner. Portable binary নামিয়ে প্রকাশিত checksum মিলিয়ে নেওয়া হলো, যাতে পথে কেউ বদলে না দেয়:
$ grep windows_x64 gitleaks_8.30.1_checksums.txt
d29144deff3a68aa93ced33dddf84b7fdc26070add4aa0f4513094c8332afc4e gitleaks_8.30.1_windows_x64.zip
$ sha256sum gitleaks_8.30.1_windows_x64.zip
d29144deff3a68aa93ced33dddf84b7fdc26070add4aa0f4513094c8332afc4e *gitleaks_8.30.1_windows_x64.zip
$ gitleaks version
8.30.1
দুটি scan, সত্যিকারের run. প্রথমটি শুধু এখনকার ফাইল দেখে, দ্বিতীয়টি পুরো git history:
$ gitleaks dir . --no-banner
5:17PM INF scanned ~82 bytes (82 bytes) in 999µs
5:17PM INF no leaks found
exit=0
$ gitleaks git . --no-banner --redact -v
Finding: PAYMENT_SECRET=REDACTED
Secret: REDACTED
RuleID: generic-api-key
Entropy: 4.923182
File: .env
Line: 2
Commit: 189a5e1ed8c3c0dfcac7422b7afabd6553ad5512
Author: Rahim
Email: rahim@example.com
Date: 2026-11-02T04:00:00Z
5:17PM INF 2 commits scanned.
5:17PM WRN leaks found: 1
exit=1
এখনকার ফাইলে কিছু নেই, কিন্তু history-র commit 189a5e1-এ key পড়ে আছে। Repository কেউ clone করলে এই commit-ও পায়। --redact রিপোর্টে মানটি ঢেকে দেয়, যাতে scan-এর log নিজেই ফাঁসের উৎস না হয়। Exit code 1 হওয়ায় CI এই ধাপে থেমে যেতে পারে। আর একই টুল commit-এর আগেও চলে, একটি pre-commit hook হিসেবে। Stage করা একটি নতুন ফাইলে নকল key রেখে চালানো হলো:
$ gitleaks git --pre-commit --staged --redact -v .
Finding: key = "REDACTED"
RuleID: generic-api-key
File: pay.js
Line: 1
…
5:18PM WRN leaks found: 1
Scanner ভেতরে কী করে? মূলত দুটি কাজ। এক, পরিচিত আকারের নিয়ম (regex), যেমন একটি নির্দিষ্ট প্রতিষ্ঠানের key-এর prefix. দুই, “key”, “secret”, “token”-এর মতো শব্দের পাশে বসা মানের এলোমেলোভাব মাপা, যাকে বলে Shannon entropy. “aaaa…”-এর মতো মানের entropy শূন্য, এলোমেলো key-এর অনেক বেশি। নিচের মডেলটি gitleaks নয়, তার ধারণার একটি ছোট রূপ:
// নকল লাইন: কোনোটিই আসল key নয়
const lines = [
'const port = 4000;',
'PAYMENT_SECRET=bzg_live_k7Qp2Xv9LmR4tW8nZc3YhJ6d',
'const apiKey = "aaaaaaaaaaaaaaaaaaaaaaaa";',
'DATABASE_URL=postgresql://shop:Xk29pLq7Vd@db.bazarghar.example/shop',
'const token = process.env.API_TOKEN;',
];
// Shannon entropy: প্রতি অক্ষরে কত bit "অনিশ্চয়তা"
function entropy(s) {
const count = {};
for (const ch of s) count[ch] = (count[ch] || 0) + 1;
return Object.values(count)
.reduce((h, n) => h - (n / s.length) * Math.log2(n / s.length), 0);
}
const rules = [
{ id: "bazarghar-live-key", re: /bzg_live_[A-Za-z0-9]{24}/ },
{ id: "url-password", re: /:\/\/[^:\s]+:([^@\s]+)@/, group: 1 },
{ id: "generic-assignment", re: /(?:key|secret|token)\w*\s*[=:]\s*"?([A-Za-z0-9_\-]{16,})"?/i, group: 1, minEntropy: 3.5 },
];
lines.forEach((line, i) => {
for (const rule of rules) {
const m = line.match(rule.re);
if (!m) continue;
const secret = m[rule.group || 0];
const h = entropy(secret);
if (rule.minEntropy && h < rule.minEntropy) {
console.log(`line ${i + 1}: ${rule.id} skipped (entropy ${h.toFixed(2)})`);
continue;
}
console.log(`line ${i + 1}: ${rule.id} entropy=${h.toFixed(2)} secret=${secret.slice(0, 4)}…`);
break;
}
});আউটপুট তিন লাইন: line 2: bazarghar-live-key entropy=4.92 secret=bzg_…, line 3: generic-assignment skipped (entropy 0.00) আর line 4: url-password entropy=3.32 secret=Xk29…. লাইন ২-এর 4.92 gitleaks-এর রিপোর্টের Entropy: 4.923182-এর সাথে মিলে যায়, কারণ একই ৩৩ অক্ষরের লেখা মাপা হয়েছে। লাইন ৩ দেখতে key-এর মতো, কিন্তু entropy 0, তাই বাদ: এভাবেই scanner ভুল সতর্কবার্তা কমায়। লাইন ৫-এ কিছু আসেনি, কারণ process.env থেকে পড়া কোনো secret নয়, বরং সঠিক অভ্যাস। আর আউটপুটে secret-এর প্রথম ৪টি অক্ষরের বেশি ছাপা হয়নি।
Entropy মানে key-এর শক্তি নয়। উপরের সংখ্যা একটি নমুনার প্রতি অক্ষরের হিসাব। Key কতটা অনুমান-অযোগ্য, তা নির্ভর করে কীভাবে বানানো হয়েছে তার ওপর। ৬২টি অক্ষর (A–Z, a–z, 0–9) থেকে নিরপেক্ষভাবে বাছাই করা ২৪ অক্ষরের key-এর শক্তি 24 × log2(62) ≈ 24 × 5.954 ≈ 142.9 bit. আর crypto.randomBytes(32) দিলে ঠিক 256 bit. তাই secret বানান CSPRNG দিয়ে, যেমন openssl rand -base64 32, কখনো নিজের মাথা থেকে নয়।
৮ · ফাঁস হলে rotation, আর secret manager
History-তে secret পাওয়া গেল। প্রথম প্রতিক্রিয়া প্রায়ই “history মুছে দিই”। কিন্তু ততক্ষণে repository কেউ clone করে থাকতে পারেন, CI-এর cache-এ থাকতে পারে, কোনো fork-এ থাকতে পারে। তাই নিয়ম: ফাঁস হওয়া secret-কে মৃত ধরে নিন। আসল প্রতিকার rotation: নতুন key বানানো, সব জায়গায় বসানো, তারপর পুরোনোটি বাতিল। History পরিষ্কার করা (যেমন git filter-repo দিয়ে) ঐচ্ছিক, পরের ধাপ। Git-এর দিক বিস্তারিত আছে Software Engineering কোর্সের Git security পর্বে।
- খোঁজা
gitleaks gitcommit 189a5e1 - নতুন key
gateway dashboardদুটিই বৈধ - Deploy
secret storeনতুন মান - বাতিল
পুরোনো keyতারপর তদন্ত
RuleID: generic-api-key
File: .env Line: 2
Commit: 189a5e1
WRN leaks found: 1
bzg_live_k7Qp… active (leaked)
bzg_live_Nw3r… active (new)
PAYMENT_SECRET = bzg_live_Nw3r…
deploy #58 (config only) → healthy
bzg_live_k7Qp… revoked
bzg_live_Nw3r… active
gateway log: পুরোনো key-এর ব্যবহার খোঁজা
$ gitleaks git --pre-commit --staged -v .
File: pay.js
WRN leaks found: 1 → commit বাতিল
- খোঁজা। gitleaks history-তে commit
189a5e1-এর.envফাইলের দ্বিতীয় লাইনে key পেল। এখনকার ফাইলে না থাকলেও এটি ফাঁস। এই ধাপের আউটপুট ল্যাবের আসল run থেকে। - নতুন key. Payment gateway-এর dashboard-এ নতুন key বানানো হলো। কিছুক্ষণ দুটি key-ই বৈধ, যাতে চলমান অ্যাপ না ভাঙে। Gateway এমন সুযোগ দেয় কি না, তার docs দেখে নিন। (ধাপ ২ থেকে ৪ নমুনা; কোনো আসল gateway ব্যবহার হয়নি।)
- নতুন মান বসানো। Secret store বা হোস্টিং সেটিংয়ে মান বদলে নতুন deploy, কোড না বদলেই। Runtime-এ পড়া মান হওয়ায় নতুন build লাগেনি। প্রতিটি পরিবেশে key আলাদা থাকায় শুধু production-এর key বদলাতে হলো।
- বাতিল আর তদন্ত। নতুন key কাজ করছে নিশ্চিত হয়ে পুরোনোটি বাতিল। তারপর gateway-এর log দেখা: ফাঁসের পর পুরোনো key দিয়ে অচেনা কোনো লেনদেন হয়েছিল কি না।
- আবার যেন না হয়। Pre-commit hook আর CI-তে gitleaks বসানো হলো। ল্যাবের আসল run-এ একটি নকল key-সহ stage করা
pay.jsধরা পড়ল, exit code 1, তাই commit হতো না।
Secret manager আর KMS — এক লাইনে
ছোট প্রকল্পে হোস্টিং প্ল্যাটফর্মের env সেটিংই প্রায়ই যথেষ্ট। দল বড় হলে আর secret বাড়লে আসে secret manager: কেন্দ্রীয় সুরক্ষিত ভাণ্ডার, কে কখন পড়ল তার হিসাব, স্বয়ংক্রিয় rotation. উদাহরণ হিসেবে HashiCorp Vault, আর বড় cloud প্রতিষ্ঠানগুলোর নিজস্ব secret manager সেবা আছে। তার পেছনে থাকে KMS, যে চাবি কখনো নিজের ঘর ছাড়ে না। এই তত্ত্ব, envelope encryption আর downtime ছাড়া rotation পুরোটাই আছে Cloud & DevOps কোর্সের Secrets ও KMS পর্বে; এখানে আবার শেখানো হলো না।
Encrypted ফাইল git-এ রাখার দুটি পরিচিত পথও আছে, যা নিয়ে সাবধানে কথা বলা ভালো:
- SOPS: শুরু Mozilla-তে, এখন getsops নামে community প্রকল্প। YAML বা JSON ফাইলের key-গুলো খোলা রেখে শুধু মানগুলো encrypt করে, চাবি আসে KMS, age বা PGP থেকে। ফলে diff পড়া যায়, মান পড়া যায় না। শিখতে একটু সময় লাগে।
- dotenv-vault:
dotenv-এর নির্মাতাদের টুল, encrypted.envভাগ করার জন্য। লেখার সময় npm-এ এর শেষ আপডেট ২০২৫ সালের মাঝামাঝি, আর একই নির্মাতারা এখনdotenvxনামে আরেকটি টুল সামনে আনছেন। ব্যবহারের আগে বর্তমান অবস্থা আর docs দেখে নিন।
যে পথই নিন, চাবি (decryption key) আর encrypted ফাইল একই জায়গায় রাখলে কোনো লাভ নেই।
৯ · আলাদা database, test data, feature flag আর একটি চেকলিস্ট
Staging কখনো production database-এ নয়
প্রতিটি পরিবেশের নিজের database, নিজের user আর নিজের password. কারণ সরল: staging-এ ভাঙা কোড চলে, পরীক্ষার script চলে, কেউ ভুল করে prisma migrate reset চালাতে পারেন। সেই কাজ যদি production-এর connection string দিয়ে হয়, ক্রেতার অর্ডার মুছে যায়। ২০১৭ সালের জানুয়ারিতে GitLab-এর প্রকাশিত postmortem অনুযায়ী, একজন প্রকৌশলী গভীর রাতে কাজ করার সময় ভুল database server-এ ডেটা মোছার কমান্ড চালান, আর কয়েক ঘণ্টার ডেটা হারায়; ঘটনাটি দেখায় যে “কোন যন্ত্রে আছি” প্রশ্নের ভুল কত দামি। প্রতিরোধের স্তর:
- Staging-এর credential দিয়ে production-এ ঢোকাই সম্ভব নয়, network আর user দুই দিক থেকে। এই নীতির নাম least privilege.
- অ্যাপ চালুর যাচাইয়ে host-এর নিয়ম, যেমন আগের runnable উদাহরণের
db-prodপরীক্ষা। - Destructive script-এর শুরুতে
APP_ENV === "production"হলে থেমে যাওয়া। - Terminal-এর prompt-এ বা database টুলের রঙে পরিবেশের নাম, যাতে চোখেও পড়ে।
Staging-এ বাস্তবের মতো ডেটা দরকার, কিন্তু আসল ক্রেতার ফোন নম্বর আর ঠিকানা নয়। Seed script দিয়ে নকল ডেটা (পর্ব ৪০), আর test-এ factory ও builder (পর্ব ৭৭)। Production-এর কপি লাগলে আগে মুখোশ পরানো: নাম, ফোন, ঠিকানা বদলে। Test database তৃতীয় একটি আলাদা জগৎ, যা প্রতিবার খালি থেকে শুরু হয় (পর্ব ৭৫)।
Feature flag — config দিয়ে feature চালু-বন্ধ
নতুন checkout পাতা তৈরি। কিন্তু একবারে সব ক্রেতাকে দেখানো ঝুঁকির। Feature flag কোড deploy আর feature চালুকে আলাদা করে: কোড production-এ আছে, কিন্তু একটি config মান ঠিক করে কে দেখবেন। এতে তিনটি সুবিধা: percentage rollout (প্রথমে ১০%), QA দলের জন্য আগেভাগে চালু, আর বিপদে এক সুইচে বন্ধ, যাকে বলে kill switch. মূল কৌশল একটি stable hash: একই ব্যবহারকারী প্রতিবার একই দলে পড়েন, তাই পাতা রিফ্রেশ করলে checkout বদলে যায় না।
// FNV-1a: একই লেখা দিলে সবসময় একই সংখ্যা
function fnv1a(text) {
let h = 0x811c9dc5;
for (let i = 0; i < text.length; i++) {
h ^= text.charCodeAt(i);
h = Math.imul(h, 0x01000193) >>> 0;
}
return h;
}
// flag-এর নামও hash-এ যায়, তাই প্রতিটি flag-এ আলাদা দল পড়ে
function bucket(flagName, userId) {
return fnv1a(`${flagName}:${userId}`) % 100; // 0..99
}
function isEnabled(flag, user) {
if (!flag.enabled) return false; // kill switch
if (flag.allowUsers.includes(user.id)) return true; // QA দল সবসময়
return bucket(flag.name, user.id) < flag.rolloutPercent;
}
const flag = { name: "new-checkout", enabled: true, rolloutPercent: 10, allowUsers: ["qa-1"] };
let on = 0;
for (let i = 1; i <= 10000; i++) if (isEnabled(flag, { id: `user-${i}` })) on++;
console.log(`10% rollout: ${on} of 10000 users see new checkout`);
const u = { id: "user-42" };
console.log("user-42 bucket:", bucket(flag.name, u.id), "same again:", bucket(flag.name, u.id));
console.log("qa-1 enabled:", isEnabled(flag, { id: "qa-1" }));
flag.rolloutPercent = 50;
let stillOn = 0, before = 0;
for (let i = 1; i <= 10000; i++) {
const b = bucket(flag.name, `user-${i}`);
if (b < 10) { before++; if (b < 50) stillOn++; }
}
console.log(`raise to 50%: ${stillOn} of the first ${before} keep it on`);
flag.enabled = false;
console.log("kill switch, qa-1 enabled:", isEnabled(flag, { id: "qa-1" }));10% rollout: 1002 of 10000 users see new checkout: প্রত্যাশা ১,০০০, পাওয়া গেল ১,০০২, hash-এর স্বাভাবিক সামান্য বিচ্যুতি। user-42 bucket: 26 same again: 26: একই ব্যবহারকারী, একই bucket, কোনো এলোমেলো সংখ্যা বা সময় নেই। qa-1 enabled: true, কারণ তালিকায় নাম আছে। raise to 50%: 1002 of the first 1002 keep it on: ১০% থেকে ৫০%-এ তুললে আগের সবাই থেকে যান, কারণ bucket 10-এর কম হলে 50-এরও কম; কারও অভিজ্ঞতা হঠাৎ উল্টে যায় না। শেষে kill switch, qa-1 enabled: false: সুইচ বন্ধ হলে সবার জন্য বন্ধ, QA-রও।
এই ছোট কোডের মান কোথা থেকে আসবে? সবচেয়ে সরল পথ env variable, যেমন FLAG_NEW_CHECKOUT_PERCENT=10; তবে বদলাতে restart লাগে। তার পরের ধাপ database-এর একটি table, যা admin পাতা থেকে বদলানো যায়। আরও বড় হলে hosted সেবা, যেমন LaunchDarkly, বা নিজে চালানো যায় এমন open-source Unleash, GrowthBook, Flagsmith; এগুলো শুধু উদাহরণ, কোনোটির সুপারিশ নয়। ভেন্ডর বদলানো সহজ রাখতে CNCF-এর OpenFeature নামে একটি সাধারণ API মানদণ্ডও আছে। দুটি সাবধানতা: পুরোনো flag নিয়মিত মুছে ফেলুন, আর পুরোনো flag-এর নাম নতুন অর্থে কখনো ব্যবহার করবেন না। পর্ব ৩১-এ বলা Knight Capital ঘটনায় পুরোনো একটি flag নতুন অর্থে ব্যবহারও একটি ভূমিকা রেখেছিল বলে প্রকাশিত প্রতিবেদনে উল্লেখ আছে।
প্রতিটি pull request-এর জন্য preview environment: আলাদা URL, sandbox key, ছোট নিজস্ব বা ভাগ করা database. এখানে একটি বড় সতর্কতা: বাইরের কারও fork থেকে আসা pull request-এ production বা staging-এর secret কখনো দেবেন না, কারণ সেই কোড তিনি নিজেই লিখেছেন। কীভাবে এটি CI-তে সাজাবেন, পর্ব ৮১-তে; কোন প্ল্যাটফর্ম কী দেয়, পর্ব ৮২-তে।
চেকলিস্ট — নতুন পরিবেশ বা deploy-এর আগে
১০ · AI-এর যুগে environment আর secret
Config আর secret-এর ভুল চলার সময় চোখে পড়ে না। অ্যাপ চলে, পাতা খোলে, আর secret চুপচাপ browser-এ বা git-এ পৌঁছে যায়। AI এই জায়গায় কয়েকটি পরিচিত ভুল করে:
| AI-এর ভুল | কীভাবে ধরবেন |
|---|---|
“কাজ করছে না” শুনে secret-এর নামে NEXT_PUBLIC_ যোগ করা | Build-এর পর .next/static-এ মানটি grep করুন |
Client Component-এ process.env.SECRET | Build-এর পর HTML-এ secret খুঁজুন; server-only বসান |
পুরোনো API, যেমন next.config.js-এর publicRuntimeConfig বা pages router-এর উদাহরণ | আপনার Next.js version-এর docs মিলিয়ে নিন |
process.env.KEY || "sk_test_123" ধরনের ডিফল্ট | মানটি মুছে চালান; build বা server থামে কি না |
| উদাহরণে আসল দেখতে key, বা আপনার পেস্ট করা key কোডে বসিয়ে দেওয়া | Diff-এ gitleaks চালান; prompt-এ কখনো আসল secret নয় |
| প্রতিটি পরিবেশের জন্য আলাদা build-এর পরামর্শ | জিজ্ঞেস করুন, কোন মান আসলে build-time হতে হবে |
অপ্রয়োজনীয় package, যেমন Next.js-এ .env পড়তে dotenv | Next.js নিজেই পড়ে; package.json-এর বদল পড়ুন |
AI-কে কাজ দিন শর্তসহ, আর যাচাইয়ের ধাপও চেয়ে নিন। আপনার .env ফাইলের লেখা কখনো prompt-এ দেবেন না; শুধু নামগুলো দিন।
AI-এর লেখা কোড মেলানোর চেকলিস্ট
আরও গভীরে: Secret ফাঁস নিয়ে বড় পরিসরের গবেষণা হয়েছে। Meli, McNiece আর Reaves-এর NDSS ২০১৯-এর গবেষণাপত্র “How Bad Can It Git?” GitHub-এর public repository-তে এক লাখেরও বেশি repository-তে ফাঁস হওয়া secret পেয়েছিল বলে জানায়। এই কারণেই এখন অনেক code hosting প্ল্যাটফর্ম push-এর সময়ই পরিচিত key আটকায়, যার নাম push protection. আর নীতির দিক থেকে NIST SP 800-57 চাবির পুরো জীবনচক্র, তৈরি থেকে বাতিল পর্যন্ত, নিয়ে নির্দেশনা দেয়।
পরীক্ষা ও ইন্টারভিউতে:
NEXT_PUBLIC_মান কখন ঠিক হয়? →next build-এর সময়, bundle-এ লেখা হয়ে যায়; পরে env বদলালে প্রভাব নেই।- Staging-এ
NODE_ENVকী হবে? →production; পরিবেশের নামের জন্য আলাদা variable, যেমনAPP_ENV. - Secret commit হয়ে গেলে প্রথম কাজ? → Key rotate বা revoke; history পরিষ্কার পরের ধাপ।
- Build once, deploy many কেন? → যা পরীক্ষা হলো ঠিক সেটিই production-এ যায়; rollback দ্রুত।
- Percentage rollout-এ stable hash কেন? → একই ব্যবহারকারী প্রতিবার একই দলে থাকেন, আর শতাংশ বাড়ালে আগের দল বদলায় না।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন, তারপর “→ উত্তর” চাপুন।
প্র ০১Staging রাখা, চালানো আর production-এর মতো রাখা খরচের। অনেক দল staging বাদ দিয়ে সরাসরি production-এ feature flag দিয়ে পরীক্ষা করে। কোনটি ঠিক?
এটি আসলেই একটি চলমান বিতর্ক, আর দুই দিকেই অভিজ্ঞ মানুষ আছেন। Staging-এর পক্ষের যুক্তি পরিচিত: migration, config আর বাইরের সংযোগ একবার আসলের মতো জায়গায় চালিয়ে দেখা যায়, ক্রেতার ক্ষতি ছাড়াই। বিশেষ করে database migration আর payment-এর মতো জায়গায়, যেখানে ভুল ফেরানো কঠিন, একটি মহড়া অনেক দুশ্চিন্তা কমায়।
বিপক্ষের যুক্তিও শক্ত। Staging প্রায় কখনোই পুরোপুরি production-এর মতো হয় না: ডেটা কম, traffic নেই, বাইরের সেবা sandbox. ফলে “staging-এ চলেছে” কথাটি একটি মিথ্যা নিশ্চয়তা দিতে পারে। আর staging নিজেই drift করে, কারণ কেউ সেটির দেখাশোনার দায়িত্ব নেন না। এই কারণে কিছু দল “testing in production” বলে একটি পথ নেয়: ছোট বদল, feature flag-এর পেছনে deploy, প্রথমে ১% ব্যবহারকারী, ভালো monitoring আর দ্রুত rollback. Charity Majors-এর মতো observability-র প্রবক্তারা প্রকাশ্যে এই দৃষ্টিভঙ্গির পক্ষে লিখেছেন।
বাস্তবে উত্তর নির্ভর করে তিনটি জিনিসের ওপর। এক, ভুল ফেরানো কতটা সহজ: একটি বোতামের রং ফেরানো সহজ, ভুল টাকা কাটা নয়। দুই, monitoring কতটা ভালো: খারাপ কিছু হলে কত সেকেন্ডে জানবেন? তিন, দল কত বড়: একজনের প্রকল্পে staging-এর রক্ষণাবেক্ষণ খরচ বেশি মনে হতে পারে।
বাজারঘরের মতো একটি দোকানে মাঝামাঝি পথ যুক্তিসংগত: একটি ছোট staging, যেখানে migration আর payment-এর sandbox প্রবাহ যাচাই হয়, আর UI-এর বদলগুলো flag-এর পেছনে ধাপে ধাপে production-এ। প্রতিটি PR-এর preview environment staging-এর অনেক কাজ আগেই সেরে দেয়।
মূল উপলব্ধি: Staging একটি উপায়, লক্ষ্য নয়। লক্ষ্য হলো ঝুঁকির বদল কম খরচে আর ছোট পরিসরে যাচাই করা; সেটি মহড়ায় হোক বা সাবধানে আসল মঞ্চে।
প্র ০২Next.js কেন NEXT_PUBLIC_ মান runtime-এ না পড়ে build-এ লিখে দেয়? এটি কি একটি ভুল নকশা?
এর পেছনে browser-এর একটি সরল বাস্তবতা আছে: browser-এ কোনো process.env নেই। Node.js-এ process.env হলো operating system থেকে পাওয়া মান। Browser আলাদা যন্ত্রে, আলাদা পরিবেশে চলে, সেখানে আপনার server-এর environment পৌঁছানোর কোনো স্বাভাবিক পথ নেই। তাই কোনো মান browser-এ নিতে হলে হয় সেটি JavaScript ফাইলের ভেতরে লেখা থাকবে, নয়তো browser চলার সময় কোথাও থেকে আনবে।
লিখে দেওয়ার পথের সুবিধা অনেক। কোনো বাড়তি network request নেই। Minifier স্থির মান দেখে অপ্রয়োজনীয় শাখা মুছে ফেলতে পারে: if (process.env.NEXT_PUBLIC_FLAG === "on") build-এ if ("off" === "on") হয়ে পুরো block বাদ পড়ে, যাকে dead-code elimination বলে। CDN থেকে static ফাইল সরাসরি পরিবেশন করা যায়, কারণ ফাইলটি সম্পূর্ণ। Webpack-এর DefinePlugin বহু বছর ধরে এভাবেই কাজ করেছে, আর Vite-এর define-ও একই ধারণা। Next.js ২০২০ সালের v9.4-এ .env আর NEXT_PUBLIC_ সমর্থন এনেছিল, docs-এর version তালিকা অনুযায়ী।
দামটি হলো promotion-এর সমস্যা, যা আমরা দেখেছি। Next.js একসময় publicRuntimeConfig নামে runtime পথও দিয়েছিল, কিন্তু সেটি pages router-এর যুগের, App Router-এ সমর্থিত ছিল না, আর Next.js 16-এর upgrade guide অনুযায়ী এখন পুরোপুরি সরিয়ে দেওয়া হয়েছে। এখন docs-এর সুপারিশ: runtime মান লাগলে Server Component বা নিজের API দিয়ে দিন।
তাই এটি ভুল নকশা নয়, একটি সচেতন বিনিময়। বেশির ভাগ public মান, যেমন analytics id বা দোকানের নাম, সত্যিই পরিবেশ ভেদে বদলায় না, অথবা বদলালেও আলাদা build মেনে নেওয়া যায়। ভুল হয় তখনই, যখন ডেভেলপার জানেন না যে মানটি জমে গেছে।
মূল উপলব্ধি: প্রতিটি config মানের জন্য জিজ্ঞেস করুন: কে পড়বে, আর কখন? Browser পড়লে সেটি প্রকাশ্য; build-এ জমলে বদলাতে নতুন build.
প্র ০৩একটি private repository-তে secret রাখলে সমস্যা কী? শুধু তো দলের মানুষই দেখতে পান।
“Private” শব্দটি যতটা নিরাপত্তা দেয় বলে মনে হয়, বাস্তবে ততটা দেয় না। একটি repository-র কপি থাকে প্রতিটি ডেভেলপারের ল্যাপটপে, CI-এর runner-এ, backup-এ, কখনো কোনো বাইরের ঠিকাদারের যন্ত্রে। এর যেকোনো একটি হাতছাড়া হলে সব secret হাতছাড়া। আর repository কখনো ভুল করে public হয়ে যায়, বা কোনো কর্মী চলে যাওয়ার পরেও তাঁর ল্যাপটপে কপি থাকে।
প্রকাশিত ঘটনা এই আশঙ্কাকে বাস্তব দেখায়। ২০১৬ সালের Uber ঘটনায়, পরে প্রকাশিত মার্কিন নিয়ন্ত্রক সংস্থার বিবরণ অনুযায়ী, আক্রমণকারীরা একটি private code repository থেকে cloud-এর access key পেয়ে বিপুল ব্যবহারকারীর তথ্যে পৌঁছেছিল। ২০২২ সালে Toyota জানায় যে তাদের একটি সেবার source code-এর অংশ প্রায় পাঁচ বছর ধরে ভুল করে public ছিল, আর তাতে একটি data server-এর access key ছিল। দুটি ক্ষেত্রেই key কোডের সাথে থাকাই মূল দুর্বলতা।
আরেকটি দিক হলো blast radius, অর্থাৎ ক্ষতি কতদূর ছড়ায়। কোডে secret থাকলে কোড পড়ার অধিকার আর secret ব্যবহারের অধিকার এক হয়ে যায়। একজন intern-এর frontend-এর কাজ করতে production database-এর password জানার দরকার নেই। Secret আলাদা রাখলে প্রত্যেকে শুধু প্রয়োজনীয়টুকু পান, আর কে কখন পড়লেন তার হিসাবও থাকে।
শেষে rotation. কোডে থাকা secret বদলাতে commit, review, build আর deploy লাগে; history-তে পুরোনোটি থেকেই যায়। Runtime-এ পড়া secret বদলাতে শুধু store-এর মান বদল আর restart.
মূল উপলব্ধি: Repository-র অধিকার আর secret-এর অধিকার দুটি আলাদা প্রশ্ন। এদের এক করে ফেললে একটি ছোট ফাঁস সবকিছু খুলে দেয়।
প্র ০৪gitleaks-এর মতো scanner কি সব secret ধরতে পারে? না পারলে কেন রাখব?
না, সব পারে না, আর এটি জেনে রাখা জরুরি। Scanner দুই রকম ভুল করে। False negative: আসল secret ধরতে না পারা। একটি ছোট, কম এলোমেলো password, যেমন Dhaka2024, কোনো নির্দিষ্ট আকারে মেলে না, আর entropy-ও কম। একটি key যদি দুই ভাগে ভেঙে দুই লাইনে লেখা হয়, বা base64 করে রাখা হয়, regex হয়তো চিনবে না। False positive: নিরীহ জিনিসকে secret বলা, যেমন একটি test fixture-এর এলোমেলো id বা একটি hash. খুব বেশি false positive হলে দল সতর্কবার্তা উপেক্ষা করতে শেখে, যা আরও বিপজ্জনক।
এই বিনিময় নিয়ন্ত্রণের হাতল হলো entropy-র সীমা আর নিয়মের তালিকা। সীমা কমালে বেশি ধরা পড়ে, কিন্তু শব্দদূষণ বাড়ে। এ কারণে gitleaks-এ allowlist আর নিজস্ব নিয়ম যোগ করার ব্যবস্থা আছে: আপনার প্রতিষ্ঠানের key-এর prefix জানা থাকলে একটি নির্দিষ্ট regex অনেক নির্ভরযোগ্য। এ কারণেই অনেক সেবা এখন key-এর শুরুতে স্পষ্ট prefix রাখে, যাতে scanner সহজে আর নিশ্চিতভাবে চেনে।
তাহলে কেন রাখব? কারণ নিরাপত্তা একটি দেয়াল নয়, কয়েকটি স্তর: .gitignore, pre-commit scan, CI scan, hosting প্ল্যাটফর্মের push protection, আর ফাঁস হলে দ্রুত rotation. প্রতিটি স্তরে ফুটো আছে, কিন্তু ফুটোগুলো একই জায়গায় নয়। এই ধারণাটি নিরাপত্তায় প্রায়ই সুইস চিজের উপমায় বলা হয়। একটি scanner সাধারণ ভুলের বড় অংশ ধরলেও সেটি অনেক দামি, কারণ বেশির ভাগ ফাঁস হয় অসাবধানতায়, জটিল কৌশলে নয়।
মূল উপলব্ধি: Scanner একটি জাল, দেয়াল নয়। এটি সাধারণ ভুল ধরে; বাকিটা ঢাকতে secret কোডে না রাখার অভ্যাস আর rotation-এর প্রস্তুতি লাগে।
প্র ০৫Feature flag এত সুবিধার হলে প্রতিটি নতুন কাজ flag-এর পেছনে রাখলেই তো হয়। এর খরচ কী?
Feature flag-এর সুবিধা বাস্তব: deploy আর release আলাদা, ধাপে ধাপে চালু, এক সুইচে বন্ধ। ২০১০-এর দশকে বড় web প্রতিষ্ঠানগুলোর trunk-based development জনপ্রিয় হওয়ার পেছনে এই কৌশলের বড় ভূমিকা ছিল, কারণ অসমাপ্ত কাজও main branch-এ রাখা যায়। Martin Fowler-এর সাইটে Pete Hodgson-এর লেখা feature toggle প্রবন্ধটি এই বিষয়ের বহুল উদ্ধৃত পরিচিতি।
কিন্তু প্রতিটি flag কোডে একটি শাখা যোগ করে। দশটি স্বাধীন boolean flag মানে তাত্ত্বিকভাবে 210 = 1,024টি সম্ভাব্য অবস্থা। সব কটি পরীক্ষা করা অসম্ভব, তাই কিছু সমন্বয় কেউ কখনো চালায়নি, আর সেখানেই bug লুকিয়ে থাকে। এটিকে flag-এর প্রযুক্তিগত ঋণ বলা হয়।
দ্বিতীয় খরচ পুরোনো flag. Feature সবার জন্য চালু হয়ে যাওয়ার পরেও flag আর পুরোনো শাখা কোডে থেকে যায়। কয়েক বছর পরে কেউ জানেন না কোনটি নিরাপদে মোছা যায়। আর পুরোনো flag-এর নাম নতুন কাজে ব্যবহার করলে বিপদ বড়: ২০১২ সালের Knight Capital ঘটনায়, SEC-এর প্রতিবেদন অনুযায়ী, পুরোনো একটি flag নতুন অর্থে ব্যবহার আর একটি server-এ পুরোনো কোড থেকে যাওয়া মিলে বিপুল ক্ষতি হয়েছিল।
তৃতীয় খরচ নির্ভরতা। Flag-এর মান যদি একটি বাইরের সেবা থেকে আসে, সেটি ধীর বা বন্ধ হলে কী হবে? ভালো SDK একটি নিরাপদ ডিফল্ট আর cache রাখে। নিজের ব্যবস্থায়ও “flag পড়তে না পারলে পুরোনো আচরণ” নিয়ম রাখুন।
ভালো অভ্যাস: প্রতিটি flag-এর একজন মালিক আর একটি মেয়াদ; release flag সাময়িক, কাজ শেষে মুছে ফেলা; kill switch-এর মতো স্থায়ী flag আলাদা তালিকায়; আর flag-এর বদলও audit log-এ।
মূল উপলব্ধি: Feature flag হলো ধারে নেওয়া নমনীয়তা। সময়মতো ফেরত না দিলে, মানে মুছে না ফেললে, সুদসহ জটিলতা ফেরত আসে।
অনুশীলন
হিসাব, কমান্ড, সমালোচনা আর আউটপুট অনুমান, চারটিই আছে।
-
Rollout আর key-এর হিসাব: (ক) বাজারঘরের ৩০,০০০ সক্রিয় ব্যবহারকারী। নতুন checkout ১৫% rollout-এ। প্রত্যাশিত কতজন দেখবেন? ২৫%-এ তুললে নতুন কতজন যোগ হবেন? (খ) ৬২টি অক্ষর থেকে এলোমেলোভাবে বাছাই করা ২০ অক্ষরের একটি key-এর শক্তি কত bit? ১২৮ bit পেতে ন্যূনতম কত অক্ষর লাগবে?
(ক) 30,000 × 0.15 = ৪,৫০০ জন (stable hash হলে বাস্তবে এর কাছাকাছি, হুবহু নয়)। ২৫%-এ: 30,000 × 0.25 = ৭,৫০০, অর্থাৎ নতুন ৩,০০০ জন; আগের ৪,৫০০ জন থেকে যান, কারণ bucket 15-এর কম হলে 25-এরও কম।
(খ) log2(62) ≈ 5.954, তাই 20 × 5.954 ≈ ১১৯.১ bit. ১২৮ bit-এর জন্য 128 ÷ 5.954 ≈ 21.5, অর্থাৎ ন্যূনতম ২২ অক্ষর (22 × 5.954 ≈ ১৩১ bit পাওয়া যায়)।
-
নিজের হাতে যাচাই (কমান্ড): একটি Next.js প্রজেক্টে
.env.local-এNEXT_PUBLIC_TEST_MARK=marker-public-123আরSECRET_TEST_MARK=marker-secret-456রাখুন। দুটিই একটি Client Component-এ দেখান।next buildচালিয়ে কোন দুটি জায়গায় খুঁজবেন, আর কী দেখার কথা?প্রথমে bundle:
grep -rl marker-public-123 .next/staticঅন্তত একটি ফাইল দেখাবে;grep -rl marker-secret-456 .next/static | wc -lদেবে 0. তারপর চলমান পাতার HTML:next startচালিয়েcurl -s http://127.0.0.1:PORT/ | grep -c marker-secret-456. পাতাটি server-এ render হলে এটি 1 দেখাবে, অর্থাৎ HTML-এ secret ফাঁস। ঠিক করার উপায়: Client Component থেকে secret-এর নাম সরানো, secret শুধুserver-onlyফাইলে। -
Config সমালোচনা (নকশা): একজন সহকর্মী এই ব্যবস্থা প্রস্তাব করেছেন। অন্তত পাঁচটি সমস্যা ধরুন: “
.env.production-এ production database-এর password আরNEXT_PUBLIC_GATEWAY_SECRETরেখে commit করব, যাতে CI build পায়। Staging-এর জন্য আলাদা build বানাবNODE_ENV=stagingদিয়ে, আর staging একই database ব্যবহার করবে যাতে ডেটা বাস্তব থাকে। Payment-এর জন্য দুই জায়গাতেই live key.”- Password git-এ: history-তে চিরকাল, repository-র সবাই পান। CI-এর secret সেটিংয়ে রাখুন।
NEXT_PUBLIC_GATEWAY_SECRET: browser-এর bundle-এ লেখা হয়ে যাবে; যে কেউ পাতা খুলে পাবেন। Secret শুধু server-এ।NODE_ENV=staging: অমানক মান, Next.js সতর্ক করে;NODE_ENV=productionরেখেAPP_ENV=staging.- আলাদা build: staging-এ পরীক্ষিত জিনিস আর production-এর জিনিস ভিন্ন; build once, runtime config.
- একই database: staging-এর ভাঙা কোড বা script আসল অর্ডার নষ্ট করতে পারে; আলাদা database, নকল বা মুখোশ পরানো ডেটা।
- Staging-এ live key: পরীক্ষায় আসল টাকা কাটা যেতে পারে; staging-এ sandbox key.
-
আউটপুট অনুমান: একটি Next.js প্রজেক্টে
.env-এNEXT_PUBLIC_A=one,.env.production-এNEXT_PUBLIC_A=two,.env.local-এNEXT_PUBLIC_A=three, আর.env.production.localনেই। (ক)next buildচালালে bundle-এ কোন মান বসবে? (খ)NEXT_PUBLIC_A=four next buildচালালে? (গ) (খ)-এর build-এর পরNEXT_PUBLIC_A=five next startচালালে browser কী দেখাবে?(ক)
three: ক্রমেprocess.env-এ নেই,.env.production.localনেই, তারপর.env.local-এ পাওয়া গেল। (খ)four: shell-এর মান সবার ওপরে। (গ)four: মান build-এর সময় bundle-এ লেখা হয়ে গেছে;next start-এর সময়েরfiveকোনো প্রভাব ফেলে না।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি নতুন version নিরাপদে production-এ নেওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- CI-তে env schema যাচাইসহ একবার build
- Artifact সংরক্ষণ, একটি নম্বর বা digest দিয়ে
- Staging-এর config দিয়ে staging-এ deploy
- Staging-এ smoke test, sandbox key দিয়ে
- একই artifact production-এর config দিয়ে promote
- Error rate দেখা, দরকার হলে আগের artifact-এ rollback
Next.js-এ NEXT_PUBLIC_API_URL ব্যবহার করে build বানানো হলো। পরে server-এ এই variable-এর মান বদলে next start আবার চালানো হলো, নতুন build ছাড়া। Browser-এর কোড কোন মান ব্যবহার করবে?
- নতুন মান, কারণ server restart হয়েছে
- build-এর সময়ের পুরোনো মান
- undefined
- Error দিয়ে পাতা খুলবে না
Next.js-এ NODE_ENV=production. একটি variable .env, .env.local আর .env.production তিন ফাইলেই আছে, shell-এ নেই। কোন ফাইলের মান কার্যকর হবে?
.env.env.production- যেটি সবশেষে বদলানো হয়েছে
.env.local
একটি payment key ভুল করে একটি commit-এ চলে গিয়েছিল, পরের commit-এ ফাইলটি মুছে ফেলা হয়েছে, আর repository অন্যরা clone করেছেন। সবচেয়ে জরুরি প্রথম কাজ কোনটি?
.gitignore-এ ফাইলের নাম যোগ করা- History থেকে commit মুছে force push করা
- নতুন key বানিয়ে বসানো আর পুরোনো key বাতিল করা
- Repository private করে দেওয়া
“Build once, deploy many” নীতির প্রধান সুবিধা কোনটি?
- যা পরীক্ষা হলো হুবহু সেটিই production-এ চলে, আর rollback দ্রুত
- Bundle-এর আকার ছোট হয়
- Secret artifact-এর ভেতরে নিরাপদে থাকে
- সব পরিবেশ একই database ব্যবহার করতে পারে
একটি secret scanner apiKey = "aaaaaaaaaaaaaaaaaaaaaaaa" লাইনটি রিপোর্ট করল না, কিন্তু PAYMENT_SECRET=bzg_live_k7Qp2Xv9LmR4tW8nZc3YhJ6d রিপোর্ট করল। সম্ভাব্য কারণ কী?
- প্রথম মানটি খুব ছোট
- প্রথম মানের entropy শূন্য, তাই সেটি এলোমেলো key-এর মতো নয়
- Scanner
apiKeyনাম চেনে না - উদ্ধৃতিচিহ্নের ভেতরের লেখা scanner পড়ে না
Feature flag-এর percentage rollout-এ প্রতিবার Math.random() < 0.1 দিয়ে সিদ্ধান্ত নিলে প্রধান সমস্যা কী?
- মোট শতাংশ ১০%-এর অনেক বেশি হয়
- কোড খুব ধীর হয়
- Kill switch কাজ করে না
- একই ব্যবহারকারী একেকবার একেক রকম পাতা দেখেন
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন, অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Local, preview, staging, production: কোড এক, config আর database আলাদা; parity রাখুন, drift যন্ত্র দিয়ে ধরুন।
- Next.js-এর load order: process.env → .env.$(NODE_ENV).local → .env.local → .env.$(NODE_ENV) → .env; staging-এও
NODE_ENV=production. NEXT_PUBLIC_আরVITE_build-এ জমে যায়, server-এর কোডেও; Client Component-এ secret লিখলে SSR-এর HTML-এ ফাঁস হতে পারে।- Build once, deploy many: পরিবেশ ভেদে বদলানো মান runtime-এ; build-এর সময় schema দিয়ে যাচাই।
- Secret git-এ নয়; scanner দিয়ে history খুঁজুন, ফাঁস হলে rotate করুন; staging কখনো production database ছোঁবে না; feature flag-এ stable hash.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৭৯ · Build আর bundle — Vite, tree shaking আর code splittingপরবর্তী পর্বBuild আসলে কী বানায়।
- ফুল-স্ট্যাক ওয়েব ফ্রেমওয়ার্ক · কোর্স হোম৯৩টি পর্বপুরো কোর্সের রোডম্যাপ।
- Cloud & DevOps · Secrets ও KMSগভীরে যানKMS, envelope encryption আর downtime ছাড়া rotation.
- Cloud & DevOps · Artifact promotionপাশের ট্র্যাকএকটি artifact, অনেক environment.
- Software Engineering · Git securityপাশের ট্র্যাকHistory-তে secret, push protection আর পরিষ্কার করা।