১. ভূমিকা
গুগল ক্লাউডে রেইলস ৮ ওয়ার্কশপে আপনাকে স্বাগতম! এই হ্যান্ডস-অন কোডল্যাবে, আপনি একটি আধুনিক রেইলস ৮ অ্যাপ্লিকেশনকে একটি সাধারণ লোকাল SQLite বেসলাইন থেকে গুগল ক্লাউডে একটি প্রোডাকশন-গ্রেড, এন্টারপ্রাইজ-রেডি রেফারেন্স আর্কিটেকচারে রূপান্তর করবেন।
এই কর্মশালায় আমরা শুধু একটি অ্যাপ ডেপ্লয় করি না — আমরা একটি সুচিন্তিত, প্রোডাকশন-গ্রেড ক্লাউড আধুনিকীকরণ বাস্তবায়ন করি ( সঠিকভাবে সম্পন্ন লিফট-অ্যান্ড-শিফট! )।
আপনি সাধারণ "আমার মেশিনে চলে" ধরনের লোকাল সেটআপ (যেমন ইফিমিরাল ডিস্ক, এমবেডেড SQLite, ইন-প্রসেস জব) থেকে শুরু করে ধীরে ধীরে এটিকে গুগল ক্লাউডে একটি এন্টারপ্রাইজ, বুলেটপ্রুফ ক্লাউড আর্কিটেকচারে রূপান্তরিত করেন:
- 🐘 লোকাল SQLite থেকে ক্লাউড SQL (PostgreSQL): রিস্টার্টের সময় আর ডেটাবেস মুছে যাবে না। আমরা ক্লাউড SQL অথোরাইজেশন প্রক্সি (mTLS, কোনো পাবলিক আইপি নেই) এর মাধ্যমে নিরাপদে সংযুক্ত ম্যানেজড PostgreSQL ব্যবহার শুরু করেছি।
- 🪣 ক্ষণস্থায়ী লোকাল স্টোরেজ থেকে প্রাইভেট জিসিএস: সার্ভারলেস ডিস্ক অস্থায়ী। আমরা IAM স্বাক্ষরিত URL (
iam: true) ব্যবহার করে ActiveStorage-কে প্রাইভেট Google Cloud Storage-এর সাথে সংযুক্ত করি — কোনো পাবলিক বাকেট নেই, কোনো কী ফাঁস হওয়ার ভয় নেই। - 🚀 ইন-প্রসেস জবস থেকে মাল্টি-কন্টেইনার ক্লাউড রান: আমরা রেইলস মনোলিথ এবং সার্ভারলেস ক্লাউডের মধ্যে সেতুবন্ধন করি! স্ট্যান্ডার্ড ডকার কম্পোজ ব্যবহার করে, আমরা একটি ক্লাউড রান মাল্টি-কন্টেইনার পডে পুমা-র পাশাপাশি সলিড কিউ-কে একটি ডেডিকেটেড ওয়ার্কার কন্টেইনার হিসেবে চালাই — কোনো মাইক্রোসার্ভিস স্প্রল নেই, কোনো থ্রেড স্টারভেশন নেই, ডিএইচএইচ গর্বিত হতেন!
🍌 আর যেহেতু আমরা ২০২৬ সালে বাস করছি, তাই আমরা গুগল অ্যান্টিগ্র্যাভিটি দিয়ে পুরো ব্যবস্থাটিকে আরও শক্তিশালী করে তুলেছি । এটি কোনো হেলাফেলা করার বিষয় নয়, এই কর্মশালাটি আরও ভালোভাবে সম্পন্ন করতে (এবং উপভোগ করতে) আপনার হারনেসকে নির্দেশনা দেওয়ার জন্য আমাদের কাছে নির্দেশিকা ও কৌশল রয়েছে !
🏛️ টার্গেট রেফারেন্স আর্কিটেকচার (ভেরিয়েন্ট ১: ক্লিন ফ্ল্যাট ভেক্টর এন্টারপ্রাইজ)
এখানে রয়েছে গুগল ক্লাউডের আদর্শ রেফারেন্স আর্কিটেকচার ( ভেরিয়েন্ট ১: ক্লিন ফ্ল্যাট ভেক্টর এন্টারপ্রাইজ ), যা আপনি তৈরি ও স্থাপন করবেন। এতে আমাদের প্রোডাকশন মাল্টি-কন্টেইনার ক্লাউড রান সার্ভিসটি প্রদর্শিত হয়েছে, যা গুগল ক্লাউডের সমস্ত পরিচালিত পার্সিস্টেন্স এবং এআই সার্ভিসের সাথে সংযুক্ত।

🎞️ স্থাপত্যের বিবর্তন: শূন্য থেকে ক্লাউড-নেটিভ
প্রতিটি ওয়ার্কশপ মাইলস্টোন জুড়ে দেখুন কীভাবে আমাদের স্ট্যাকটি একটি ক্ষণস্থায়ী একক-মেশিন বেসলাইন থেকে ক্রমান্বয়ে একটি সম্পূর্ণভাবে পরিচালিত গুগল ক্লাউড ব্লুপ্রিন্টে আধুনিক হয়ে উঠছে:

আপনি যা শিখবেন
- রেইলস ৮ এবং গুগল ক্লাউডের রহস্য উন্মোচনে গুগল অ্যান্টিগ্র্যাভিটির সাথে কীভাবে পেয়ার-প্রোগ্রামিং করবেন।
- কীভাবে স্বয়ংক্রিয় প্রাক-উড্ডয়ন ডায়াগনস্টিকস চালানো যায় (
just workshop-test)। -
googleপ্রোভাইডার ব্যবহার করে লোকাল ডেভেলপমেন্ট ব্লক না করে, টেরাফর্মের সাহায্যে কীভাবে অ্যাসিঙ্ক্রোনাসভাবে গুগল ক্লাউড ইনফ্রাস্ট্রাকচার প্রোভিশন করা যায়। - নিরাপত্তা অ্যান্টি-প্যাটার্ন দূর করার উপায়:
0.0.0.0/0খোলার পরিবর্তে প্রাইভেট GCS বাকেট (iam: true) এবং ক্লাউড SQL অথ প্রক্সি mTLS টানেল ব্যবহার করুন। - গুগল ক্লাউড সিক্রেট ম্যানেজার থেকে সরাসরি সিক্রেট কীভাবে ইনজেক্ট করবেন।
- Solid Queue- এর মাধ্যমে কীভাবে অ্যাসিঙ্ক্রোনাস GenAI ব্যাকগ্রাউন্ড জব (ন্যানোব্যানানা কভার জেনারেটর, দ্বিভাষিক পডকাস্ট সিন্থেসিস) পরিচালনা করা যায়।
চলুন শুরু করা যাক!
২. ধাপ ০: পূর্বশর্তসমূহ, অ্যান্টিগ্র্যাভিটি সেটআপ এবং বিলিং যাচাইকরণ
💡 প্রেক্ষাপট: আপনি এবং আপনার দল একটি অত্যন্ত গুরুত্বপূর্ণ রেইলস ৮ (Rails 8) অ্যাপ্লিকেশন তৈরি করছেন। কোডে হাত দেওয়ার বা ক্লাউড রিসোর্স চালু করার আগে, আমাদের অবশ্যই টুলচেইন স্থাপন করতে হবে, গুগল অ্যান্টিগ্র্যাভিটি (Google Antigravity) সংযোগ করতে হবে এবং আমাদের গুগল ক্লাউড ক্রেডেনশিয়াল ও বিলিং ফাউন্ডেশন যাচাই করতে হবে।
১. পূর্বশর্তের চেকলিস্ট
শুরু করার আগে, নিশ্চিত করুন যে আপনার পরিবেশে নিম্নলিখিত সরঞ্জামগুলি উপলব্ধ আছে:
-
just(১.২১+): (just --version) টাস্ক রানার — এই ওয়ার্কশপের প্রতিটি কমান্ডই একটিjustরেসিপি (আপনার এআই হারনেসকে এটি ইনস্টল করতে বলুন, অথবাskills/rails8app-workshopদেখুন)। - গিট (২.৩০+): (
git --version) ভার্সন কন্ট্রোল, ব্রাঞ্চিং এবং রিপোজিটরি ক্লোন করার জন্য। - গুগল ক্লাউড এসডিকে (
gcloudCLI): ইনস্টল করা এবং হালনাগাদ। - টেরাফর্ম সিএলআই (১.৫+): ঘোষণামূলক পরিকাঠামো সরবরাহের জন্য।
-
dockerওdocker-compose: স্থানীয়ভাবে ইনস্টল এবং চালু আছে। - রুবি
3.4.5: (ruby -v—blog/.ruby-versionদ্বারা পিন করা; অনুপস্থিত থাকলে আপনার AI হারনেসকে3.4.5ইনস্টল করতে বলুন)। রেইলস ৮Gemfileএ ঘোষিত এবংbundle installদ্বারা স্বয়ংক্রিয়ভাবে ইনস্টল হয়। - গুগল অ্যান্টিগ্র্যাভিটি ২.০: আপনার স্বয়ংক্রিয় এআই পেয়ার প্রোগ্রামিং সহকারী ( গুগল অ্যান্টিগ্র্যাভিটি ২.০ ডাউনলোড করুন )। আপনি আপনার ব্যক্তিগত জিমেইল দিয়ে লগইন করবেন — কোনো এপিআই টোকেনের প্রয়োজন নেই।
২. গুগল ক্লাউড অথেন্টিকেশন, ডেডিকেটেড কনফিগারেশন ও এডিসি
বিদ্যমান কর্পোরেট, ব্যক্তিগত, বা একাধিক-অ্যাকাউন্ট gcloud সেটআপের সাথে সংঘর্ষ এড়ানোর জন্য, আমরা এই কর্মশালার জন্য একটি নির্দিষ্ট নামযুক্ত কনফিগারেশন তৈরি করার জন্য দৃঢ়ভাবে সুপারিশ করছি:
# 1. Create and activate dedicated workshop configuration (Fixes Issue #38)
gcloud config configurations create rails8-on-gcp-workshop --activate 2>/dev/null || \
gcloud config configurations activate rails8-on-gcp-workshop
# 2. Authenticate your account and Application Default Credentials (ADC)
gcloud auth login $GOOGLE_CLOUD_ACCOUNT
gcloud auth application-default login
# 3. Configure active project and region
gcloud config set project $GOOGLE_CLOUD_PROJECT
gcloud config set compute/region europe-west1
💡 কেন একটি ডেডিকেটেড কনফিগারেশন? gcloud config configurations create rails8-on-gcp-workshop ব্যবহার করে সমস্ত CLI সেটিংস (অ্যাকাউন্ট, কোটা, প্রজেক্ট, ডিফল্ট রিজিয়ন) বিশেষভাবে এই ওয়ার্কশপের জন্য আলাদা করে রাখা হয়। কাজ শেষ হলে, আপনি যেকোনো সময় gcloud config configurations activate default মাধ্যমে আপনার সাধারণ সেটআপে ফিরে যেতে পারবেন।

৩. 🚨 জিসিপি বিলিং যাচাইকরণ
⚠️ দ্রষ্টব্য : Google Cloud SQL এবং Cloud Run ডেপ্লয়মেন্টের জন্য একটি সক্রিয় লিঙ্ক করা বিলিং অ্যাকাউন্ট অথবা বৈধ ওয়ার্কশপ এডুকেশনাল ক্রেডিট প্রয়োজন। চলুন এখন আপনার সেটআপটি সঠিক আছে কিনা তা নিশ্চিত করা যাক। এটি করার জন্য, বিলিং ভেরিফিকেশন চেকটি চালান:
gcloud beta billing projects describe $GOOGLE_CLOUD_PROJECT
নিশ্চিত করুন billingEnabled: true রিটার্ন করা হয়। আপনি আপনার সমস্ত বিলিং অ্যাকাউন্ট তালিকাভুক্ত করতে পারেন:
gcloud billing accounts list
নিশ্চিত করুন যে সেগুলোর মধ্যে অন্তত একটিতে OPEN: True দেখাচ্ছে। যদি কোনোটিই সক্রিয় না থাকে, তাহলে একটি বিলিং অ্যাকাউন্ট লিঙ্ক করুন অথবা Google Cloud Console বিলিং পেজ- এ আপনার ওয়ার্কশপ ক্রেডিট কুপনটি রিডিম করুন।

