<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Rashika Karki</title>
    <description>The latest articles on Forem by Rashika Karki (@rashikakarki).</description>
    <link>https://forem.com/rashikakarki</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F436450%2Fafc8ca54-a8b2-480f-abdf-830ddff780bd.png</url>
      <title>Forem: Rashika Karki</title>
      <link>https://forem.com/rashikakarki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/rashikakarki"/>
    <language>en</language>
    <item>
      <title>Building LearnForge: Multi-Agent AI Learning Platform on Cloud Run with Google ADK</title>
      <dc:creator>Rashika Karki</dc:creator>
      <pubDate>Sat, 08 Nov 2025 21:55:23 +0000</pubDate>
      <link>https://forem.com/rashikakarki/building-learnforge-multi-agent-ai-learning-platform-on-cloud-run-with-google-adk-m4b</link>
      <guid>https://forem.com/rashikakarki/building-learnforge-multi-agent-ai-learning-platform-on-cloud-run-with-google-adk-m4b</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note: I wrote this article for the Google Cloud Run Hackathon 2025.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I built &lt;strong&gt;LearnForge&lt;/strong&gt;, an AI-powered learning platform that does something no other platform does: it has a conversation with you, figures out what you actually want to learn (not what you think you want), researches the topic in real-time, and then creates a personalized learning journey that adapts to how you learn.&lt;/p&gt;

&lt;p&gt;Built on &lt;strong&gt;Google Cloud Run&lt;/strong&gt; and &lt;strong&gt;Agent Development Kit (ADK)&lt;/strong&gt; with 12 specialized AI agents working together, it handles everything from vague "I want to learn AI" statements to structured, adaptive learning missions that remember where you left off—even if you come back weeks later.&lt;/p&gt;

&lt;p&gt;The result? A learning experience that feels like having a personal tutor who never forgets, never gets tired, and actually knows what they're talking about.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem: Why Online Learning is Hard (And How I Fixed It)
&lt;/h2&gt;

&lt;p&gt;Let me paint you a picture. You're sitting at your computer, motivated, ready to learn something new. You type "learn Python" into a course platform. You get 47 courses. You pick one. Three hours later, you realize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You're learning about data types when you wanted to build web APIs&lt;/li&gt;
&lt;li&gt;The instructor assumes you know nothing, but you've coded in JavaScript for years&lt;/li&gt;
&lt;li&gt;The examples are boring and don't relate to what you actually want to build&lt;/li&gt;
&lt;li&gt;You're on lesson 12 of 200, and you have no idea if you're learning the right things&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sound familiar? This is the fundamental problem with online learning: &lt;strong&gt;one-size-fits-all doesn't fit anyone&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Problem
&lt;/h3&gt;

&lt;p&gt;Here's what's broken:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. The Goal Mismatch&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When someone says "I want to learn AI," they could mean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building chatbots for customer service&lt;/li&gt;
&lt;li&gt;Fine-tuning language models for text summarization&lt;/li&gt;
&lt;li&gt;Understanding neural network architectures&lt;/li&gt;
&lt;li&gt;Creating computer vision systems for medical imaging&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But traditional platforms give everyone the same "Introduction to AI" course. It's like asking "Where do you want to go?" and getting the same map regardless of whether you said "the grocery store" or "Mount Everest."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Experience Gap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A beginner needs hand-holding. An intermediate learner needs to skip basics. An expert needs advanced concepts. But courses are static, they can't adapt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The Context Loss&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You start a course on Monday. Life happens. You come back on Friday. The platform has no idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What you were learning&lt;/li&gt;
&lt;li&gt;How well you understood it&lt;/li&gt;
&lt;li&gt;Where you left off&lt;/li&gt;
&lt;li&gt;What you were confused about&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. The Content Problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most courses are pre-recorded, static content. But learning is dynamic. You might need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A different explanation when you're confused&lt;/li&gt;
&lt;li&gt;Real-world examples relevant to your goals&lt;/li&gt;
&lt;li&gt;Video tutorials when you're a visual learner&lt;/li&gt;
&lt;li&gt;Step-by-step guides when you prefer structured learning&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution: AI That Actually Understands
&lt;/h3&gt;

&lt;p&gt;What if, instead of picking from a catalog, you could just... talk to an AI?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You&lt;/strong&gt;: "I want to learn AI"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI&lt;/strong&gt;: "Great! I checked what's trending in AI learning. Most people start with either model training, data pipelines, or building AI-powered applications. Which direction do you want to focus on?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You&lt;/strong&gt;: "Building applications, I think"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI&lt;/strong&gt;: "Got it. Are you thinking of chatbots, image generators, or recommendation systems?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You&lt;/strong&gt;: "Chatbots"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI&lt;/strong&gt;: "Perfect! So you want to learn &lt;strong&gt;AI chatbot development&lt;/strong&gt;, at an &lt;strong&gt;intermediate&lt;/strong&gt; level, focusing on &lt;strong&gt;building real applications&lt;/strong&gt;. Does that sound right?"&lt;/p&gt;

&lt;p&gt;And then here's the magic the AI:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Researches the topic using Google Search&lt;/li&gt;
&lt;li&gt;Creates a personalized learning mission with checkpoints&lt;/li&gt;
&lt;li&gt;Guides you through it conversationally&lt;/li&gt;
&lt;li&gt;Adapts content based on your responses&lt;/li&gt;
&lt;li&gt;Remembers everything, even if you come back weeks later&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This isn't a chatbot. This is a &lt;strong&gt;multi-agent AI system&lt;/strong&gt; that coordinates 12 specialized agents to create a complete learning experience.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works: The Magic Behind the Scenes
&lt;/h2&gt;

&lt;p&gt;LearnForge uses Google's Agent Development Kit (ADK) to orchestrate 12 specialized AI agents. Think of it like a well-coordinated team where each agent has a specific job, but to the user, it feels like talking to one intelligent tutor.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 1: Mission Creation (Meet Polaris)
&lt;/h3&gt;

&lt;p&gt;When you first connect, you meet &lt;strong&gt;Polaris&lt;/strong&gt;, the Pathfinder. Polaris doesn't just ask questions it researches your topic in real-time to ask intelligent, informed questions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You: "I want to learn machine learning"

Polaris: [Searches Google for "machine learning learning paths 2025"]
         [Finds that most people focus on: model training, data prep, or deployment]

         "When people explore 'machine learning,' they often focus on 
         model training, data prep, or deployment. Which of these do 
         you want to master first?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Behind the scenes, Polaris uses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pathfinder Agent&lt;/strong&gt;: Conversational goal clarification with research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Search Agent&lt;/strong&gt;: Google Search API for real-time topic research&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mission Curator Agent&lt;/strong&gt;: Converts your goals into structured learning missions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? Instead of a generic "Machine Learning 101" course, you get a mission tailored to your specific goal: "Building Production ML Systems with Python and TensorFlow" or "Data Preparation for Machine Learning Models."&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Learning Execution (Meet Lumina)
&lt;/h3&gt;

&lt;p&gt;Once your mission is created, &lt;strong&gt;Lumina&lt;/strong&gt; takes over. Lumina is your personal learning companion patient, adaptive, and genuinely helpful.&lt;/p&gt;

&lt;p&gt;Lumina guides you through checkpoints (bite-sized learning goals) using a sophisticated multi-agent system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Lumina Orchestrator (invisible coordinator)
    ├── Greeter: "Welcome! Let's start your journey..."
    ├── Flow Briefer: "Next up: Understanding Neural Networks. Ready?"
    ├── Sensei (your teacher)
    │   ├── Content Composer
    │   │   ├── Content Searcher: Finds educational articles via Google Search
    │   │   ├── Video Selector: Curates YouTube videos (4-20 min, educational)
    │   │   └── Content Formatter: Personalizes content for your learning style
    │   └── Evaluates your understanding
    ├── Help Desk: Answers off-topic questions
    └── Wrapper: Celebrates your completion
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what makes this special:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Content is Generated in Real-Time&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When Sensei teaches you about "neural networks," it doesn't pull from a static database. Instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content Searcher finds the latest, most relevant articles&lt;/li&gt;
&lt;li&gt;Video Selector curates educational YouTube videos (filtered by duration, category, relevance)&lt;/li&gt;
&lt;li&gt;Content Formatter adapts everything to your learning style (visual? examples? step-by-step?)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. It Adapts to Your Understanding&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Sensei: "Can you explain how backpropagation works?"

You: "It's like... adjusting weights based on errors?"

Sensei: "You're on track with the error part! Let me clarify the 
        weight adjustment mechanism..."
        [Delegates to Content Composer for a clearer explanation]
        [Presents it naturally, as if Sensei knew it all along]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. It Remembers Everything&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is where it gets interesting. Most chatbots lose context when you close the browser. LearnForge uses &lt;strong&gt;Cloud SQL with DatabaseSessionService&lt;/strong&gt; to persist everything:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which checkpoint you're on&lt;/li&gt;
&lt;li&gt;What content was presented&lt;/li&gt;
&lt;li&gt;How you responded to questions&lt;/li&gt;
&lt;li&gt;What you were confused about&lt;/li&gt;
&lt;li&gt;Your learning preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Close your browser, come back next week, switch devices it all just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: How 12 Agents Work Together Seamlessly
&lt;/h2&gt;

&lt;p&gt;The technical magic is in the orchestration. LearnForge uses Google ADK's hierarchical agent system to coordinate specialized agents without the user ever knowing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Silent Orchestration: The Invisible Hand
&lt;/h3&gt;

&lt;p&gt;The orchestrators are completely invisible. Users never see messages like "Let me hand you over to the Sensei..." Instead, transitions are seamless:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# What the user sees:
&lt;/span&gt;&lt;span class="n"&gt;Sensei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s explore neural networks! Here&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s how they work...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# What's actually happening:
&lt;/span&gt;&lt;span class="n"&gt;Orchestrator&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;delegates&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Sensei&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Sensei&lt;/span&gt; &lt;span class="n"&gt;delegates&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="n"&gt;Composer&lt;/span&gt; 
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="n"&gt;Composer&lt;/span&gt; &lt;span class="n"&gt;chains&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Searcher&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Video&lt;/span&gt; &lt;span class="n"&gt;Selector&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Formatter&lt;/span&gt;
&lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Content&lt;/span&gt; &lt;span class="n"&gt;flows&lt;/span&gt; &lt;span class="n"&gt;back&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="n"&gt;Sensei&lt;/span&gt; &lt;span class="n"&gt;presents&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="n"&gt;naturally&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The orchestrator's instruction is explicit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;root_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    YOU MUST NEVER TALK TO THE USER DIRECTLY.
    YOU MUST NEVER ACKNOWLEDGE DELEGATIONS.
    The user should ONLY see responses from sub-agents.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Content Authority Separation: Why Teaching Agents Don't Generate Content
&lt;/h3&gt;

&lt;p&gt;Here's an insight that improved content quality dramatically: &lt;strong&gt;teaching agents shouldn't generate content they should delegate to specialized agents.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;sensei_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    YOU ARE FORBIDDEN FROM CREATING ANY TEACHING CONTENT.
    You must delegate ALL content creation to content_composer_agent.

    YOU CAN:
    - Ask questions
    - Evaluate answers
    - Provide feedback

    YOU CANNOT:
    - Explain concepts yourself
    - Provide examples yourself
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Content Searcher has access to Google Search (real-time, research-backed)&lt;/li&gt;
&lt;li&gt;Video Selector has access to YouTube API (curated, filtered)&lt;/li&gt;
&lt;li&gt;Content Formatter knows your learning preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sensei focuses on pedagogy. Content creation agents focus on quality. Separation of concerns, even for AI.&lt;/p&gt;