৪. রিপোজিটরিটি ক্লোন করুন এবং অ্যান্টিগ্র্যাভিটির সাথে যুক্ত করুন
রিপোজিটরিটি ক্লোন করুন এবং ডিরেক্টরিতে প্রবেশ করুন। লক্ষ্য করুন যে আমরা সম্পূর্ণভাবে main এই থাকব:
git clone https://github.com/palladius/rails8-app-on-gcp.git
cd rails8-app-on-gcp
গুগল অ্যান্টিগ্র্যাভিটিতে এই ডিরেক্টরিটি খুলুন। অ্যান্টিগ্র্যাভিটি স্বয়ংক্রিয়ভাবে রিপোজিটরিটি পরীক্ষা করবে, AGENTS.md ফাইলটি পড়বে এবং আপনার পেয়ার প্রোগ্রামার হিসেবে প্রস্তুত থাকবে।
৫. 🪫 যদি অ্যান্টিগ্র্যাভিটির ফ্রি ক্রেডিট শেষ হয়ে যায়
⚠️ আপনি এইমাত্র যে ক্লাউড ক্রেডিটগুলো রিডিম করেছেন, তা দিয়ে অ্যান্টিগ্র্যাভিটি রিফিল হয় না। এগুলোর বিলিং ব্যবস্থা দুটি আলাদা: অ্যান্টিগ্র্যাভিটির কোটা গুগল এআই প্ল্যান (গুগল ওয়ান)-এর অধীনে থাকে, অন্যদিকে ভাউচার এবং ক্লাউড ক্রেডিট একটি ক্লাউড বিলিং অ্যাকাউন্টে জমা হয়। আপনার GCP প্রোজেক্টের সাথে একটি বিলিং অ্যাকাউন্ট লিঙ্ক করলে আপনি একটিও অতিরিক্ত অ্যান্টিগ্র্যাভিটি প্রম্পট পাবেন না।
যদি এজেন্ট কোটা বার্তা দিয়ে থেমে যায়, তাহলে এই ধাপগুলো অনুসরণ করুন — প্রথম দুটি বিনামূল্যে এবং তাৎক্ষণিক:
- মডেল পুল পরিবর্তন করুন। প্রতিটি মডেল ফ্যামিলির জন্য কোটা ট্র্যাক করা হয়, এবং অ্যান্টিগ্র্যাভিটি দুটি স্বাধীন পুল দেখায় (জেমিনি মডেল বনাম ক্লড/জিপিটি মডেল)। ক্লড এবং জিপিটি-ওএসএস মডেলগুলো ফ্রি টিয়ারে পাওয়া যায়, তাই জেমিনি পুলের কোটা শেষ হয়ে গেলে প্রায়শই অন্যটি অব্যবহৃত থেকে যায়। প্রম্পট বক্সের নিচে থাকা মডেল সিলেক্টরটি ব্যবহার করুন।

- প্রো-এর পরিবর্তে ফ্ল্যাশ মডেল বেছে নিন। রেট লিমিট নির্ভর করে এজেন্ট প্রতিটি অনুরোধে কতটা কাজ করে তার উপর, তাই একটি হালকা মডেল অবশিষ্ট কাজের পরিমাণকে আরও বেশিদিন ধরে চালাতে সাহায্য করে।
- আপনার নিজের জেমিনি এপিআই কী (Gemini API key) দিয়ে অ্যান্টিগ্র্যাভিটি সিএলআই (Antigravity CLI) ব্যবহার করুন । অ্যান্টিগ্র্যাভিটি আইডিই (Antigravity IDE)-তে ব্রিং-ইয়োর-ওন-কী (Bring-your-own-key) সমর্থিত নয় — সিএলআই হলো এর নথিভুক্ত বিকল্প। গুগল এআই স্টুডিও (Google AI Studio) -তে একটি বিনামূল্যের কী তৈরি করুন (এর জন্য একটি প্রজেক্ট প্রয়োজন, কিন্তু কোনো বিলিং অ্যাকাউন্টের দরকার নেই ), তারপর অ্যান্টিগ্র্যাভিটি সিএলআই ডাউনলোড এবং কনফিগার করুন।
- অপেক্ষা করুন, অথবা আপগ্রেড করুন । ফ্রি-টিয়ার কোটা প্রতি সপ্তাহে রিফ্রেশ হয় (এরর মেসেজে আপনার রিসেটের তারিখ উল্লেখ করা থাকে)। ডেস্কটপ অ্যাপের মধ্যে বেসলাইন বাড়ানোর একমাত্র নথিভুক্ত উপায় হলো গুগল এআই প্রো/আল্ট্রা; এই প্ল্যানগুলো প্রতি পাঁচ ঘণ্টা পর পর রিফ্রেশ হয় এবং অতিরিক্ত ব্যবহারের জন্য কেনা এআই ক্রেডিট খরচ করা যায়।
- যদিও এটি একটি অ্যান্টিগ্র্যাভিটি ওয়ার্কশপ, বেশিরভাগ এআই কোডিং হারনেসও কাজ করা উচিত — শুধু আপনার হারনেসটিকে রিপোজিটরির
AGENTS.mdএবংskills/ডিরেক্টরির দিকে নির্দেশ করে দিন।
৬. স্বয়ংক্রিয় ধাপ ০ যাচাইকরণ
যাচাই করুন যে আপনার স্টেপ ০ এনভায়রনমেন্টটি ইভ্যালুয়েশন স্যুটের সাথে শতভাগ সঙ্গতিপূর্ণ:
just workshop-eval 0
✨ অসাধারণ মুহূর্ত: অ্যান্টিগ্র্যাভিটি এবং ওয়ার্কশপ ইভ্যালুয়েশন ইঞ্জিন ২ সেকেন্ডেরও কম সময়ে আপনার CLI ভার্সন, রুবি এনভায়রনমেন্ট এবং ADC অথেনটিকেশন যাচাই করে নেয়!
৩. ধাপ ১: টেরাফর্ম ইনফ্রাস্ট্রাকচার কিকঅফ এবং প্রি-ফ্লাইট ডায়াগনস্টিকস
💡 কৌশল: গুগল ক্লাউড এসকিউএল বা পোস্টগ্রেসকিউএল-এর মতো ম্যানেজড ডেটাবেস প্রোভিশন করতে প্রায় ১০-১২ মিনিট সময় লাগে। পরে অলসভাবে অপেক্ষা না করে, আমরা স্থানীয়ভাবে ডেভেলপ করার সময়েই টেরাফর্মের মাধ্যমে ব্যাকগ্রাউন্ডে অপরিবর্তনীয় ইনফ্রাস্ট্রাকচার চালু করে দিই!
১. স্বয়ংক্রিয় প্রি-ফ্লাইট ডায়াগনস্টিকস স্যুট চালান
ক্লাউড ইনফ্রাস্ট্রাকচার চালু করার আগে, বিস্তারিত প্রি-ফ্লাইট টেস্ট স্যুটটি চালান:
just workshop-test
এই স্ক্রিপ্টটি ( bin/workshop_diagnostics.rb ):
- আপনার
GOOGLE_CLOUD_ACCOUNTপরিচয় কনফিগারেশন যাচাই করে। - সক্রিয় বিলিং এবং প্রকল্পের সংযোগ যাচাই করে।
- Vertex AI-এর জন্য অ্যাপ্লিকেশন ডিফল্ট ক্রেডেনশিয়াল (ADC) যাচাই করে।
- ActiveStorage ক্যানারি সিড ইমেজটি (
blog/app/assets/images/gcs_dev_image.jpg) নিশ্চিত করা হয়েছে।

যদি .env ফাইলটি না থাকে, তাহলে ডকুমেন্ট করা টেমপ্লেট থেকে এটি কপি করুন:
cp .env.dist .env
# Edit .env and configure GOOGLE_CLOUD_ACCOUNT with your Google/Gmail account
💡 পরামর্শ: যদি just workshop-test রিপোর্ট করে যে আপনার প্রোজেক্টে বিলিং API নিষ্ক্রিয় করা আছে, তাহলে দ্রুত এটি সক্রিয় করুন:
gcloud services enable cloudbilling.googleapis.com
২. ⏱️ অ্যাসিঙ্ক্রোনাসভাবে টেরাফর্ম ইনফ্রাস্ট্রাকচার চালু করুন
এক-ধাপের টেরাফর্ম রেসিপিটি চালান, যা রিমোট ব্যাকএন্ড স্টেট বাকেট তৈরি করে এবং ইনফ্রাস্ট্রাকচার ডেপ্লয়মেন্ট চালু করে:
just terraform-apply

ম্যানুয়াল বিকল্প (অথবা সরাসরি `iac/bin/terraform-apply.sh` চালান)
# Fall back to active gcloud config if environment variables are unset
GOOGLE_CLOUD_PROJECT="${GOOGLE_CLOUD_PROJECT:-$(gcloud config get-value project)}"
GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Create remote state bucket idempotently
gcloud storage buckets create "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" --location="$GOOGLE_CLOUD_REGION" 2>/dev/null || \
gcloud storage buckets describe "gs://${GOOGLE_CLOUD_PROJECT}-tfstate" >/dev/null
cd iac
terraform init -backend-config="bucket=${GOOGLE_CLOUD_PROJECT}-tfstate"
terraform apply -auto-approve -var="project_id=${GOOGLE_CLOUD_PROJECT}" -var="region=${GOOGLE_CLOUD_REGION}"
cd ..
টেরাফর্ম যা বিধান করে:
- গুগল ক্লাউড স্টোরেজ বাকেট: ব্যক্তিগত অ্যাক্সেস এবং IAM ক্রেডেনশিয়াল সাইনিং (
iam: true) সহ তৈরি করা হয়েছে। - ক্যানারি টেস্ট ইমেজ: এন্ড-টু-এন্ড ব্লব ভেরিফিকেশন সক্ষম করার জন্য
gcs_dev_image.jpgফাইলটি বাকেটে আপলোড করে। - গুগল ক্লাউড SQL PostgreSQL ইনস্ট্যান্স: ব্যাকগ্রাউন্ড প্রোভিশনিং শুরু হয় (~১০-১২ মিনিট)।

💡 দ্রষ্টব্য: স্বয়ংক্রিয় ব্যাকআপ না থাকার সতর্কবার্তাটি আপনি নিশ্চিন্তে উপেক্ষা করতে পারেন — এই ওয়ার্কশপ ডেটাবেসের জন্য আমাদের সেগুলোর প্রয়োজন হবে না (যদিও যেকোনো প্রকৃত প্রোডাকশন ডেটাবেসের জন্য স্বয়ংক্রিয় দৈনিক ব্যাকআপ একটি অপরিহার্য উত্তম অনুশীলন!)।