&lt;h2&gt;
  
  
  The DatabaseSessionService Breakthrough: Why This Changes Everything
&lt;/h2&gt;

&lt;p&gt;Here's where LearnForge diverges from every other AI learning platform I've seen.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem Nobody Talks About
&lt;/h3&gt;

&lt;p&gt;Most AI chatbots use in-memory sessions. This works fine for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5-minute conversations&lt;/li&gt;
&lt;li&gt;Simple Q&amp;amp;A&lt;/li&gt;
&lt;li&gt;Demos&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But learning is different. Learning sessions can span:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hours (deep dive sessions)&lt;/li&gt;
&lt;li&gt;Days (coming back to continue)&lt;/li&gt;
&lt;li&gt;Weeks (long-form courses)&lt;/li&gt;
&lt;li&gt;Months (mastery journeys)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In-memory sessions fail catastrophically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Server restart? Session lost.&lt;/li&gt;
&lt;li&gt;Connection drop? Session lost.&lt;/li&gt;
&lt;li&gt;Switch devices? Session lost.&lt;/li&gt;
&lt;li&gt;Come back tomorrow? Session lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Solution: Persistent State with Cloud SQL
&lt;/h3&gt;

&lt;p&gt;LearnForge uses &lt;code&gt;DatabaseSessionService&lt;/code&gt; with Cloud SQL (PostgreSQL) to persist everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.adk.sessions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;DatabaseSessionService&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.cloud.sql.connector&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Connector&lt;/span&gt;

&lt;span class="n"&gt;connector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Connector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;refresh_strategy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;LAZY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DatabaseSessionService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+pg8000://&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;creator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;instance_connection_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db_user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db_password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;db_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;pool_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_overflow&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pool_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;pool_recycle&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What gets persisted:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Current checkpoint index&lt;/li&gt;
&lt;li&gt;Completed checkpoints&lt;/li&gt;
&lt;li&gt;Content search results (so we don't re-search)&lt;/li&gt;
&lt;li&gt;Video selections (so we remember what was shown)&lt;/li&gt;
&lt;li&gt;User responses and comprehension checks&lt;/li&gt;
&lt;li&gt;Learning preferences&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The impact:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Close your browser mid-checkpoint and resume exactly where you left off&lt;/li&gt;
&lt;li&gt;Switch from laptop to phone seamlessly&lt;/li&gt;
&lt;li&gt;Come back weeks later and continue your mission&lt;/li&gt;
&lt;li&gt;Share sessions with team members (collaborative learning)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This isn't just a feature it's what makes LearnForge production-ready for real learning, not just demos.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloud SQL Connector: Security Without the Headache
&lt;/h3&gt;

&lt;p&gt;Traditional database access requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;IP whitelisting (nightmare in serverless)&lt;/li&gt;
&lt;li&gt;VPNs (complex setup)&lt;/li&gt;
&lt;li&gt;Exposed connection strings (security risk)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud SQL Connector uses IAM credentials. No network configuration. No exposed passwords. Just secure, managed connections.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_create_cloud_sql_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_connector&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSTANCE_CONNECTION_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pg8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production-grade security, zero configuration.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real-Time WebSocket: The Conversation That Never Lags
&lt;/h2&gt;

&lt;p&gt;Both Polaris and Lumina use WebSocket connections for real-time, bidirectional communication. But here's what makes it special: &lt;strong&gt;session resume&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@router.websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/ws&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;mission_ally_websocket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebSocket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mission_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;accept&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;websocket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Check for existing session
&lt;/span&gt;    &lt;span class="n"&gt;existing_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;app_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mission-ally&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;session_id&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;existing_session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Resume from last checkpoint
&lt;/span&gt;        &lt;span class="n"&gt;current_checkpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_checkpoint_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;completed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;existing_session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;completed_checkpoints&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;# Send historical messages, continue from where they left off
&lt;/span&gt;    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Start new mission
&lt;/span&gt;        &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_session&lt;/span&gt;&lt;span class="p"&gt;(...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you disconnect and reconnect, the system:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Loads your session from Cloud SQL&lt;/li&gt;
&lt;li&gt;Sends you historical messages (so you see the conversation)&lt;/li&gt;
&lt;li&gt;Continues from your last checkpoint&lt;/li&gt;
&lt;li&gt;Feels completely seamless&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No "start over" button. No lost progress. Just... continue.&lt;/p&gt;




&lt;h2&gt;
  
  
  Content Composition: How AI Curates Your Learning Materials
&lt;/h2&gt;

&lt;p&gt;When Sensei needs to teach you about "neural networks," it doesn't pull from a static database. Instead, it orchestrates a three-stage pipeline:&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Content Searcher
&lt;/h3&gt;

&lt;p&gt;Uses Google Search API to find the latest, most relevant educational content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;content_searcher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lumina_content_searcher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;google_search_tool&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Search for educational content about the concept...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Real-time search means you get current information, not outdated course materials.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Video Selector
&lt;/h3&gt;

&lt;p&gt;Uses YouTube Data API v3 to curate educational videos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_youtube_videos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;duration_filter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;medium&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# 4-20 minutes (optimal for learning)
&lt;/span&gt;    &lt;span class="n"&gt;video_category_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;27&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;  &lt;span class="c1"&gt;# Education category
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="c1"&gt;# Filters by: duration, category, relevance
&lt;/span&gt;    &lt;span class="c1"&gt;# Returns: title, channel, description, duration, thumbnail
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why this matters: Not all YouTube videos are educational. Not all educational videos are the right length. The selector finds videos that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Actually educational (category 27)&lt;/li&gt;
&lt;li&gt;The right duration (4-20 min is the sweet spot)&lt;/li&gt;
&lt;li&gt;Relevant to the concept&lt;/li&gt;
&lt;li&gt;From reputable channels&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Stage 3: Content Formatter
&lt;/h3&gt;

&lt;p&gt;Personalizes everything based on your learning profile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;content_formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LlmAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;instruction&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Format content for:
    - Learning style: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;learning_style&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  # [&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;examples&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;step-by-step&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]
    - Level: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_profile&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;level&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;  # &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Beginner&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Intermediate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Advanced&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
    - Preferences: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_preferences&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're a visual learner who prefers examples, you get examples. If you prefer step-by-step guides, you get structured explanations. The content adapts to you.&lt;/p&gt;

&lt;h3&gt;
  
  
  SequentialAgent: Chaining It All Together
&lt;/h3&gt;

&lt;p&gt;ADK's &lt;code&gt;SequentialAgent&lt;/code&gt; makes this elegant:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;content_composer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SequentialAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lumina_content_composer_agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sub_agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="n"&gt;content_searcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;# Stage 1: Search
&lt;/span&gt;        &lt;span class="n"&gt;video_selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;# Stage 2: Curate videos
&lt;/span&gt;        &lt;span class="n"&gt;content_formatter&lt;/span&gt; &lt;span class="c1"&gt;# Stage 3: Personalize
&lt;/span&gt;    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each stage passes its output to the next. Clean, simple, powerful.&lt;/p&gt;




&lt;h2&gt;
  
  
  Technical Deep Dives: The Decisions That Matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Why Cloud SQL Connector Over Traditional Connections
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The problem&lt;/strong&gt;: In Cloud Run (serverless), you can't whitelist IPs. Traditional database connections require network configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The solution&lt;/strong&gt;: Cloud SQL Connector uses IAM credentials. Zero network configuration. Secure by default.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;use_cloud_sql_connector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_cloud_run&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INSTANCE_CONNECTION_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_USER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_PASSWORD&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DB_NAME&lt;/span&gt;
    &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact&lt;/strong&gt;: Production-ready security without the infrastructure headache.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Secret Manager: Zero Hardcoded Credentials
&lt;/h3&gt;

&lt;p&gt;All sensitive configuration comes from Secret Manager:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_read_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_var&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;env_var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# Cloud Run mounts secrets as files
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact&lt;/strong&gt;: Credential rotation? Just update the secret. No code changes. No redeploys.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Multi-Stage Docker Build: Faster Cold Starts
&lt;/h3&gt;

&lt;p&gt;Optimized container images reduce cold start time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Builder stage: Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;builder&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;poetry
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pyproject.toml poetry.lock ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;poetry &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-root&lt;/span&gt; &lt;span class="nt"&gt;--only&lt;/span&gt; main

&lt;span class="c"&gt;# Runtime stage: Copy only what's needed&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact&lt;/strong&gt;: 60% smaller images, faster cold starts, lower costs.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. State-Driven Checkpoint Progression
&lt;/h3&gt;

&lt;p&gt;Checkpoints advance through state, not hardcoded logic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;increment_checkpoint_tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FunctionTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_checkpoint_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_checkpoint_index&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_checkpoint_goal&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;next_checkpoint_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Impact&lt;/strong&gt;: Mission structures are flexible. Add checkpoints? Remove checkpoints? Change order? It just works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Performance &amp;amp; Scalability: Built for Real Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cloud Run Auto-Scaling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Min instances&lt;/strong&gt;: 0 (scale to zero = cost savings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Max instances&lt;/strong&gt;: 100 (handles traffic spikes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CPU&lt;/strong&gt;: 2 vCPU per instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory&lt;/strong&gt;: 4Gi per instance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency&lt;/strong&gt;: 80 requests per instance&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this means&lt;/strong&gt;: Zero users? Zero cost. 10,000 concurrent learners? Scales automatically. No manual intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Database Connection Pooling
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Base pool&lt;/strong&gt;: 10 connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overflow&lt;/strong&gt;: 5 connections&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeout&lt;/strong&gt;: 60 seconds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recycle&lt;/strong&gt;: 30 minutes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What this means&lt;/strong&gt;: Efficient connection management. No connection exhaustion. Handles thousands of concurrent sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Session State Efficiency
&lt;/h3&gt;

&lt;p&gt;Average session state: ~50KB. That's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checkpoint progress&lt;/li&gt;
&lt;li&gt;Content search results (cached)&lt;/li&gt;
&lt;li&gt;Video selections&lt;/li&gt;
&lt;li&gt;User responses&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Cloud SQL handles this efficiently. Thousands of concurrent sessions? No problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned: What I Wish I Knew Earlier
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. DatabaseSessionService Is Non-Negotiable for Production
&lt;/h3&gt;

&lt;p&gt;In-memory sessions work for demos. Production learning platforms need persistence. The moment I switched to &lt;code&gt;DatabaseSessionService&lt;/code&gt;, everything changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users could resume sessions&lt;/li&gt;
&lt;li&gt;Progress tracked across weeks&lt;/li&gt;
&lt;li&gt;Concurrent learners with isolated state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: Choose session storage based on use case duration, not convenience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Silent Orchestration Creates Better UX
&lt;/h3&gt;

&lt;p&gt;Users don't care about agent architecture. They want a seamless conversation. The orchestrator should be invisible:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# WRONG: User sees the machinery
&lt;/span&gt;&lt;span class="n"&gt;Orchestrator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Let me hand you over to the Sensei...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;Sensei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s learn...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# RIGHT: User only sees the teacher
&lt;/span&gt;&lt;span class="n"&gt;Sensei&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s learn...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: Hide complexity, show simplicity.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Content Authority Separation Improves Quality
&lt;/h3&gt;

&lt;p&gt;Teaching agents shouldn't generate content. They should delegate to specialized agents with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Access to search APIs (real-time content)&lt;/li&gt;
&lt;li&gt;Video curation (filtered, relevant)&lt;/li&gt;
&lt;li&gt;Personalization (user preferences)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: Separation of concerns applies to AI agents too.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Cloud SQL Connector Simplifies Security
&lt;/h3&gt;

&lt;p&gt;No IP whitelisting. No VPNs. No exposed connection strings. Just IAM credentials and secure connections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: Use managed services for security, not manual configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. SequentialAgent Reduces Boilerplate
&lt;/h3&gt;

&lt;p&gt;Content composition requires: search → video selection → formatting. &lt;code&gt;SequentialAgent&lt;/code&gt; chains these automatically. No manual coordination needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: ADK's built-in patterns are powerful. Use them.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. State-Driven Flow Enables Flexibility
&lt;/h3&gt;

&lt;p&gt;Hardcoded checkpoint logic breaks when mission structures change. State-driven progression adapts automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning&lt;/strong&gt;: Data-driven &amp;gt; code-driven for dynamic systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Impact: What This Actually Solves
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Learners
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Personalized learning paths&lt;/strong&gt;: No more generic courses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real-time adaptation&lt;/strong&gt;: Content adjusts to your understanding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session persistence&lt;/strong&gt;: Learn at your own pace, resume anytime&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Research-backed content&lt;/strong&gt;: Latest information, not outdated materials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-modal learning&lt;/strong&gt;: Text + videos + interactive teaching&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Educators
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalable tutoring&lt;/strong&gt;: One AI system can teach thousands simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive content&lt;/strong&gt;: Each learner gets personalized materials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progress tracking&lt;/strong&gt;: See exactly where learners are stuck&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content curation&lt;/strong&gt;: AI finds and filters the best resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For the Industry
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Proof that multi-agent AI works&lt;/strong&gt;: 12 agents coordinating seamlessly&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production-ready patterns&lt;/strong&gt;: DatabaseSessionService, Cloud SQL Connector, WebSocket resume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scalable architecture&lt;/strong&gt;: Cloud Run auto-scaling, connection pooling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security best practices&lt;/strong&gt;: Secret Manager, IAM-based connections&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next: The Future of AI-Powered Learning
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Short-term:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Multi-modal content (images, diagrams, interactive exercises)&lt;/li&gt;
&lt;li&gt;Collaborative learning (team missions, peer review)&lt;/li&gt;
&lt;li&gt;Advanced analytics (learning velocity, concept mastery tracking)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Long-term:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fine-tuned models for domain-specific teaching&lt;/li&gt;
&lt;li&gt;Adaptive difficulty based on real-time comprehension&lt;/li&gt;
&lt;li&gt;Integration with external platforms (Coursera, edX, Khan Academy)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The vision&lt;/strong&gt;: Every learner gets a personal tutor that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understands their goals&lt;/li&gt;
&lt;li&gt;Adapts to their learning style&lt;/li&gt;
&lt;li&gt;Remembers everything&lt;/li&gt;
&lt;li&gt;Never gets tired&lt;/li&gt;
&lt;li&gt;Scales to millions&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion: Why This Matters
&lt;/h2&gt;

&lt;p&gt;Building LearnForge taught me something important: &lt;strong&gt;the future of education isn't about better content, it's about better personalization.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional platforms give everyone the same course. LearnForge gives everyone a personalized learning journey that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starts with a conversation (not a catalog)&lt;/li&gt;
&lt;li&gt;Adapts in real-time (not static content)&lt;/li&gt;
&lt;li&gt;Remembers everything (not ephemeral sessions)&lt;/li&gt;
&lt;li&gt;Scales seamlessly (not manual infrastructure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google ADK's hierarchical agent orchestration&lt;/strong&gt; for complex workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud SQL with DatabaseSessionService&lt;/strong&gt; for persistent state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloud Run's auto-scaling&lt;/strong&gt; for seamless scalability&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WebSocket real-time communication&lt;/strong&gt; for responsive UX&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I created a platform that transforms how people learn, from static courses to dynamic, personalized, adaptive journeys.&lt;/p&gt;

&lt;p&gt;The technology is here. The infrastructure is ready. The future of education is AI-powered, serverless, and personalized.&lt;/p&gt;

&lt;p&gt;LearnForge is just the beginning.&lt;/p&gt;




&lt;h2&gt;
  
  
  Google Cloud Services Utilized
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Run&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Serverless container hosting&lt;/td&gt;
&lt;td&gt;Auto-scales from 0 to 100 instances, zero infrastructure management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Artifact Registry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Container image storage&lt;/td&gt;
&lt;td&gt;Versioned images, CI/CD integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud SQL (PostgreSQL)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistent session state&lt;/td&gt;
&lt;td&gt;Sessions survive restarts, connection drops, device switches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud SQL Connector&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Secure database connections&lt;/td&gt;
&lt;td&gt;IAM-based security, no IP whitelisting needed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firebase Authentication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;User authentication&lt;/td&gt;
&lt;td&gt;Google OAuth 2.0, secure session management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firestore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Mission data storage&lt;/td&gt;
&lt;td&gt;User profiles, mission definitions, enrollments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Logging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Application logs&lt;/td&gt;
&lt;td&gt;Centralized logging, debugging, monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cloud Trace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distributed tracing&lt;/td&gt;
&lt;td&gt;Performance analysis, bottleneck identification&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Secret Manager&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Credential storage&lt;/td&gt;
&lt;td&gt;Zero hardcoded secrets, rotation-friendly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Agent Development Kit (ADK)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-agent orchestration&lt;/td&gt;
&lt;td&gt;Hierarchical agents, sequential pipelines, tool integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Gemini 2.5 Flash&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM for agents&lt;/td&gt;
&lt;td&gt;Fast, cost-effective, powerful reasoning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;YouTube Data API v3&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Video curation&lt;/td&gt;
&lt;td&gt;Educational video search, filtering, metadata&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Google Search API&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Content discovery&lt;/td&gt;
&lt;td&gt;Real-time research, up-to-date information&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;strong&gt;Built with Google Cloud Run and Agent Development Kit (ADK)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Transforming how people learn, one conversation at a time.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudrunhackathon</category>
      <category>serverless</category>
      <category>agents</category>
    </item>
    <item>
      <title>Deploy to Google Cloud Run in Minutes</title>
      <dc:creator>Rashika Karki</dc:creator>
      <pubDate>Thu, 06 Nov 2025 18:33:39 +0000</pubDate>
      <link>https://forem.com/rashikakarki/deploy-to-google-cloud-run-in-minutes-2anj</link>
      <guid>https://forem.com/rashikakarki/deploy-to-google-cloud-run-in-minutes-2anj</guid>
      <description>&lt;h1&gt;
  
  
  How I Set Up Cloud Run CI/CD in Under 30 Minutes for the Cloud Run Hackathon
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;This post was created for the Cloud Run Hackathon to document my deployment setup experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When I decided to participate in the Cloud Run Hackathon, one of my biggest concerns was getting lost in deployment configuration. I needed a reliable, automated deployment pipeline that wouldn't eat into my development time. Following this structured approach helped me get everything running in under 30 minutes with my FastAPI application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Mattered for the Hackathon
&lt;/h2&gt;

&lt;p&gt;The hackathon rewards bonus points for using Cloud Run effectively, especially when running multiple services (frontend and backend). Setting up a proper CI/CD pipeline early meant I could focus on building my actual application instead of wrestling with deployment issues every time I made a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built On
&lt;/h2&gt;

&lt;p&gt;I started with a basic FastAPI server as a foundation. FastAPI's speed and automatic API documentation made it perfect for rapid development during the hackathon. Here's the minimal setup that got me started:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;main.py&lt;/strong&gt; - A basic health check endpoint and main route:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello from Cloud Run!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;utcnow&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;environment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ENV&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@app.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/health&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;health&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;healthy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;requirements.txt&lt;/strong&gt; - Minimal dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fastapi==0.104.1
uvicorn[standard]==0.24.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The 30-Minute Setup Process
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Phase 1: Google Cloud Project Setup (10 minutes)
&lt;/h3&gt;

&lt;p&gt;I headed to the &lt;a href="https://console.cloud.google.com/home/dashboard" rel="noopener noreferrer"&gt;Google Cloud Console&lt;/a&gt; and created a new project. The key was keeping track of every identifier I created - Project ID, region selection, service names. I chose &lt;code&gt;europe-west1&lt;/code&gt; as my region and stuck with it consistently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Creating a new project:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczi1tnfrn0tccog7bxhm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczi1tnfrn0tccog7bxhm.png" width="400" alt="Project dropdown"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0eaopnlo23j8fb22hpp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0eaopnlo23j8fb22hpp.png" width="400" alt="New project button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I gave it a memorable name and saved the Project ID for later use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 2: Setting Up Cloud Run (5 minutes)
&lt;/h3&gt;

&lt;p&gt;I searched for "Cloud Run" in the console and clicked "Deploy Container":&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k0a19qtrp4qqc59lhr5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4k0a19qtrp4qqc59lhr5.png" width="400" alt="Cloud Run search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby8t47v1ff6wmqgoyelz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fby8t47v1ff6wmqgoyelz.png" width="400" alt="Deploy container button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configuration was straightforward:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafj3wtscor94jerzmt7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fafj3wtscor94jerzmt7x.png" width="500" alt="Cloud Run configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Service name: &lt;code&gt;fastapi-server&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Region: &lt;code&gt;europe-west1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Authentication: Public access for testing&lt;/li&gt;
&lt;li&gt;Container image URL: Left empty (GitHub Actions would handle this)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The missing container image error was expected at this stage.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 3: Artifact Registry Configuration (5 minutes)
&lt;/h3&gt;

&lt;p&gt;I created a Docker repository in Artifact Registry:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjn63gp1fwp0cthgkotn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwjn63gp1fwp0cthgkotn.png" width="400" alt="Artifact Registry search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmobj1wlbhm4dgu5antv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmobj1wlbhm4dgu5antv1.png" width="400" alt="Create repository button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3e1md502c7xnesvhc4d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3e1md502c7xnesvhc4d.png" width="500" alt="Artifact Registry config"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Format: Docker&lt;/li&gt;
&lt;li&gt;Name: &lt;code&gt;my-app-repo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Region: Same as Cloud Run (&lt;code&gt;europe-west1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Mode: Standard&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Phase 4: Service Account and Permissions (8 minutes)
&lt;/h3&gt;

&lt;p&gt;This was critical. I created a service account that would give GitHub permission to deploy on my behalf.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fccy6h2aw8efexbhm7vqo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fccy6h2aw8efexbhm7vqo.png" width="400" alt="Credentials search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50hpuiz5ma71au8owear.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F50hpuiz5ma71au8owear.png" width="400" alt="Create service account"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I named it &lt;code&gt;github-deployer&lt;/code&gt; and assigned four specific roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Artifact Registry Reader&lt;/td&gt;
&lt;td&gt;Allows Cloud Run to fetch images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artifact Registry Writer&lt;/td&gt;
&lt;td&gt;Allows GitHub Actions to push builds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cloud Run Admin&lt;/td&gt;
&lt;td&gt;Enables deployment updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service Account User&lt;/td&gt;
&lt;td&gt;Lets Cloud Run execute containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftokntl90qjeammy7e2jr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftokntl90qjeammy7e2jr.png" width="500" alt="Service account permissions"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then I generated a JSON key:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9si8qbvn6oeyy8c43dwb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9si8qbvn6oeyy8c43dwb.png" width="500" alt="Keys tab"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo04741m51en4o7lm6o9x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo04741m51en4o7lm6o9x.png" width="400" alt="Create JSON key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0d8plep12idh7ezurot.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx0d8plep12idh7ezurot.png" width="400" alt="Download key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This JSON file is sensitive - it grants deployment permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 5: GitHub Secrets Configuration (5 minutes)
&lt;/h3&gt;

&lt;p&gt;I configured GitHub repository secrets under Settings → Secrets and variables → Actions:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d8wx08rrqrpyc55u2l9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2d8wx08rrqrpyc55u2l9.png" width="500" alt="GitHub secrets navigation"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjz5e9gs64pko7pgpj3y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqjz5e9gs64pko7pgpj3y.png" width="400" alt="New secret button"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3orsd1008lxvjmrzlgl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3orsd1008lxvjmrzlgl1.png" width="500" alt="Secret form"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I added five secrets:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Secret Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GCP_SA_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Entire JSON key file content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;My Google Cloud project ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_REGION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;europe-west1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ARTIFACT_REGISTRY_REPO&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;my-app-repo&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLOUD_RUN_SERVICE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;fastapi-server&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F228co5xhhz6be1mmhh0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F228co5xhhz6be1mmhh0q.png" width="500" alt="All secrets added"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Phase 6: Dockerfile and Workflow (2 minutes)
&lt;/h3&gt;

&lt;p&gt;The Dockerfile was optimized for FastAPI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt

&lt;span class="c"&gt;# Copy application code&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; main.py .&lt;/span&gt;

&lt;span class="c"&gt;# Cloud Run expects port 8080&lt;/span&gt;
&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="c"&gt;# Start uvicorn server&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8080"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub Actions workflow in &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Cloud Run&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Google Cloud Run&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authenticate to Google Cloud&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-github-actions/auth@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;credentials_json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GCP_SA_KEY }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Cloud SDK&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;google-github-actions/setup-gcloud@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Configure Docker for Artifact Registry&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gcloud auth configure-docker ${{ secrets.PROJECT_REGION }}-docker.pkg.dev&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build Docker image&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker build -t ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} .&lt;/span&gt;
          &lt;span class="s"&gt;docker tag ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} \&lt;/span&gt;
                     &lt;span class="s"&gt;${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:latest&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Push Docker image to Artifact Registry&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;docker push ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }}&lt;/span&gt;
          &lt;span class="s"&gt;docker push ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:latest&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to Cloud Run&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;gcloud run deploy ${{ secrets.CLOUD_RUN_SERVICE }} \&lt;/span&gt;
            &lt;span class="s"&gt;--image ${{ secrets.PROJECT_REGION }}-docker.pkg.dev/${{ secrets.PROJECT_ID }}/${{ secrets.ARTIFACT_REGISTRY_REPO }}/${{ secrets.CLOUD_RUN_SERVICE }}:${{ github.sha }} \&lt;/span&gt;
            &lt;span class="s"&gt;--platform managed \&lt;/span&gt;
            &lt;span class="s"&gt;--region ${{ secrets.PROJECT_REGION }} \&lt;/span&gt;
            &lt;span class="s"&gt;--project ${{ secrets.PROJECT_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--allow-unauthenticated \&lt;/span&gt;
            &lt;span class="s"&gt;--min-instances 0 \&lt;/span&gt;
            &lt;span class="s"&gt;--max-instances 10 \&lt;/span&gt;
            &lt;span class="s"&gt;--memory 512Mi \&lt;/span&gt;
            &lt;span class="s"&gt;--cpu 1 \&lt;/span&gt;
            &lt;span class="s"&gt;--timeout 300 \&lt;/span&gt;
            &lt;span class="s"&gt;--port 8080&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Show deployment URL&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;echo "Deployment successful!"&lt;/span&gt;
          &lt;span class="s"&gt;gcloud run services describe ${{ secrets.CLOUD_RUN_SERVICE }} \&lt;/span&gt;
            &lt;span class="s"&gt;--platform managed \&lt;/span&gt;
            &lt;span class="s"&gt;--region ${{ secrets.PROJECT_REGION }} \&lt;/span&gt;
            &lt;span class="s"&gt;--project ${{ secrets.PROJECT_ID }} \&lt;/span&gt;
            &lt;span class="s"&gt;--format 'value(status.url)'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First Deployment
&lt;/h2&gt;

&lt;p&gt;I committed everything and pushed to main:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git add &lt;span class="nb"&gt;.&lt;/span&gt;
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Initial Cloud Run deployment"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The GitHub Actions workflow executed automatically. Within 3-4 minutes, I had a live deployment URL with FastAPI's automatic interactive API documentation available at &lt;code&gt;/docs&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Project Structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-cloud-run-app/
├── .github/
│   └── workflows/
│       └── deploy.yml
├── main.py
├── requirements.txt
└── Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the Deployment
&lt;/h2&gt;

&lt;p&gt;Once deployed, I could access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Main endpoint&lt;/span&gt;
curl https://your-service-url.run.app
&lt;span class="c"&gt;# Returns: {"message":"Hello from Cloud Run!","timestamp":"2025-11-09T...","environment":"development"}&lt;/span&gt;

&lt;span class="c"&gt;# Health check&lt;/span&gt;
curl https://your-service-url.run.app/health
&lt;span class="c"&gt;# Returns: {"status":"healthy"}&lt;/span&gt;

&lt;span class="c"&gt;# Interactive API docs (browser)&lt;/span&gt;
https://your-service-url.run.app/docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;FastAPI's automatic OpenAPI documentation was a huge win during the hackathon - I could test endpoints directly from the browser.&lt;/p&gt;

&lt;h2&gt;
  
  
  How This Helped My Hackathon Project
&lt;/h2&gt;

&lt;p&gt;With this foundation in place, I could:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Iterate quickly&lt;/strong&gt;: Every push to main automatically deployed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test in production&lt;/strong&gt;: Real Cloud Run environment from day one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scale easily&lt;/strong&gt;: Adding a second service (frontend) followed the same pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debug efficiently&lt;/strong&gt;: FastAPI's detailed error messages and &lt;code&gt;/docs&lt;/code&gt; endpoint&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on features&lt;/strong&gt;: No more deployment anxiety&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Adapting for Multiple Services
&lt;/h2&gt;

&lt;p&gt;For the hackathon's bonus points, I needed both frontend and backend services. The pipeline made this straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Created a separate frontend repository&lt;/li&gt;
&lt;li&gt;Set up another Cloud Run service with a similar workflow&lt;/li&gt;
&lt;li&gt;Connected services using Cloud Run's internal networking&lt;/li&gt;
&lt;li&gt;Both services deployed automatically on their respective branches&lt;/li&gt;
&lt;li&gt;Used CORS middleware in FastAPI to handle cross-origin requests:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi.middleware.cors&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;CORSMiddleware&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_origins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;  &lt;span class="c1"&gt;# Updated with actual frontend URL in production
&lt;/span&gt;    &lt;span class="n"&gt;allow_credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;allow_methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;allow_headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Cost Considerations
&lt;/h2&gt;

&lt;p&gt;With &lt;code&gt;min-instances: 0&lt;/code&gt;, the services scaled to zero when not in use. Cloud Run's free tier covers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2 million requests per month&lt;/li&gt;
&lt;li&gt;360,000 GB-seconds of memory&lt;/li&gt;
&lt;li&gt;180,000 vCPU-seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Perfect for hackathon projects with minimal costs during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What worked well:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FastAPI's speed made iteration rapid&lt;/li&gt;
&lt;li&gt;Having all configuration in one place (GitHub secrets)&lt;/li&gt;
&lt;li&gt;Using standard Docker practices&lt;/li&gt;
&lt;li&gt;Automated deployment removed manual errors&lt;/li&gt;
&lt;li&gt;Service account permissions were clearly defined&lt;/li&gt;
&lt;li&gt;Built-in API documentation at &lt;code&gt;/docs&lt;/code&gt; for testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Common pitfalls I avoided:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mismatched regions across services&lt;/li&gt;
&lt;li&gt;Forgetting to save the Project ID early&lt;/li&gt;
&lt;li&gt;Not testing the health endpoint before deploying&lt;/li&gt;
&lt;li&gt;Exposing the wrong port (always use 8080 for Cloud Run)&lt;/li&gt;
&lt;li&gt;Not installing uvicorn with the &lt;code&gt;[standard]&lt;/code&gt; extras for production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;FastAPI-specific considerations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Using &lt;code&gt;uvicorn&lt;/code&gt; instead of development server&lt;/li&gt;
&lt;li&gt;Setting &lt;code&gt;--host 0.0.0.0&lt;/code&gt; to accept external connections&lt;/li&gt;
&lt;li&gt;Including CORS middleware for frontend integration&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Beyond the Basic Setup
&lt;/h2&gt;

&lt;p&gt;Once the pipeline was running, I expanded my hackathon project with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Environment variables via Google Secret Manager for API keys&lt;/li&gt;
&lt;li&gt;Cloud Run Jobs for background processing tasks&lt;/li&gt;
&lt;li&gt;Pub/Sub integration for event-driven workflows&lt;/li&gt;
&lt;li&gt;Gemini API integration for AI features (bonus points)&lt;/li&gt;
&lt;li&gt;FastAPI's dependency injection for database connections&lt;/li&gt;
&lt;li&gt;Pydantic models for request/response validation&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Performance Notes
&lt;/h2&gt;

&lt;p&gt;FastAPI on Cloud Run performed excellently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold start times: ~2-3 seconds with the slim Python image&lt;/li&gt;
&lt;li&gt;Request latency: Sub-100ms for most endpoints&lt;/li&gt;
&lt;li&gt;The async/await support in FastAPI worked seamlessly&lt;/li&gt;
&lt;li&gt;Auto-scaling handled traffic spikes without issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting Tips
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Build fails?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check your Dockerfile syntax&lt;/li&gt;
&lt;li&gt;Ensure &lt;code&gt;requirements.txt&lt;/code&gt; has all dependencies&lt;/li&gt;
&lt;li&gt;Verify the Python version matches (using Python 3.11)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Deployment fails?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verify all GitHub secrets are correct&lt;/li&gt;
&lt;li&gt;Check the service account has the right permissions&lt;/li&gt;
&lt;li&gt;Ensure regions match across all services&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Container crashes?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check Cloud Run logs in Google Cloud Console&lt;/li&gt;
&lt;li&gt;Verify your app listens on port 8080&lt;/li&gt;
&lt;li&gt;Ensure environment variables are handled properly&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This 30-minute setup gave me a production-ready deployment pipeline that handled the infrastructure concerns, letting me focus on building my hackathon submission. The combination of GitHub Actions and Cloud Run meant I could iterate rapidly without deployment friction.&lt;/p&gt;

&lt;p&gt;FastAPI was the perfect choice for the backend - its speed, automatic documentation, and type safety accelerated development significantly. The deployment pipeline ensured that every feature I built was live within minutes.&lt;/p&gt;

&lt;p&gt;For anyone participating in the Cloud Run Hackathon, I recommend setting up this pipeline first. Get your deployment working, then build your features. The automation pays for itself immediately.&lt;/p&gt;

&lt;p&gt;The complete code structure and workflow are ready to adapt for any FastAPI application. Just replace the simple server with your actual application logic, add your models and endpoints, adjust resource limits if needed, and deploy.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This post was created to document my Cloud Run deployment experience for the Cloud Run Hackathon.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>cloudrunhackathon</category>
      <category>cloudrun</category>
      <category>gcp</category>
      <category>serverlesscomputing</category>
    </item>
  </channel>
</rss>