📝 আপনার ক্লাউড রান ইউআরএল (Cloud Run URL) লিখে রাখুন: Terraform apply সফল হওয়া উচিত এবং আপনার ইনফ্রাস্ট্রাকচার আউটপুট প্রিন্ট করা উচিত। আপনার cloud_run_url লিখে রাখুন (অথবা ক্লিপবোর্ডে কপি করে নিন) — পরবর্তী ধাপে আপনার লাইভ সার্ভিস যাচাই করার সময় এটি প্রয়োজন হবে!
৩. স্বয়ংক্রিয় ধাপ ১ যাচাইকরণ (ঐচ্ছিক)
আপনার ধাপ ১-এর পূর্বশর্ত এবং কনফিগারেশনগুলো পূরণ হয়েছে কিনা তা যাচাই করুন:
just workshop-eval 1
⏱️ দ্রষ্টব্য: এই মূল্যায়নটি লাইভ ক্লাউড রিসোর্স পরীক্ষা করে এবং এটি শেষ হতে কয়েক মিনিট সময় লাগতে পারে। এটি সম্পূর্ণ ঐচ্ছিক — যখন এটি আপনার টার্মিনালে চলতে থাকে, আপনি অপেক্ষা না করেই অবিলম্বে পরবর্তী ধাপে এগিয়ে যেতে পারেন!
৪. ধাপ ২: লোকাল বেসলাইন, মেইলপিট এবং অ্যাডমিন অনবোর্ডিং
আমাদের সূচনা বিন্দু হলো একটি পরিচ্ছন্ন ও আধুনিক রেইলস ৮ ব্লগ অ্যাপ্লিকেশন, যা লোকালহোস্টে SQLite, Mailpit ইমেইল ইন্টারসেপশন এবং ডিস্ক-ভিত্তিক ActiveStorage সহ চলমান।
১. অ্যাপটি স্থানীয়ভাবে বুট করুন
📁 ডিরেক্টরিটির দিকে খেয়াল রাখুন! এই রিপোজিটরিতে blog/ ডিরেক্টরিতে Rails অ্যাপ এবং iac/ ডিরেক্টরিতে Terraform রয়েছে। ধাপ ১ আপনাকে রিপোজিটরির রুট ডিরেক্টরিতে রেখেছিল, তাই ধাপ ২-এর প্রতিটি কমান্ড সেখান থেকেই রান করবে।
blog/ — Gemfile , bin/rails এবং compose.yaml সবই সেখানে থাকে। রুট থেকে এগুলো চালাতে গেলে no configuration file provided: not found অথবা Gemfile অনুপস্থিত' এই ত্রুটি দেখায়।
Rails অ্যাপে প্রবেশ করুন, নিশ্চিত করুন যে আপনি পিন করা Ruby-তে আছেন, তারপর জেমগুলি ইনস্টল করুন এবং ডাটাবেস সেট আপ করুন:
cd blog
ruby -v # must print 3.4.5 — see Step 0 if it does not
bundle install
bin/rails db:setup
🧯 লেখার সময় অনুমতির ত্রুটি
/var/lib/gems/
অথবা
ruby -v
সিস্টেম রুবি (যেমন 3.3.x) দেখাচ্ছে? ভ্যানিলা লিনাক্স সিস্টেম রুবি .ruby-version ফাইলগুলোকে পুরোপুরি উপেক্ষা করে এবং সিস্টেম-ব্যাপী জেম ইনস্টল করার চেষ্টা করে! নিশ্চিত করুন যে আপনার সক্রিয় টার্মিনাল শেলে rbenv লোড করা আছে:
eval "$(rbenv init - bash)" # or: eval "$(rbenv init - zsh)"
ruby -v # confirm it now outputs 3.4.5!
দ্রষ্টব্য: যদি আপনি ধাপ ০-তে 3.4.5 ইনস্টল করা বাদ দিয়ে থাকেন, তাহলে rbenv install 3.4.5 চালান। অথবা সহজভাবে নিচের মোড এ (ডকার কম্পোজ) ব্যবহার করুন ( just compose-up ), যা হোস্ট জেম স্পর্শ না করেই পিন করা রুবি স্ট্যাকটি সম্পূর্ণরূপে কন্টেইনারের ভিতরে চালায়!
💡 এটা নিয়ে ভাবতে চান না? just install এবং রিপো রুট থেকে just compose-up এটি আপনার জন্য cd blog কমান্ডটি করে দেবে।
২. 🌱 স্মার্ট সিড অটো-ডিসকভারি এবং অ্যাডমিন বুটস্ট্র্যাপ (ইস্যু #২১ ও #২৫)
ডাটাবেস সিড ( db/seeds.rb )-এ স্মার্ট এনভায়রনমেন্ট অটো-ডিসকভারি বৈশিষ্ট্য রয়েছে:
- এটি আপনার সক্রিয় ডাটাবেস অ্যাডাপ্টার (SQLite বনাম Postgres) এবং স্টোরেজ কনফিগারেশন পরীক্ষা করে।
- এটি স্টেজ ০ (লোকালহোস্ট) শনাক্ত করে এবং স্বয়ংক্রিয়ভাবে প্রাথমিক অ্যাডমিন ব্যবহারকারী তৈরি করে ও শিক্ষামূলক পোস্টটি স্থাপন করে:
-
[LOCAL BASELINE] Welcome to Rails 8 on Localhost! - ডিফল্টভাবে একটি স্থানীয় দুঃখজনক ছবির সংযুক্তি (
local_sad_image.png), যাতে একটি ওয়াটারমার্ক রয়েছে যা আপনাকে জানিয়ে দেয় যে স্থানীয় ডিস্ক স্টোরেজ ক্ষণস্থায়ী।
-
- এটি অ্যাকশনমেইলারের মাধ্যমে স্বয়ংক্রিয়ভাবে একটি পাসওয়ার্ড রিসেট ইমেল পাঠিয়ে দেয়।
সীডটি যাচাই করুন বা পুনরায় চালান (এটি স্বয়ংক্রিয়ভাবে .env থেকে আপনার GOOGLE_CLOUD_ACCOUNT ব্যবহার করে):
bin/rails db:seed # or from repo root: just seed
ডকার কম্পোজের মাধ্যমে স্থানীয় ডেভেলপমেন্ট স্ট্যাকটি বুট করুন:
docker compose up -d # from blog/ — or `just compose-up` from the repo root

অ্যাপ্লিকেশন, স্থানীয় মেইলপিট SMTP সার্ভার এবং অ্যাডমিনার ডাটাবেস ভিউয়ার এখন পৃথক কন্টেইনারে একসাথে চলছে:
- রেইলস অ্যাপ : http://localhost:3000
- মেইলপিট ওয়েব ইউআই : http://localhost:8025
- প্রশাসক DB UI : http://localhost:8081
🧯 পোর্ট ৩০০০-এর দ্বন্দ্ব অথবা
server.pid
ত্রুটি? আপনি যদি আগে একটি নেটিভ ডেভ সার্ভার ( bin/dev ) চালু করে থাকেন, তাহলে Ctrl+C চেপে সেটি বন্ধ করুন। যদি ডকার অভিযোগ করে যে পোর্ট 3000 ইতিমধ্যেই বরাদ্দ করা আছে অথবা একটি অবশিষ্ট server.pid খুঁজে পায়, তাহলে এটি পরিষ্কার করুন:
just compose-down
rm -f blog/tmp/pids/server.pid
just compose-up
৩. মেইলপিট অভিজ্ঞতা এবং অ্যাডমিন লগইন
- Mailpit দিয়ে পাঠানো ইমেল গ্রহণ করুন : আপনার ব্রাউজারে
http://localhost:8025খুলুন। আপনার স্থানীয় ইনবক্সটি দেখুন!db:seedচলাকালীন, ActionMailer একটি অ্যাডমিন অনবোর্ডিং ইমেল পাঠিয়েছিল, যা Mailpit কোনো বাহ্যিক ইমেল সার্ভার বা ক্রেডেনশিয়াল স্পর্শ না করেই নিরাপদে স্থানীয়ভাবে গ্রহণ করেছে:
২. ব্লগে লগ ইন করুন : আপনার ব্রাউজারে http://localhost:3000 খুলুন।
- আপনার পাসওয়ার্ড সেট করার জন্য আপনি সরাসরি মেইলপিট ইমেইলের ভেতরে থাকা পাসওয়ার্ড রিসেট লিঙ্কে ক্লিক করতে পারেন।
- বিকল্পভাবে, আপনার Google Cloud অ্যাকাউন্টের ইমেল (
.envথেকে) এবং ডিফল্ট নির্ধারিত পাসওয়ার্ড:Ch4ng3m3!!1ব্যবহার করে লগ ইন করুন।
- ভিজ্যুয়াল টেলিমেট্রি ব্যাজগুলো পর্যবেক্ষণ করুন:
- UI-তে হলুদ এনভায়রনমেন্ট ব্যানার এবং ব্যাজগুলো লক্ষ্য করুন:
Notice: Ephemeral Database Active (POSTGRESQL)and[EPHEMERAL DB / STORAGE] 💾 Local. - পোস্টের ওয়াটারমার্কটি লক্ষ্য করুন: 🏠 স্ট্যাম্পটি (কভার ইমেজের নিচের-ডান কোণায় থাকা
nanobanana_stamp_local.png)। এটি তাৎক্ষণিক চাক্ষুষভাবে নিশ্চিত করে যে আপনার অ্যাসেট এবং ডেটাবেস বর্তমানে ক্ষণস্থায়ী লোকাল স্টোরেজের সাথে সংযুক্ত রয়েছে।
- UI-তে হলুদ এনভায়রনমেন্ট ব্যানার এবং ব্যাজগুলো লক্ষ্য করুন:

💡 ঐচ্ছিক প্রো-টিপ: রেইলস কনসোল অনুশীলন। CLI থেকে আপনার দেওয়া ডেটার সাথে রেইলস কীভাবে কাজ করে তা জানতে আগ্রহী? ইন্টারেক্টিভ কনসোলে প্রবেশ করুন:
bin/rails console
আপনার এনভায়রনমেন্ট ভেরিয়েবল ব্যবহার করে ডায়নামিকভাবে আপনার তৈরি করা অ্যাডমিন ব্যবহারকারীকে পরীক্ষা করুন:
user = User.find_by(email_address: ENV.fetch("GOOGLE_CLOUD_ACCOUNT"))
# Or simply grab the first user:
user = User.first
puts "Admin email: #{user.email_address} (created via: #{user.created_via})"
exit
৪. স্বয়ংক্রিয় ধাপ ২ যাচাইকরণ
আপনার স্থানীয় বেসলাইন এবং অ্যাডমিন সেটআপ যাচাই করুন:
just workshop-eval 2

৫. মূল সমস্যা: রাষ্ট্রহীন কন্টেইনার
ক্লাউড রান কন্টেইনারগুলো স্টেটলেস এবং ক্ষণস্থায়ী। যদি আমরা আমাদের SQLite ডেটাবেস এবং লোকাল storage/ ডিরেক্টরি সরাসরি ক্লাউড রানে ডেপ্লয় করি, তাহলে যখনই কোনো কন্টেইনারের স্কেল শূন্যে নেমে আসবে বা এটি রিস্টার্ট হবে, তখন সমস্ত পোস্ট এবং আপলোড করা ছবি স্থায়ীভাবে মুছে যাবে।
পরবর্তী ধাপে, আমরা স্টেটলেস শক-এর প্রভাব সরাসরি প্রত্যক্ষ করার জন্য ইচ্ছাকৃতভাবে এই ক্ষণস্থায়ী কনফিগারেশনটি ক্লাউড রান-এ স্থাপন করব!
৫. ধাপ ৩: প্রয়োগ ১ — স্টেটলেস শক (৩ মিনিটেই চমকপ্রদ ফলাফল!)
ওয়েবে কিছু দেখার আগে ক্লাউড ডেটাবেসের জন্য ১৫-২০ মিনিট অপেক্ষা করার পরিবর্তে, আধুনিক সার্ভারলেস ডেভেলপমেন্ট একটি প্রাথমিক বিজয় দিয়েই শুরু হয়: আমাদের সিঙ্গেল-কন্টেইনার রেইলস অ্যাপ্লিকেশনটি ৩ মিনিটেরও কম সময়ে সরাসরি গুগল ক্লাউড রান- এ ডেপ্লয় করা!
১. টাইম-মেশিন রিওয়াইন্ড
আমাদের প্রারম্ভিক কনফিগারেশনটি যে শতভাগ ক্ষণস্থায়ী (স্থানীয় SQLite ডেটাবেস এবং স্থানীয় ডিস্ক ফাইল স্টোরেজ), তা নিশ্চিত করতে জিরো-ব্রাঞ্চ টাইম-মেশিন ইঞ্জিনটি ব্যবহার করুন:
just workshop-rewind 1
💡 এইমাত্র কী ঘটল? workshop-rewind 1 main শাখা থেকে বের না হয়েই blog/config/ ফোল্ডারে stage-1-stateless কনফিগারেশন ওভারলেটি প্রয়োগ করেছে। আপনার অ্যাপটি কন্টেইনার ডিস্কে SQLite এবং লোকাল ফাইলসিস্টেমে ActiveStorage দিয়ে কনফিগার করা আছে।
২. ক্লাউড রানে একক-কন্টেইনার পুমা স্থাপন করা
সরাসরি সোর্স কোড থেকে ক্লাউড রান-এ ডিপ্লয় করুন। আধুনিক রেইলস ৮ স্বয়ংক্রিয়ভাবে একটি অফিশিয়াল, প্রোডাকশন-রেডি Dockerfile তৈরি করে (মাল্টি-স্টেজ বিল্ড, jemalloc, এবং নন-রুট সিকিউরিটি সহ)। যখন আপনি blog/ ফোল্ডার থেকে ডিপ্লয় করেন, গুগল ক্লাউড বিল্ড সরাসরি এই নেটিভ Dockerfile শনাক্ত করে এবং একটি ম্যানেজড সার্ভারলেস সার্ভিস প্রোভিশন করে:
# Ensure you are inside the Rails application directory
cd blog
# Ensure default region is set
export GOOGLE_CLOUD_REGION="europe-west1"
# Deploy single-container service from source (with dummy key fallback if starting without credentials)
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--allow-unauthenticated \
--set-env-vars GOOGLE_CLOUD_ACCOUNT=$GOOGLE_CLOUD_ACCOUNT,SECRET_KEY_BASE_DUMMY=1
আপনি এর অনুরূপ একটি অগ্রগতি আউটপুট দেখতে পাবেন:

⏳ এতে কয়েক মিনিট সময় লাগতে পারে, কারণ এই সময়ের মধ্যে ক্লাউড বিল্ড আপনার কন্টেইনারটি প্যাকেজ করবে এবং ক্লাউড রান রিভিশনটি প্রস্তুত করবে।
🧯 সমস্যা সমাধান এবং সাধারণ সতর্কতা
PERMISSION_DENIED: Build failed because the default service account is missing required IAM permissions
দুটি ভিন্ন কারণে একই ত্রুটি দেখা দেয় :
- gcloud এইমাত্র আপনার জন্য একটি API সক্রিয় করেছে। যদি deploy করার সময়
The following APIs are not enabled ... cloudbuild.googleapis.com" লেখাটি প্রিন্ট হয় এবং আপনিYউত্তর দিয়ে থাকেন, তাহলে নতুন পারমিশনগুলো সম্পূর্ণরূপে প্রোপাগেট হওয়ার আগেই বিল্ড শুরু হয়ে থাকতে পারে — gcloud-এর নিজস্ব প্রম্পটেই সতর্ক করা হয় "এতে কয়েক মিনিট সময় লাগবে"। এক মিনিট অপেক্ষা করুন এবং হুবহু একই কমান্ডটি আবার চালান ; এটি সাধারণত সফল হয়। ধাপ ১-এরterraform applyসাধারণত আপনার জন্য এই API-টি সক্রিয় করে দেয়, তাই আপনার প্রম্পটটি মোটেও দেখার কথা নয়। - ডিফল্ট কম্পিউট SA-তে আসলেই রোলের অভাব রয়েছে। আধুনিক GCP প্রোজেক্টগুলো এতে লিস্ট প্রিভিলেজ প্রয়োগ করে, যা সোর্স ডিপ্লয়মেন্টে সমস্যা তৈরি করে। এটি ঠিক করুন:
just project-status # grants storage.admin, logging.logWriter, artifactregistry.writer, cloudbuild.builds.builderjust workshop-testও এখন এটি রিপোর্ট করছে, তাই deploy বারবার ব্যর্থ হলে এটি পুনরায় চালানো উচিত।
মোতায়েনের সময়:
- ক্লাউড রান আপনার রেইলস কন্টেইনার ইমেজ তৈরি করে।
- এটি একটি সর্বজনীন, সুরক্ষিত TLS ডোমেইন বরাদ্দ করে:
https://blog-[hash]-[region].a.run.app। - ৮০৮০ পোর্টে পুমা-তে ট্র্যাফিক রাউটিং শুরু হয়।
৩. ✨ প্রথম দিকের সেই মুগ্ধতার মুহূর্ত
আপনার ব্রাউজারে তৈরি হওয়া ক্লাউড রান ইউআরএলটি খুলুন!
- আপনার আধুনিক রেইলস ৮ অ্যাপ্লিকেশনটি গুগল ক্লাউডে লাইভ হয়েছে!
- আপনার অ্যাডমিন ক্রেডেনশিয়াল (
GOOGLE_CLOUD_ACCOUNTএবংAPP_ADMIN_PASSWORD) দিয়ে লগ ইন করুন। যদি কোনো লগইন বাটন খুঁজে না পান, তাহলে "New Post"- এ ক্লিক করুন এবং এটি প্রথমে ইউজারনেম ও পাসওয়ার্ড চাইবে:
- ‘New Post’- এ ক্লিক করুন, ‘My First Cloud Run Post’ শিরোনামে একটি আর্টিকেল লিখুন, একটি ছবি সংযুক্ত করুন এবং ‘Create Post’-এ ক্লিক করুন।
- আপনার পোস্টটি সম্পূর্ণ ফরম্যাটিং সহ প্রকাশিত হয়েছে এবং আপনার ছবিটি রেন্ডার করা হয়েছে।
- ভিজ্যুয়াল টেলিমেট্রি লক্ষ্য করুন:
- হেডার ব্যাজ:
[EPHEMERAL DB / STORAGE] 💾 Local - ছবির ওয়াটারমার্ক: 🏠 ছাপ (নিচের ডান কোণায় থাকা
127.0.0.1ক্ষণস্থায়ী চাকতির মতো ব্যাজ)।
- হেডার ব্যাজ:
🐝 লাইভ ওয়ার্কশপ হাইভ লিডারবোর্ডে যোগ দিন! আপনি যদি অনলাইনে থাকেন এবং আপনার প্রোক্টর লিডারবোর্ড দেখান, আর আপনি তাতে যোগ দিতে চান, তাহলে এখানে আপনার ক্লাউড রান URL যোগ করুন: 👉 লিডারবোর্ডে আপনার ক্লাউড রান রেজিস্টার করুন

৪. 💥 আসল সমস্যা: স্টেটলেস শক এবং "পুমা ওয়ার্কঅ্যারাউন্ড" ফাঁদ
ক্লাউড রান একটি স্টেটলেস, সার্ভারলেস প্ল্যাটফর্ম । যখন ওয়েব ট্র্যাফিক শূন্যে নেমে আসে, ক্লাউড রান খরচ বাঁচাতে কন্টেইনার ইনস্ট্যান্সের সংখ্যা শূন্যে নামিয়ে আনে। যখন একটি নতুন HTTP অনুরোধ আসে বা একটি নতুন কন্টেইনার রিভিশন ডেপ্লয় করা হয়, ক্লাউড রান একটি একেবারে নতুন, পরিষ্কার কন্টেইনার ইমেজ চালু করে।
আটকে থাকা চাকরি ব্যানার
যখন আপনি একটি সিঙ্গেল-কন্টেইনার ডিপ্লয়মেন্টে কোনো পোস্ট তৈরি করেন বা ছবি সংযুক্ত করেন, তখন রেইলস ActiveJob টাস্কগুলোকে (যেমন ছবির ডাইমেনশন বিশ্লেষণ বা মেটাডেটা ইনডেক্সিং) কিউতে যুক্ত করে। কিন্তু যেহেতু কেউ ব্যাকগ্রাউন্ড ওয়ার্কার চালাচ্ছে না, তাই আপনি এই সতর্কীকরণ ব্যানারটি দেখতে পাবেন:

লোভনীয় সমাধান: পুমা-র ভিতরে সলিড কিউ চালানো
একজন বুদ্ধিমান ডেভেলপার হয়তো বলবেন: "দাঁড়ান! রেইলস ৮ আমাদেরকে সলিড কিউ সরাসরি পুমা ওয়েব সার্ভার প্রসেসের ভেতরে চালানোর সুযোগ দেয়, শুধু একটি অপশন চালু করে দিলেই হয়।"
SOLID_QUEUE_IN_PUMA=true
পরিবেশ পরিবর্তনশীল!
চলুন ক্লাউড রান-এ এই বিকল্প সমাধানটি পরীক্ষা করে দেখি:
# Force a revision update to enable Solid Queue inside Puma
gcloud run services update blog \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars SOLID_QUEUE_IN_PUMA=true
এখন, আপনার ব্রাউজারে ফিরে যান এবং পৃষ্ঠাটি রিফ্রেশ করুন ।
💥 রাষ্ট্রহীনদের ধাক্কা:
- সুখবর: সলিড কিউ ওয়ার্কার এখন পুমা-র ভেতরে সক্রিয়! যেকোনো নতুন জব সাথে সাথেই ড্রেইন হয়ে যায়।
- ঠান্ডা ঝরনা (কৌশলটি হলো!):
- ক্লাউড রান একটি নতুন সংস্করণ স্থাপন করার কারণে, পূর্ববর্তী কন্টেইনার ইনস্ট্যান্সটি প্রতিস্থাপিত হয়েছে!
- আপনার লেখা প্রবন্ধটি এবং ডিস্কে থাকা SQLite ডাটাবেস ফাইলটি সম্পূর্ণরূপে মুছে গেছে !
- স্থাপত্যগত শিক্ষা: পুমা-র ভিতরে ব্যাকগ্রাউন্ড ওয়ার্কার চালালে মূল্যবান ওয়েব থ্রেড সিপিইউ/র্যাম খরচ হয়, এবং এটি পার্সিস্টেন্সের সমস্যার সমাধানও করে না ।
৫. স্বয়ংক্রিয় ধাপ ৩ যাচাইকরণ
আপনার ক্লাউড রান ডেপ্লয়মেন্ট এবং অ্যালার্ট হ্যান্ডলিং যাচাই করুন:
just workshop-eval 3
✨ শিক্ষা: SOLID_QUEUE_IN_PUMA মতো একক-কন্টেইনার সমাধানগুলো স্থানীয় উন্নয়নে সুবিধাজনক হলেও, আধুনিক ক্লাউড আর্কিটেকচারে:
- মিডিয়া ফাইলগুলির জন্য গুগল ক্লাউড স্টোরেজ (GCS) প্রয়োজন (ধাপ ৪)।
- রিলেশনাল ডেটা এবং জব কিউ-এর জন্য ম্যানেজড গুগল ক্লাউড এসকিউএল প্রয়োজন (ধাপ ৫)।
- ভারী ব্যাকগ্রাউন্ড কর্মীদের জন্য একটি নির্দিষ্ট সাইডকার কন্টেইনার রয়েছে (ধাপ ৬)।
৬. ধাপ ৪: ডেপ্লয় ৩ — GCS পারসিস্টেন্ট স্টোরেজ এবং POLA সতর্কতা
এই ধাপে, আমরা IAM ক্রেডেনশিয়ালস এপিআই ( iam: true ) এর মাধ্যমে স্বল্পস্থায়ী স্বাক্ষরিত ইউআরএল ব্যবহার করে ActiveStorage-কে গুগল ক্লাউড স্টোরেজ (GCS)- এ পরিবর্তন করার মাধ্যমে কন্টেইনার ডিস্ক থেকে মিডিয়া এবং ফাইল স্টোরেজকে বিচ্ছিন্ন করি।
যেকোনো কনফিগারেশন পরিবর্তন করার আগে, জেনে নিন GCS-এর সাথে যোগাযোগের জন্য Rails-এর তিনটি জিনিস প্রয়োজন:
① প্রজেক্ট আইডি + বাকেটের নাম — ব্লবগুলো কোথায় সংরক্ষণ করতে হবে তা রেইলসকে জানতে হবে। অ্যাপটি এনভায়রনমেন্ট থেকে GOOGLE_CLOUD_PROJECT পড়ে এবং ${GOOGLE_CLOUD_PROJECT}-activestorage-prod হিসেবে বাকেটের নামটি নির্ধারণ করে। ধাপ ১-এর আপনার টেরাফর্ম ইতিমধ্যেই এই বাকেটটি তৈরি করে ফেলেছে — কোনো অতিরিক্ত কাজের প্রয়োজন নেই।
২ সঠিক অনুমতিসহ একটি সার্ভিস অ্যাকাউন্ট — ক্লাউড রান কম্পিউট ইঞ্জিনের ডিফল্ট সার্ভিস অ্যাকাউন্ট ( PROJECT_NUMBER-compute@developer.gserviceaccount.com ) হিসেবে চলে। এই SA-টির বাকেটে দুটি IAM রোলের প্রয়োজন:
-
roles/storage.objectAdmin— ব্লব আপলোড এবং পরিবেশন করার জন্য -
roles/iam.serviceAccountTokenCreatorনিজেই — IAM CredentialssignBlobAPI-কে কল করতে এবং স্বল্পস্থায়ী স্বাক্ষরিত URL তৈরি করতে।
⚠️ লোভনীয় সংক্ষিপ্ত পথ —
public: true GCS চালু করার সবচেয়ে দ্রুত উপায় হলো storage.yml এ public: true সেট করা এবং বাকেটের উপর allUsers:objectViewer অনুমতি দেওয়া। ছবিগুলো সঙ্গে সঙ্গে লোড হয়, কোনো সাইনিংয়ের প্রয়োজন হয় না। এটি করবেন না। একটি পাবলিক বাকেট ব্যবহারকারীর আপলোড করা সমস্ত মিডিয়া চিরতরে উন্মুক্ত ইন্টারনেটে ফাঁস করে দেয়। আমাদের ব্লুপ্রিন্ট বাকেটটিকে ১০০% ব্যক্তিগত রাখতে iam: true ব্যবহার করে — প্রতিটি ছবির URL হলো একটি স্বাক্ষরিত, মেয়াদোত্তীর্ণ টোকেন যা IAM Credentials API দ্বারা তাৎক্ষণিকভাবে তৈরি করা হয়।
③
ACTIVE_STORAGE_SERVICE=google — এই একটিমাত্র এনভায়রনমেন্ট ভ্যারিয়েবলই রানটাইমে রেইলসকে লোকাল ডিস্ক থেকে GCS-এ পরিবর্তন করে দেয়। কোনো কোড পরিবর্তন বা অ্যাপ্লিকেশন লজিক পুনরায় ডিপ্লয় করার প্রয়োজন নেই — শুধু একটি এনভায়রনমেন্ট ভ্যারিয়েবল দিয়েই টগল করা যায়।
১. টাইম-মেশিন রিওয়াইন্ড
টাইম মেশিনটিকে দ্বিতীয় পর্যায়ে এগিয়ে দিন:
just workshop-rewind 2
blog/config/storage.yml পরীক্ষা করুন — আপনি দেখতে পাবেন যে অ্যাপটি তিনটি নামযুক্ত GCS এনভায়রনমেন্ট (dev / test / prod) সংজ্ঞায়িত করেছে, যেগুলোর সবগুলোই একই সিকিওর প্যাটার্ন ব্যবহার করছে:
<% gcp_project = ENV.fetch("GOOGLE_CLOUD_PROJECT") { Rails.application.credentials.dig(:gcs, :project) } %>
<% gcs_signer_sa = ENV.fetch("GCS_SIGNER_SA_EMAIL") { "rails-cloudrun-sa@#{gcp_project}.iam.gserviceaccount.com" } %>
google_prod: &google_prod
service: GCS
project: <%= gcp_project %>
bucket: <%= gcp_project %>-activestorage-prod # ← derived from project ID, no extra env var!
iam: true # Sign URLs via IAM Credentials signBlob API (no private key JSON required!)
gsa_email: <%= gcs_signer_sa %>
# Convenience alias — ACTIVE_STORAGE_SERVICE=google points here
google:
<<: *google_prod

২. আইএএম স্টোরেজ এবং সাইনিং অনুমতি প্রদান
নিশ্চিত করুন যে আপনার ক্লাউড রান রানটাইম পরিষেবা অ্যাকাউন্টের ইউআরএল স্বাক্ষর করার এবং অবজেক্ট আপলোড করার অনুমতি আছে:
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
# Grant Storage Object Admin
gcloud storage buckets add-iam-policy-binding gs://$GCS_BUCKET \
--member="serviceAccount:$RUN_SA" \
--role="roles/storage.objectAdmin"
# Grant Service Account Token Creator (required for IAM signed URLs)
gcloud iam service-accounts add-iam-policy-binding $RUN_SA \
--member="serviceAccount:$RUN_SA" \
--role="roles/iam.serviceAccountTokenCreator"
৩. তৃতীয়ত, GCS সংযুক্ত করে ক্লাউডে স্থাপন করুন।
GCS সক্রিয় করে পুনরায় ডিপ্লয় করুন। নিশ্চিত করুন যে আপনি blog/ ফোল্ডারের ভিতরে আছেন (অন্যথায় Cloud Run, Rails 8 Dockerfile পরিবর্তে Buildpacks ব্যবহার করে বিল্ড করার চেষ্টা করে, যার ফলে বিল্ড ব্যর্থ হয়)।
বিদ্যমান এনভায়রনমেন্ট ভেরিয়েবল (যেমন মাস্টার কী, আইএপি কনফিগারেশন ইত্যাদি) অপরিবর্তিত রাখতে --update-env-vars ( --set-env-vars নয়) ব্যবহার করুন।
cd blog
gcloud run deploy blog \
--source . \
--region $GOOGLE_CLOUD_REGION \
--update-env-vars ACTIVE_STORAGE_SERVICE=google
💡 পরামর্শ: যদি ডেপ্লয়মেন্ট ব্যর্থ হয়, তাহলে আপনার টার্মিনাল আউটপুটে সুবিধাজনকভাবে দেওয়া ক্লাউড বিল্ড লগস লিঙ্কে ক্লিক করুন (যেমনটি নিচের চিত্রে দেখানো হয়েছে)। এছাড়া, ত্রুটিটি বুঝতে ও সমাধান করতে আপনি অ্যান্টিগ্র্যাভিটির কাছেও সাহায্য চাইতে পারেন!

৪. ✨ টিকে থাকা ছবি ও মেঘের ছাপ
- আপনার ব্লগের ইউআরএল রিফ্রেশ করুন।
- 'Surviving the Cloud' শিরোনামে একটি নতুন ব্লগ পোস্ট তৈরি করুন এবং একটি ছবি আপলোড করুন।
- ছবিটির নিচের ডান কোণায় দেখুন:
- বাড়ির ছাপটা চলে গেছে!
- এটি রঙিন ক্লাউড জিসিএস স্ট্যাম্প (
nanobanana_stamp_cloud.png) দ্বারা প্রতিস্থাপিত হয়।
- কন্টেইনারটি পুনরায় চালু করতে বাধ্য করুন (যা চতুর্থ রিভিশনটি চালু করবে):
gcloud run services update blog --region $GOOGLE_CLOUD_REGION --update-env-vars RESTART_TRIGGER=$(date +%s) - পৃষ্ঠাটি রিফ্রেশ করুন: SQLite ডাটাবেস রিসেট হলেও, ইমেজ বাইনারিটি গুগল ক্লাউড স্টোরেজে সুরক্ষিত আছে ! CLI-এর মাধ্যমে যাচাই করুন:
gcloud storage ls -l -r gs://$GCS_BUCKET/
আপনার প্রোডাকশন বাকেটে ( gs://$GCS_BUCKET ) গুগল ক্লাউড স্টোরেজ ব্রাউজার কনসোলে আপনি যে আপলোড করা ব্লব এবং মিডিয়া অবজেক্টগুলো দেখতে পারেন, তার একটি তালিকা নিচে দেওয়া হলো:

৫. ⚠️ আসল কথা হলো: আটকে পড়া চাকরির সতর্কীকরণ ব্যানার
আপনি যখন ছবিটি আপলোড করেছেন, ActiveStorage মাত্রা এবং মেটাডেটা বের করার জন্য একটি বিশ্লেষণ কাজ ( ActiveStorage::AnalyzeJob ) সারিবদ্ধ করেছে।
তবে, ক্লাউড রান বর্তমানে শুধুমাত্র একটি ওয়েব কন্টেইনার ( puma ) চালাচ্ছে। রেইলস ৮-এ, সলিড কিউ ব্যাকগ্রাউন্ড জবগুলো ডেটাবেসে সংরক্ষণ করে, কিন্তু কেউ bundle exec rails solid_queue:start !` কমান্ডটি চালাচ্ছে না।
আপনার ব্লগ পেজের উপরের দিকে তাকান: আপনি blog/app/views/layouts/_check_stuck_jobs.html.erb দ্বারা রেন্ডার করা একটি উজ্জ্বল সতর্কীকরণ ব্যানার দেখতে পাবেন।
আমরা ধাপ ৬- এ এর যথাযথ সমাধান করব, যখন আমরা একটি প্রোডাকশন-গ্রেড মাল্টি-কন্টেইনার সাইডকার আর্কিটেকচারে উন্নীত হব, যেখানে একটি ডেডিকেটেড worker কন্টেইনার ওয়েব ট্র্যাফিক থেকে স্বাধীনভাবে সলিড কিউ (Solid Queue) চালাবে!
৬. স্বয়ংক্রিয় ধাপ ৪ যাচাইকরণ
আপনার GCS কনফিগারেশন এবং আটকে থাকা জবগুলির টেলিমেট্রি যাচাই করুন:
just workshop-eval 4
৭. ধাপ ৫: প্রি-ফ্লাইট চেকলিস্ট: ক্লাউড এসকিউএল, সিক্রেটস এবং ডিবি কানেক্টিভিটি
ধাপ ৩ এবং ৪-এ যখন আপনি স্টেটলেস কন্টেইনার এবং গুগল ক্লাউড স্টোরেজ আয়ত্ত করেছেন, টেরাফর্ম ততক্ষণে ব্যাকগ্রাউন্ডে আপনার প্রোডাকশন ব্যাকএন্ড তৈরির কাজ শেষ করে ফেলেছে !
আমাদের সম্পূর্ণ ৩-কন্টেইনার প্রোডাকশন সাইডকার আর্কিটেকচার স্থাপন করার আগে (ধাপ ৬-এর স্থাপন ৪), এই ধাপটি ডেটাবেসের স্বাস্থ্য, সংযোগ এবং গোপনীয় অ্যাক্সেস যাচাই করার জন্য একটি দ্রুত প্রি-ফ্লাইট পরিদর্শন হিসেবে কাজ করে।
০. বুটস্ট্র্যাপ এনভায়রনমেন্ট ভেরিয়েবল
আপনার Terraform আউটপুট থেকে সরাসরি সমস্ত প্রয়োজনীয় শেল ভেরিয়েবল এক্সপোর্ট করুন:
# Core project variables (should already be set from Step 0)
export GOOGLE_CLOUD_REGION="${GOOGLE_CLOUD_REGION:-europe-west1}"
# Compute runtime service account
export PROJECT_NUMBER=$(gcloud projects describe $GOOGLE_CLOUD_PROJECT --format="value(projectNumber)")
export RUN_SA="${PROJECT_NUMBER}-compute@developer.gserviceaccount.com"
# Cloud SQL instance name (from Terraform output, NOT hardcoded)
export SQL_INSTANCE_NAME=$(cd iac && terraform output -raw sql_instance_name 2>/dev/null || gcloud sql instances list --format='value(name)' --limit=1)
# Database password (from Terraform output)
export DB_PASSWORD=$(cd iac && terraform output -raw db_password 2>/dev/null || echo "CHANGE_ME")
# GCS bucket names
export GCS_BUCKET="${GOOGLE_CLOUD_PROJECT}-activestorage-prod"
echo "✅ Pre-flight environment variables ready:"
echo " RUN_SA=$RUN_SA"
echo " SQL_INSTANCE_NAME=$SQL_INSTANCE_NAME"
echo " GCS_BUCKET=$GCS_BUCKET"
১. ক্লাউড এসকিউএল স্বাস্থ্য পরীক্ষা
যাচাই করুন যে আপনার পরিচালিত PostgreSQL ইনস্ট্যান্সটির প্রাথমিক প্রোভিশনিং সম্পন্ন হয়েছে এবং এটি সুস্থ আছে:
echo "Checking instance: $SQL_INSTANCE_NAME"
gcloud sql instances describe $SQL_INSTANCE_NAME --format="value(state)"
আউটপুটটি অবশ্যই RUNNABLE হতে হবে।
২. (ঐচ্ছিক) আপনার ল্যাপটপ থেকে ডাটাবেস সংযোগ পরীক্ষা করা
প্রোডাকশনে, ক্লাউড রান কন্টেইনারগুলি ডাটাবেসকে পাবলিক ইন্টারনেটে ( 0.0.0.0/0 ) উন্মুক্ত না করেই একটি এনক্রিপ্টেড সাইডকার প্রক্সির ( 127.0.0.1:5432 ) মাধ্যমে ক্লাউড এসকিউএল-এর সাথে নিরাপদে সংযুক্ত হয়।
ℹ️ দ্রষ্টব্য: এই ধাপটি সম্পূর্ণ ঐচ্ছিক! ক্লাউড রান-এ, অফিসিয়াল ক্লাউড এসকিউএল অথ প্রক্সি সাইডকার কন্টেইনার স্বয়ংক্রিয়ভাবে এমটিএলএস সংযোগ পরিচালনা করে। ওয়ার্কশপটি সম্পন্ন করার জন্য আপনাকে স্থানীয়ভাবে কিছু ইনস্টল করতে হবে না ।
আপনার মেশিনে যদি cloud-sql-proxy এবং psql ইনস্টল করা থাকে এবং আপনি আপনার ল্যাপটপ থেকে সরাসরি সংযোগ যাচাই করতে চান:
# Optional: test connectivity via Google-managed mTLS proxy
gcloud sql connect $SQL_INSTANCE_NAME --user=rails_user --database=rails_production
(পাসওয়ার্ড চাওয়া হলে, প্রবেশ করান)
$DB_PASSWORD
অন্যথায়, নির্দ্বিধায় নিচের ধাপ ৩-এ চলে যান!
৩. টেরাফর্ম-প্রোভিশন করা সিক্রেটগুলো পরিদর্শন করা
কখনো গিট বা কন্টেইনার এনভায়রনমেন্ট ভেরিয়েবলে প্লেইন-টেক্সট ডাটাবেস পাসওয়ার্ড, এপিআই কী, বা রেইলস মাস্টার কী সংরক্ষণ করবেন না। আধুনিক ক্লাউড আর্কিটেকচারে, আমরা জিরো-ট্রাস্ট রানটাইম ইনজেকশনের জন্য গুগল ক্লাউড সিক্রেট ম্যানেজার ব্যবহার করি।
ℹ️ টেরাফর্ম প্রথম ধাপেই আপনার জন্য এটি করে দিয়েছে:
-
rails-master-key— রানটাইমে রেইলস ক্রেডেনশিয়াল ডিক্রিপ্ট করার জন্য। -
rails-db-password— একটি ক্রিপ্টোগ্রাফিকভাবে সুরক্ষিত র্যান্ডম পাসওয়ার্ড যা ক্লাউড SQL ব্যবহারকারীর সাথে মেলে। -
rails-admin-password— আপনার প্রাথমিক অ্যাডমিনিস্ট্রেটর ব্যবহারকারীর জন্য একটি সুরক্ষিত পাসওয়ার্ড।
সিক্রেট ম্যানেজারে আপনার গোপনীয় তথ্যগুলো উপস্থিত ও সক্রিয় আছে কিনা তা যাচাই করুন:
gcloud secrets list --filter="name:rails-"

🔑 আপনার ল্যাপটপে master.key সিঙ্ক করা হচ্ছে (লোকাল সিঙ্ক)
নিরাপত্তার কারণে blog/config/master.key ফাইলটি গিটইগনোর করা হয়েছে, তাই আপনার লোকাল ক্লোনে এটি এখনও নাও থাকতে পারে। সিক্রেট ম্যানেজার থেকে এটি পুল করার জন্য এই সহজ ও সেলফ-হিলিং কোডটি চালান, যাতে আপনার লোকাল রেইলস টুলগুলো ক্রেডেনশিয়াল ডিক্রিপ্ট করতে পারে:
if [ ! -f blog/config/master.key ]; then
echo "📥 Pulling rails-master-key from Secret Manager to local blog/config/master.key..."
gcloud secrets versions access latest --secret=rails-master-key > blog/config/master.key
echo "🔑 Created local blog/config/master.key! 🎉"
else
echo "✅ Local blog/config/master.key already exists! 🚀"
fi
🧪 খেলার মাঠ: শুধু এটা কতটা সহজ তা দেখার জন্য একটি কাস্টম সিক্রেট তৈরি করতে চান? সিক্রেট ম্যানেজার শুধু রেইলস-এর জন্য নয়! আপনি একটি সহজ CLI কমান্ডেই যেকোনো সিক্রেট তৈরি করতে পারেন:
echo -n "MySuperSecretValue123!" | gcloud secrets create workshop-fun-secret --data-file=-
echo "✨ Created workshop-fun-secret in Secret Manager! 🪄"
৪. গোপন অ্যাক্সেসর অনুমতি যাচাই করা
ক্লাউড রান কন্টেইনারগুলো যাতে বুট করার সময় এই সিক্রেটগুলোকে এনভায়রনমেন্ট ভেরিয়েবল বা ভলিউম মাউন্ট হিসেবে মাউন্ট করতে পারে, তার জন্য রানটাইম সার্ভিস অ্যাকাউন্ট ( $RUN_SA )-এর প্রতিটি সিক্রেটের উপর roles/secretmanager.secretAccessor রোলটি থাকা প্রয়োজন।
টেরাফর্ম ইতিমধ্যেই ধাপ ১-এ এটি মঞ্জুর করেছে! rails-master-key তে আইএএম পলিসি বাইন্ডিং যাচাই করুন:
# Verify that Cloud Run runtime SA has Secret Accessor role
gcloud secrets get-iam-policy rails-master-key \
--flatten="bindings[].members" \
--format="table(bindings.role,bindings.members)" \
--filter="bindings.members:$RUN_SA"
আপনার দেখা উচিত:
ROLE MEMBERS
roles/secretmanager.secretAccessor serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com

৫. স্বয়ংক্রিয় প্রাক-উড্ডয়ন যাচাইকরণ
আপনার ডাটাবেসটি রান করার যোগ্য কিনা, সিক্রেটগুলো খালি নয় কিনা, এবং IAM পারমিশনগুলো সবুজ কিনা তা নিশ্চিত করতে স্বয়ংক্রিয় ধাপ ৫ টেস্ট স্যুটটি চালান:
just workshop-eval 5
৮. ধাপ ৬: ৪টি এন্টারপ্রাইজ মাল্টি-কন্টেইনার সাইডকার স্থাপন করুন (সর্বোত্তম মান)
এখন আমরা চূড়ান্ত রেফারেন্স আর্কিটেকচারটি তৈরি করব: মাল্টি-কন্টেইনার ক্লাউড রান !
১. স্বর্ণমান পুনরুদ্ধার
আপনার রিপোজিটরি কনফিগারেশনকে তার প্রামাণিক main অবস্থায় পুনরুদ্ধার করুন:
just workshop-restore-gold
২. ৩-কন্টেইনার উৎপাদন ব্লুপ্রিন্ট পরিদর্শন
blog/compose.prod.yaml খুলুন এবং এর গঠনবিন্যাস পরীক্ষা করুন:

graph LR
Client[Internet / HTTPS Client] -->|Port 8080| Web[Web Container: Puma]
Web -->|Localhost:5432| Proxy[Sidecar: Cloud SQL Auth Proxy]
Worker[Worker Container: Solid Queue] -->|Localhost:5432| Proxy
Proxy -->|IAM mTLS Tunnel| CloudSQL[(Google Cloud SQL PostgreSQL)]
প্রোডাকশন ডেপ্লয়মেন্টে একই লোকাল নেটওয়ার্ক শেয়ার করে তিনটি সমন্বিত কন্টেইনার চলে:
-
web: পুমা-র মাধ্যমে ৮০৮০ পোর্টে HTTP ট্র্যাফিক পরিবেশন করে। -
worker: ডেডিকেটেড ব্যাকগ্রাউন্ড প্রসেসিং কন্টেইনার যাbundle exec rails solid_queue:startচালাচ্ছে। -
cloudsql-proxy: অফিসিয়াল গুগল ক্লাউড এসকিউএল অথেন্টিকেশন প্রক্সি সাইডকার (gcr.io/cloud-sql-connectors/cloud-sql-proxy:2)127.0.0.1:5432এর সাথে আবদ্ধ।
৩. ক্লাউড রান জবের মাধ্যমে ডাটাবেস মাইগ্রেশন চালানো
আমাদের নতুন ডেপ্লয়মেন্টে ওয়েব ট্র্যাফিক রাউটিং করার আগে, আমাদের অবশ্যই ক্লাউড এসকিউএল-এ ডাটাবেস মাইগ্রেশন এবং প্রাথমিক সিডিং চালাতে হবে।
⚖️ কন্টেইনারে মাইগ্রেশন চালানোর দুটি কৌশল
কন্টেইনারাইজড ক্লাউড পরিবেশে, ডাটাবেস মাইগ্রেশন সম্পাদনের জন্য দুটি প্রচলিত পদ্ধতি রয়েছে:
- বিকল্প A: ডকার এন্ট্রি পয়েন্টের ভিতরে স্বয়ংক্রিয় (কন্টেইনার বুট করার সময়
entrypoint.shdb:migrateএক্সিকিউট করে)- সুবিধা: সম্পূর্ণ স্বয়ংক্রিয়; ডেভেলপারদের কোনো কমান্ড চালানোর কথা মনে রাখতে হয় না।
- অসুবিধা: সার্ভারলেস অটোস্কেলিং-এর ক্ষেত্রে ঝুঁকিপূর্ণ! যখন উচ্চ ট্র্যাফিকের কারণে ক্লাউড রান একই সাথে ১০টি ইনস্ট্যান্সে স্কেল আপ করে, তখন ১০টি কন্টেইনার একযোগে মাইগ্রেশন চালানোর জন্য প্রতিযোগিতা করে, যার ফলে ডাটাবেস লকিং, স্কিমা রেস কন্ডিশন বা বুট টাইমআউট হতে পারে।
- বিকল্প বি: ক্লাউড রান জবের মাধ্যমে সুস্পষ্ট (আমাদের স্থাপত্যগত পছন্দ)
- সুবিধা: সম্পূর্ণ নিয়ন্ত্রণ, লেনদেনের নিরাপত্তা এবং কোনো রেস কন্ডিশন নেই। এটি ওয়েব ট্র্যাফিক পরিবেশন থেকে সম্পূর্ণ বিচ্ছিন্ন একটি ডেডিকেটেড ওয়ান-শট কন্টেইনার টাস্ক হিসেবে চলে। মাইগ্রেশন ব্যর্থ হলেও ওয়েব পরিবেশন প্রভাবিত হয় না।
- অসুবিধা: ডেপ্লয়মেন্টের আগে একটি CLI জব বা CI/CD স্টেপ চালু করার প্রয়োজন হয়।
সর্বোচ্চ উৎপাদন নির্ভরযোগ্যতার জন্য আমরা অপশন বি বেছে নিই।
⚠️ গুরুত্বপূর্ণ: DATABASE_URL ফরম্যাট এবং রুবি ৩.৪.৫ RFC ৩৯৮৬-এর কঠোরতা
ক্লাউড রান জবস নেটিভ ইউনিক্স সকেট প্রক্সি ( /cloudsql/PROJECT:REGION:INSTANCE ) ব্যবহার করে ক্লাউড এসকিউএল-এর সাথে সংযোগ স্থাপন করে। DATABASE_URL এ অবশ্যই ট্রিপল-স্ল্যাশ ফরম্যাট ব্যবহার করতে হবে:
postgresql:///rails_production?user=rails_user&password=PASS&host=/cloudsql/PROJECT:REGION:INSTANCE
# Core environment variables for database migration & initial admin seed
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
echo "Admin user will be seeded for: $GOOGLE_CLOUD_ACCOUNT"
# Get the latest blog image from Cloud Run (jobs don't support --source)
export BLOG_IMAGE=$(gcloud run services describe blog --region=$GOOGLE_CLOUD_REGION --format='value(spec.template.spec.containers[0].image)')
# Build the DATABASE_URL in triple-slash format (required for Unix socket proxy)
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_CONNECTION="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
export DB_URL="postgresql:///rails_production?user=rails_user&password=${DB_PASSWORD}&host=/cloudsql/${CLOUDSQL_CONNECTION}"
# Create migration job — note ALL 4 database URL env vars and GOOGLE_CLOUD_ACCOUNT for seed user creation
gcloud run jobs create rails-migrate \
--image=$BLOG_IMAGE \
--command "bin/rails" \
--args "db:prepare" \
--set-env-vars="DATABASE_URL=${DB_URL},DATABASE_QUEUE_URL=${DB_URL},DATABASE_CACHE_URL=${DB_URL},DATABASE_CABLE_URL=${DB_URL},GOOGLE_CLOUD_PROJECT=${GOOGLE_CLOUD_PROJECT},GOOGLE_CLOUD_ACCOUNT=${GOOGLE_CLOUD_ACCOUNT}" \
--set-secrets="RAILS_MASTER_KEY=rails-master-key:latest" \
--set-cloudsql-instances="${CLOUDSQL_CONNECTION}" \
--service-account=$RUN_SA \
--region $GOOGLE_CLOUD_REGION 2>/dev/null || true
# Execute migration job
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
💡 রেইলস ৮ মাল্টি-ডাটাবেস: অ্যাপটি প্রতিটি database.yml জন্য ৪টি ডাটাবেস (প্রাইমারি, কিউ, ক্যাশ, কেবল) ব্যবহার করে। ৪টি DATABASE_*_URL এনভায়রনমেন্ট ভ্যারিয়েবলকেই অবশ্যই একই ক্লাউড SQL ইনস্ট্যান্সকে নির্দেশ করতে হবে, অন্যথায় সলিড কিউ/ক্যাশ/কেবল টেবিলগুলো তৈরি হবে না।
db:prepare পরে, Solid Queue/Cache/Cable স্কিমাগুলো লোড করুন:
# Schema load for queue, cache, and cable databases (creates solid_queue_jobs, etc.)
gcloud run jobs update rails-migrate \
--command "bash" \
--args "-c,DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bin/rails db:schema:load:queue db:schema:load:cache db:schema:load:cable" \
--region $GOOGLE_CLOUD_REGION
gcloud run jobs execute rails-migrate --region $GOOGLE_CLOUD_REGION --wait
উপরের চিত্রে, আপনি একটি সম্ভাব্য আউটপুট দেখতে পাচ্ছেন।
gcloud run jobs executions describe $JOBNAME
গুগল ক্লাউড রান জবস কনসোল দেখাচ্ছে
rails-migrate
মৃত্যুদণ্ড কার্যকর সফল হয়েছে
৪. চতুর্থ ডেপ্লয়: ডকার কম্পোজের মাধ্যমে মাল্টি-কন্টেইনার ক্লাউড রান ডেপ্লয় করা
এখন compose.prod.yaml থেকে সরাসরি আসল ৩-কন্টেইনারের প্রোডাকশন স্ট্যাকটি ( web , worker , cloudsql-proxy ) ডিপ্লয় করুন:
# 1. Self-healing environment exports (guarantees no missing master key or DB password)
export RAILS_MASTER_KEY="${RAILS_MASTER_KEY:-$(cat config/master.key 2>/dev/null || gcloud secrets versions access latest --secret=rails-master-key)}"
export DB_PASSWORD=$(gcloud secrets versions access latest --secret=rails-db-password)
export CLOUDSQL_INSTANCE_CONNECTION_NAME="${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME}"
# 2. Database credentials & GCS attachments
export DB_USER="rails_user"
export DB_NAME="rails_production"
export GCS_BUCKET="${GCS_BUCKET}"
export ACTIVE_STORAGE_SERVICE="google"
export GOOGLE_CLOUD_ACCOUNT="${GOOGLE_CLOUD_ACCOUNT:-$(gcloud config get-value account)}"
export GEMINI_API_KEY="${GEMINI_API_KEY:-dummy-key}"
# 3. Deploy the full multi-container sidecar architecture to Cloud Run
gcloud run compose up compose.prod.yaml \
--region=$GOOGLE_CLOUD_REGION \
--allow-unauthenticated
গুগল ক্লাউড রান কনসোল: 'কন্টেইনার' ট্যাবে তিনটি সাইডকার কন্টেইনারই প্রদর্শিত হচ্ছে (
cloudsql-proxy
,
web
,
worker
)
💡 UI সংক্রান্ত কৌতূহল / কনসোলের ত্রুটি: কনসোল ড্রপডাউনে, আপনি হয়তো লক্ষ্য করবেন যে ক্লাউড রান উপরের এন্ট্রিটিকে Ingress container: cloudsql-proxy হিসেবে চিহ্নিত করছে। চিন্তা করবেন না! এটি গুগল ক্লাউড কনসোলের (যা #149- এ ট্র্যাক করা হয়েছে) কন্টেইনার নির্ভরতার ক্রমের কারণে সৃষ্ট একটি নিছক ভিজ্যুয়াল UI ত্রুটি। বাস্তবে, web হলো একমাত্র কন্টেইনার যা HTTP পোর্ট ৮০৮০-তে লিসেন করছে এবং ১০০% ইনগ্রেস ওয়েব ট্র্যাফিক পরিচালনা করে, অন্যদিকে cloudsql-proxy শুধুমাত্র পোর্ট ৫৪৩২-এ প্রাইভেট PostgreSQL ট্র্যাফিক পরিবেশন করে!
৫. ✨ বিস্ময়কর মুহূর্ত: কোনো সতর্কতা ছাড়াই ক্লাউডে সম্পূর্ণ ডেটা স্থায়িত্ব!
একটি গভীর শ্বাস নিন এবং আপনার স্ক্রিনের দিকে তাকিয়ে অবাক হয়ে দেখুন: সব সতর্কীকরণ ব্যানার উধাও হয়ে গেছে! 🪄
The production Rails 8 blog: pristine UI, zero warning banners, and persistent Cloud SQL state!
The application is now officially Production-Grade & Cloud-Native :
- Backed by managed Google Cloud SQL (PostgreSQL) via secure mTLS tunneling.
- Media safely stored in private Google Cloud Storage with IAM blob signing.
- Zero ephemeral storage warnings and zero stuck jobs alerts.
Open your Cloud Run URL:
- Look at the telemetry badges:
-
[CLOUD PERSISTENT 🐘 ☁️]is verified in your database seed! - Both Google Cloud Run and GCS badges in the footer shine green!
- The stuck jobs warning banner is completely gone !
-
- Create blog posts and comments.
- Restart or redeploy as many times as you like: your data, posts, comments, and assets survive forever in Cloud SQL and GCS!
6. Automated Step 6 Validation
Verify the multi-container configuration:
just workshop-eval 6
9. Step 7: Generative AI Pipelines, Podcastifier & The GCS Treasure Hunt 🏴☠️
With Solid Queue running in a dedicated container and Google Cloud Storage active, we can unleash asynchronous Generative AI!
১. ন্যানোবানানা ভিন্টেজ কভার জেনারেটর (ব্যবহারের জন্য প্রস্তুত জেনএআই পাইপলাইন)

Good news: this functionality is already fully implemented in the codebase! You don't need to write any code — your task here is to trigger and observe an asynchronous cloud GenAI pipeline in action.
🧠 নেপথ্যে কীভাবে কাজ করে
- Authentication: Zero API keys required! It uses Application Default Credentials (
roles/aiplatform.useron Vertex AI) automatically via the Cloud Run runtime service account. (Alternatively, ifGEMINI_API_KEYis provided, it calls the Gemini API directly). - The Pipeline: When an article is created without a cover image, an
after_commitcallback enqueuesGenerateCoverImageJobinto Solid Queue . - Model: It prompts Google's multimodal model ( Gemini 2.5 Flash Image / Imagen on Vertex AI) to generate an authentic vintage 1960s Italian film poster ( "Locandina di un film 1960" ) complete with a cameo banana and a shiny ruby "8".
- Dynamic Provenance Watermark: Uses
libvipsto dynamically stamp a visual badge on the corner (colorful cloud stamp on GCS). - Turbo Stream Hot-Swap: When the image is attached,
Turbo::StreamsChannel.broadcast_refresh_to(post)updates the reader's browser live without a manual page reload!

📐 View Mermaid Sequence Diagram Source
sequenceDiagram
autonumber
actor User as 👤 Reader / Author
participant Web as 🌐 Web (Puma)
participant DB as 🐘 Cloud SQL (Solid Queue)
participant Worker as ⚙️ Worker (Solid Queue)
participant Vertex as 🧠 Vertex AI (Gemini Flash / Imagen)
participant GCS as ☁️ Google Cloud Storage
User->>Web: POST /posts (Title & Body, No Cover)
Web->>DB: 1. Save Post & Enqueue GenerateCoverImageJob
Web-->>User: Redirect to Post (turbo_stream subscribed)
Worker->>DB: 2. Dequeue GenerateCoverImageJob
Worker->>Vertex: 3. Prompt for 1960s Italian Poster (ADC auth)
Vertex-->>Worker: 4. Raw Image Buffer
Worker->>Worker: 5. Stamp Provenance Watermark (libvips)
Worker->>GCS: 6. Attach Blob via ActiveStorage (IAM signBlob)
Worker->>Web: 7. Turbo::StreamsChannel.broadcast_refresh_to(post)
Web-->>User: ⚡ Live UI Hot-Swap: Poster magically appears!
🧪 Try It Live!
You can trigger it in two ways:
- Option A (Web UI): Click New post , enter a title like "Serverless Architecture with Ruby on Rails 8" , write some content, and leave the cover image file input empty . Click Create Post .
- Option B (CLI Runner): Run from your terminal:
bin/rails runner 'Post.create!(title: "Serverless Architecture with Ruby on Rails 8", body: "Exploring asynchronous AI pipelines with Solid Queue and NanoBanana!")'
Wait ~15–30 seconds: watch the Solid Queue worker logs in Cloud Run. The vintage poster will appear directly on your post!
📸 TODO(riccardo): add screenshot of a blog post with an AI-generated vintage 1960s Italian movie poster featuring a cameo banana and ruby 8
2. The Bilingual Podcastifier Quest (Hands-On Antigravity Coding Exercise)
⚠️ Important Notice: Unlike the Cover Generator above, this feature is NOT yet implemented!
If you open blog/app/jobs/podcastifier_job.rb , you will find an empty scaffold stub:
class PodcastifierJob < ApplicationJob
queue_as :default
def perform(post_id)
# TODO(student): COMPLETE_ME — Pair program with Google Antigravity to implement
# Italian TTS podcast generation using voice 'it-IT-Wavenet-A' via Cloud TTS / ADC!
end
end
Your mission in this hands-on exercise is to pair-program with Google Antigravity to complete this feature:
- Prompt Antigravity: "Help me implement PodcastifierJob in
app/jobs/podcastifier_job.rb. It should fetch the Post, translate or synthesize an Italian audio overview using Google Cloud Text-to-Speech (voice 'it-IT-Wavenet-A' with language code 'it-IT' via ADC credentials), and attach the audio file topost.podcast_audio_itusing ActiveStorage!" - Add Player to View: In
app/views/posts/show.html.erb, add an HTML5player when@post.podcast_audio_it.attached?. - Trigger Synthesis: When saved, Solid Queue will execute the text-to-speech job asynchronously in the background.

🎧 Sample Podcastifier Outputs:
💡 Need Help or Got Stuck? A complete, battle-tested reference solution is available on branch solutions/podcastifier . You can inspect it anytime with git diff solutions/podcastifier .
3. [OPTIONAL] 🏴☠️ The GCS Treasure Hunt (Console Blob Recovery)
ℹ️ Prerequisite Check: Local
cloud-sql-proxy
CLI Required This optional exercise connects your local laptop directly to Google Cloud SQL via an encrypted mTLS tunnel. Check if you have the CLI proxy installed:
which cloud-sql-proxy
- If installed: Proceed below!
- If NOT installed and you want to do this quest: Install it with:
# Linux (x86_64) curl -o /tmp/cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.14.0/cloud-sql-proxy.linux.amd64 chmod +x /tmp/cloud-sql-proxy && sudo mv /tmp/cloud-sql-proxy /usr/local/bin/cloud-sql-proxy # macOS (Homebrew) brew install cloud-sql-proxy - Otherwise: Feel free to skip to the Step 7 automated validation!
Remember that photo you uploaded back in Step 4 before the container restart wiped out the ephemeral SQLite database? That image file is still sitting safely in your private GCS bucket as an "orphaned blob"!
Let's use the Rails console to rescue it and attach it to a Cloud SQL post:
# Connect to your production database via Rails console and Cloud SQL Proxy
cloud-sql-proxy --port 5432 ${GOOGLE_CLOUD_PROJECT}:${GOOGLE_CLOUD_REGION}:${SQL_INSTANCE_NAME} &
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production bin/rails c
Inside the Rails console:
# 1. Search for orphaned GCS blobs in ActiveStorage
orphan_blobs = ActiveStorage::Blob.where.missing(:attachments)
puts "🏴☠️ Found #{orphan_blobs.count} orphaned GCS blobs!"
# 2. Attach the surviving blob to your latest post
if orphan_blobs.any?
post = Post.last
post.cover_image.attach(orphan_blobs.first)
post.save!
puts "🎉 Rescued blob #{orphan_blobs.first.filename} attached to '#{post.title}'!"
end
exit
📸 TODO(riccardo): add screenshot of the interactive Rails console executing the GCS Treasure Hunt snippet and rescuing orphaned ActiveStorage blobs
Refresh your blog: the photo uploaded during Step 4's Stateless Shock is resurrected and permanently attached to your Cloud SQL post!
4. Automated Step 7 Validation
Verify GenAI jobs and assets:
just workshop-eval 7
10. Step 8: Choose Your Own Adventure / Advanced Quests 🏆
Now that you have mastered the canonical reference architecture, choose your graduation quest!
🛡️ Quest 1: Zero-Trust Google IAP (Identity-Aware Proxy)
- Difficulty: Medium (Enterprise Security)
- The Goal: Lock down your Cloud Run application so only your verified Google / Gmail accounts can access it, eliminating password login entirely!
- How it Works:
- An External HTTPS Application Load Balancer terminates Google OAuth and verifies identity before traffic ever reaches Cloud Run.
- Google forwards verified identity headers (
X-Goog-Authenticated-User-Email). - Rails automatically logs in the verified Google identity via
blog/app/controllers/concerns/iap_authenticatable.rb:module IapAuthenticatable extend ActiveSupport::Concern included { before_action :authenticate_via_iap } private def authenticate_via_iap return unless (raw = request.headers["X-Goog-Authenticated-User-Email"]).present? email = raw.sub(/^accounts\.google\.com:/, "") user = User.find_or_create_by!(email_address: email) { |u| u.password = SecureRandom.hex(16) } start_new_session_for(user) unless authenticated? end end - Enable the Terraform module in
iac/iap.tfwithenable_iap = trueand specify your allowed Google accounts:iap_allowed_users = [var.google_cloud_account]

📊 Quest 2: Production SRE Telemetry & Cloud Logging
- Difficulty: Medium (Observability)
- The Goal: Stream structured JSON application logs with trace correlation IDs directly to Google Cloud Logging and catch production exceptions in real-time with Cloud Error Reporting.
- How it Works:
- Configure
blog/config/environments/production.rbto emit structured JSON logs with GCP trace labels. - Trigger an intentional test exception and watch Google Cloud Error Reporting group and notify you instantly.
- Configure
📸 TODO(riccardo): add screenshot of Google Cloud Error Reporting showing grouped production exceptions and Cloud Logging structured JSON payload with trace labels
🧠 Quest 3: pgvector Semantic Search & Gemini RAG
- Difficulty: Hard (GenAI Capstone)
- The Goal: Search articles conceptually using vector embeddings stored in PostgreSQL on Cloud SQL.
- How it Works:
- Enable the
vectorextension on your Cloud SQL PostgreSQL instance:CREATE EXTENSION IF NOT EXISTS vector; - Add the
neighborgem toblog/Gemfileand generate text embeddings with Gemini (text-embedding-004) onPost#after_save. - Perform cosine distance queries (
<=>) to power a semantic search bar with Turbo Streams!
- Enable the
📸 TODO(riccardo): add screenshot of the live blog semantic search bar returning conceptual matches with cosine similarity scores
⚡ Quest 4: SEO & Performance Audit Assistant
- Difficulty: Easy / Fun (Developer Experience)
- The Goal: Audit Core Web Vitals, Largest Contentful Paint (LCP), and Flesch-Kincaid / Fog readability indices using Antigravity and Speedgrapher tools.
📸 TODO(riccardo): add screenshot of Speedgrapher / Lighthouse SEO and Core Web Vitals audit summary
🤔 Curiosity Box: Getting a Live Rails Console on Cloud Run
💡 The Question: On traditional VMs or Heroku-style PaaS platforms, developers commonly SSH into a running instance or use heroku run rails console to inspect or fix production data live. What is the Cloud Run-native equivalent, and how do you harden it?
কেন
gcloud run exec
doesn't exist for Cloud Run Services
Cloud Run is a serverless platform: your container may be running as zero, one, or hundreds of identical instances, with no stable identity or addressable shell. This is fundamentally different from Kubernetes ( kubectl exec ), where a specific pod has a named socket you can attach to.
# ❌ This does NOT exist for Cloud Run Services:
gcloud run services exec blog --region europe-west1 -- rails console
# ✅ This exists for Cloud Run JOBS (non-interactive, one-shot tasks):
gcloud run jobs execute my-job --region europe-west1 --wait
The Four GCP-Native Patterns
প্যাটার্ন | আদেশ | Interactive? | নিরাপত্তা |
1. Local proxy + remote DB | | ✅ Full | 🔒 Cloud SQL Auth Proxy mTLS |
2. Cloud Run Job (one-shot) | | ❌ Script only | 🔒 IAM role required |
3. Web Console gem + IAP | Browser → | ✅ Full | 🛡️ IAP Zero-Trust |
4. Cloud Shell + Proxy | Cloud Shell → | ✅ Full | 🔒 Google identity |
Pattern 1 (Recommended for data inspection): Run a local Rails console that talks directly to your Cloud SQL PostgreSQL via the Cloud SQL Auth Proxy:
# In one terminal: start the auth proxy
cloud-sql-proxy rails8-fl-20260907b-xm8ko4:europe-west1:your-instance
# In another terminal: open a read-write or sandbox console
DATABASE_URL=postgresql://rails_user:${DB_PASSWORD}@127.0.0.1:5432/rails_production \
bin/rails console --sandbox
You get full IRB interactivity with zero network exposure — the Cloud SQL Proxy uses mTLS over a Unix domain socket or loopback.
Pattern 3 (Web Console + IAP Zero-Trust, the hardened browser path):
The web-console gem provides a browser-based IRB session. Without protection it is a critical security vulnerability — never expose it publicly. But combined with Google IAP (Quest 1), the Attack surface collapses: only verified Google identities on your allowlist can reach the /rails/console endpoint.
# Gemfile — only load in development/staging, never production without IAP!
gem "web-console", group: :development
# config/environments/production.rb — if IAP is enabled:
config.web_console.allowed_ips = ["0.0.0.0/0"] # IAP terminates auth before us
⚠️ Pattern 3 requires Quest 1 (IAP) to be enabled first. Without it, /rails/console on a public URL is equivalent to leaving your server room unlocked. With IAP, it becomes a powerful operator tool hardened by Google-grade Zero-Trust.
Ask Antigravity: "Show me how to run
rails console --sandbox
against my Cloud SQL production database safely using the Cloud SQL Auth Proxy."
🏆 Graduating on The Hive: Proctor-Validated Proof-of-Work
Once you have completed your chosen quest, claim your Step 8 Graduation Trophy 🏆 on The Hive Leaderboard :
- Submit Your Quest on GitHub :
- Open a new Issue on the official repository: github.com/palladius/rails8-app-on-gcp/issues/new
- Title format:
🎓 [Step 8 Completed]: - In the body, include a brief description of what you accomplished and a link to your live Cloud Run deployment.
- Connect Your Cloud Run App to Your Issue :
- Note your Issue number (eg
88or the full URLhttps://github.com/palladius/rails8-app-on-gcp/issues/88). - Inject the
STEP_8_GHIenvironment variable into your Cloud Run service:gcloud run services update blog \ --region europe-west1 \ --update-env-vars STEP_8_GHI=<YOUR_ISSUE_NUMBER> - Verify that your
/status.jsonendpoint now reports your quest: You should see:curl -s https://<YOUR_APP>.run.app/status.json | jq .quest{ "step_8_completed": true, "ghi_issue": 88, "ghi_url": "https://github.com/palladius/rails8-app-on-gcp/issues/88" }
- Note your Issue number (eg
- Get Your Proctor Review & Unlock the Golden Trophy :
- Check The Hive Leaderboard : your row will now display an amber badge:
⏳ GHI #XX review pending. - Ask workshop proctors ( @palladius , @emilianodellacasa , or @ricc ) to inspect your deployment.
- As soon as a proctor comments
LGTMon your issue, The Hive will automatically elevate your progress to 8/8 🏆 with a golden/purple glowing badge and a permanent trophy linking directly to your capstone issue!
- Check The Hive Leaderboard : your row will now display an amber badge:
11. Conclusion and Clean Up 🎓
Congratulations! 🎉
You have built and deployed a production-grade, enterprise-ready Rails 8 application on Google Cloud Platform:
- Zero-Branch Progression: Mastered modern workflows with Time-Machine overlays on
main. - Cloud Storage: Scalable, private object storage with IAM blob signing (
iam: true). - Cloud SQL: Managed PostgreSQL secured with Cloud SQL Auth Proxy mTLS tunnels.
- Secret Manager: Zero plain-text credentials or
.envfile leaks. - Cloud Run Multi-Container: Isolated Puma web, Solid Queue worker, and proxy sidecars.
- Generative AI: Asynchronous vintage poster generation and TTS podcast synthesis with Vertex AI.
- Quests: Zero-Trust IAP, SRE Observability, and pgvector embeddings!
🧹 Resource Clean Up
To avoid ongoing charges after completing the workshop, make sure to clean up your Google Cloud resources! Choose the approach that best fits your environment:
Option 1: Terraform Teardown (Recommended / Default 🟢)
Use this option if you are using an existing or shared Google Cloud project where other resources or data live. It surgically destroys only the resources provisioned during this workshop (Cloud Run, Cloud SQL, Secret Manager, GCS buckets), leaving the rest of your project untouched.
cd iac
terraform destroy -auto-approve
Option 2: Total Project Deletion (Leave Zero Trace 🌪️)
Use this option if you created a dedicated workshop project (eg, using workshop credits or a sandbox) and want to guarantee that zero trace remains—including logs, metadata, service accounts, and billing links.
gcloud projects delete $GOOGLE_CLOUD_PROJECT --quiet