1. Introduction
This codelab walks you through the basics of getting started with Cloud Run. You'll learn how to use additional features, including VPC access, Secret Manager, and ADK for AI agents hosted on Cloud Run.
What you'll learn
- Deploy an nginx image
- Deploy from your source code
- Rollback a deployment
- Preview a deployment
- Use the Developer Knowledge MCP server tool
- Use secret manager with Cloud Run
- Connect to an internal Cloud Run service within a VPC
- Deploy an ADK agent to Cloud Run
What you'll need
- A web browser such as Chrome
- A Google Cloud project with billing enabled
Create a Google Cloud Project
- In the Google Cloud Console, on the project selector page, select or create a Google Cloud project.
- Make sure that billing is enabled for your Cloud project. Learn how to check if billing is enabled on a project.
Start Cloud Shell
Cloud Shell is a command-line environment running in Google Cloud that comes preloaded with necessary tools.
- Click Activate Cloud Shell at the top of the Google Cloud console.
- Once connected to Cloud Shell, verify your authentication:
gcloud auth list - Confirm your project is configured:
gcloud config get project - If your project is not set as expected, set it:
export PROJECT_ID=<YOUR_PROJECT_ID> gcloud config set project $PROJECT_ID
Set Environment Variables
This codelab uses the following environment variable.
First, set your region.
export REGION=<YOUR_REGION>
Next, confirm your PROJECT_ID and REGION
echo "PROJECT_ID: $PROJECT_ID | REGION: $REGION"
2. Deploy from Image
In this section, you'll deploy a standard nginx image directly from Docker Hub. You'll configure it to be publicly accessible and set the container port to 80.
- Deploy the nginx service:
gcloud run deploy nginx-service \
--image=nginx \
--allow-unauthenticated \
--port=80 \
--region=$REGION
- Once the deployment is complete, the command output will provide a Service URL. Open that URL in your browser to see the "Welcome to nginx!" page.
3. Deploy from source
mkdir color-app && cd $_
Create a file called requirements.txt with the following contents:
Flask>=2.0.0
gunicorn>=20.0.0
Create a file called main.py with the following contents:
import os
from flask import Flask, render_template_string
app = Flask(__name__)
TEMPLATE = """
<!doctype html>
<html lang="en">
<head>
<title>Cloud Run Traffic Revisions</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 50vh;
background-color: darkseagreen;
font-family: sans-serif;
}
.content {
background-color: rgba(255, 255, 255, 0.8); /* Semi-transparent white background */
padding: 2em;
border-radius: 8px;
text-align: center;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div class="content">
<!-- ROLLBACK DEMO: change this text to "gray" -->
<p>background color: <strong>darkseagreen</strong></p>
</div>
</body>
</html>
"""
@app.route('/')
def main():
return render_template_string(TEMPLATE)
if __name__ == '__main__':
port = int(os.environ.get('PORT', 8080))
app.run(debug=True, host='0.0.0.0', port=port)
Now run the following command.
gcloud run deploy \
--allow-unauthenticated
--region $REGION
4. Rollbacks and Preview links
In this section, you'll introduce a bug and learn how to roll back to a previous revision while you investigate the fix.
- First, you'll record the name of the revision that's currently serving traffic, as it does not contain the bug.
GOOD_REVISION=$(gcloud run revisions list --service color-app \
--region $REGION --format 'value(REVISION)')
- Within the color-app
main.pyfile, search for "ROLLBACK DEMO" and update the line to the following:
<p>background color: <strong>gray</strong></p>
- Now re-run
gcloud run deploy. Notice how your previous configurations were used.
Now that you've deployed a bug, you could go back to your source, make a change or do a git revert and then build, trigger a new build, etc. However, it's possible you could introduce an error along the way.
A safer way is to do a roll back.
- To roll back to the previous revision, run the following:
gcloud run services update-traffic color-app \
--to-revisions=$GOOD_REVISION=100 \
--region=$REGION
Now you can deploy a new revision that will not receive any traffic.
- Now fix the bug by changing the text back to
darkseagreen
<p>background color: <strong>darkseagreen</strong></p>
- And deploy it to verify the fix. Note that it will not receive any traffic, because 100% of traffic is pinned to the GOOD_REVISION
gcloud run deploy color-app --no-traffic --tag bugfix --region $REGION
- Verify the deployment
You'll notice the URL is slightly different. When you visit it, you'll see your bug fix in this deployment.
- Send traffic back to the latest revision.
Now you'll set traffic back to the latest revision.
gcloud run services update-traffic color-app \
--to-latest \
--region=$REGION
and delete the revision tag
gcloud run services update-traffic color-app \
--remove-tags=bugfix \
--region=$REGION
You can learn more about rollbacks in the docs.
5. Developer Knowledge MCP server
The developer Knowledge MCP server gives AI-powered development tools the ability to search Google's official developer documentation and retrieve information for Google's products such as Firebase, Google Cloud, Android, Maps, and more. By connecting your AI application straight to our official library of documentation, it ensures the code and guidance you receive are up-to-date and based on authoritative context.
You'll want to follow the installation guidance for giving your AI agent access to the Developer Knowledge MCP server.
Once installed, you can ask your AI agent questions about the latest features in the documentation that might have become available after the your model's training date cut-off.
For example, if you look at Cloud Run release notes, you'll see for 24 February 2026 an entry about "Deploy a highly available, multi-region Cloud Run service with automated failover and failback for external traffic using Cloud Run service health (Preview)."
You can now ask your AI agent "tell me more about this new Cloud Run feature for multi-regional automated failover."
6. Using Secret Manager
There are 3 ways to expose secrets on Cloud Run:
- As an environment variable (locked to the version pulled at deploy time).
- Mounted as a file volume (continuously updated to the latest version).
- Using the Secret Manager client libraries in your code.
In this section, you'll expose a secret as an environment variable using a dedicated service account.
- Create a new secret named ‘my-secret':
gcloud secrets create my-secret --replication-policy="automatic"
- Add the secret value as a new version:
echo -n "my precious" | gcloud secrets versions add my-secret --data-file=-
- Create a dedicated service account for the color-app:
gcloud iam service-accounts create color-app-sa \
--display-name="Color App Service Account"
- Grant the dedicated service account access to the secret.
gcloud secrets add-iam-policy-binding my-secret \
--member="serviceAccount:color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
- Deploy again. Now the service will have access to the MY_SECRET environment variable:
gcloud run deploy color-app \
--source . \
--update-secrets=MY_SECRET=my-secret:latest \
--service-account=color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com \
--region=$REGION
7. Connect to a VPC
In this section, you'll set up the following architecture:
- private backend that is not accessible from the public internet
- a public frontend that communicates with the backend through direct VPC Egress
This example will use the default network and subnet.
Prerequisite: Ensure Private Google Access is enabled on your subnet so the VPC can route internal requests to Cloud Run services.
gcloud compute networks subnets update default \
--region=$REGION \
--enable-private-ip-google-access
- Create folder for this section
mkdir ../vpc-demo
cd ../vpc-demo
- create the private backend service
mkdir backend
touch backend/app.js
touch backend/package.json
In the backend/app.js file, add the following:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World from the Private Backend!');
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`Private backend listening on port ${port}`);
});
In the backend/package.json file, add the following:
{
"name": "backend",
"scripts": {
"start": "node app.js"
}
}
- Deploy the private backend with internal-only ingress:
gcloud run deploy private-backend \
--source ./backend \
--region $REGION \
--ingress internal \
--no-allow-unauthenticated
- Record the backend URL. You will provide this URL to the frontend app later.
export PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format='value(projec
tNumber)')
export BACKEND_URL="https://private-backend-${PROJECT_NUMBER}.${REGION}.run.app"
- Create the frontend App
mkdir frontend
touch frontend/app.js
touch frontend/package.json
In the frontend/app.js file, add the following:
const http = require('http');
const server = http.createServer(async (req, res) => {
const backendUrl = process.env.BACKEND_URL;
if (!backendUrl) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
return res.end('Error: BACKEND_URL environment variable is missing.');
}
try {
// Fetch the OIDC token from the Metadata server
const tokenResponse = await fetch(`http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/identity?audience=${backendUrl}`, {
headers: { 'Metadata-Flavor': 'Google' }
});
if (!tokenResponse.ok) {
throw new Error(`Failed to fetch identity token: ${tokenResponse.statusText}`);
}
const token = await tokenResponse.text();
// Ping the backend with the token
const response = await fetch(backendUrl, {
headers: { 'Authorization': `Bearer ${token}` }
});
const text = await response.text();
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(`Frontend successfully routed through VPC. Backend says: "${text}"`);
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end(`Frontend failed to reach the backend. Error: ${error.message}`);
}
});
const port = process.env.PORT || 8080;
server.listen(port, () => {
console.log(`Public frontend listening on port ${port}`);
});
- In the
frontend/package.jsonfile, add the following:
{
"name": "backend",
"scripts": {
"start": "node app.js"
}
}
- Create a dedicated service account for the frontend service:
gcloud iam service-accounts create frontend-sa \
--display-name="Frontend Service Account"
- Grant it the Cloud Run Invoker role
PROJECT_ID=$(gcloud config get project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:frontend-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/run.invoker"
- Now deploy the public frontend using Direct VPC Egress. We set ‘–vpc-egress=all-traffic' to force the outbound request into the VPC:
gcloud run deploy public-frontend \
--source ./frontend \
--region $REGION \
--allow-unauthenticated \
--network default \
--subnet default \
--vpc-egress all-traffic \
--service-account=frontend-sa@${PROJECT_ID}.iam.gserviceaccount.com \
--set-env-vars BACKEND_URL=$BACKEND_URL
- Verify the services
- Test the Frontend: Curl the public frontend URL. It should successfully communicate with the backend and return a response.
FRONTEND_URL=$(gcloud run services describe public-frontend --region $REGION --format='value(status.url)')
curl $FRONTEND_URL
- Test the Backend (Direct): Attempt to curl the backend URL directly from your local machine (public internet). It should fail with a 404 error because ingress is restricted to ‘internal' and authentication is required.
curl $BACKEND_URL
8. Deploy an ADK Agent
In this section, you'll see how the Python buildpack supports default entrypoint detection for the Agent Development Kit (ADK).
You will create the following folder structure:
adk-demo - my_agent - __init.py__ - agent.py - requirements.txt
- Create the folder structure
mkdir ../adk-demo
cd ../adk-demo
mkdir my_agent
touch my_agent/__init.py__
touch my_agent/agent.py
touch requirements.txt
- Add the following contents to the
my_agent/__init.py__file:
from . import agent
- Add the following contents to the
my_agent/agent.pyfile:
from google.adk import Agent
root_agent = Agent(
name="demo_agent",
model="gemini-3-flash-preview",
instruction="You are a helpful assistant for a Cloud Run demo."
)
- Add the following contents to the
requirements.txtfile:
google-adk
- Create a dedicated service account for the agent:
gcloud iam service-accounts create agent-sa \
--display-name="Agent Service Account"
- Grant the service account the Vertex AI User role:
PROJECT_ID=$(gcloud config get-value project)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:agent-sa@${PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/aiplatform.user"
- Deploy the ADK agent
You must deploy in a region where the Gemini API is accessible. In this example, it is us-west1.
gcloud run deploy my-adk-agent-demo \
--source . \
--region us-west1 \
--allow-unauthenticated \
--service-account=agent-sa@${PROJECT_ID}.iam.gserviceaccount.com \
--set-env-vars="GOOGLE_GENAI_USE_VERTEXAI=TRUE,GOOGLE_CLOUD_PROJECT=$PROJECT_ID,GOOGLE_CLOUD_LOCATION=global"
- Curl the endpoint
You can see how the agent is instantly available as a production-ready API.
Record the Cloud Run service URL into an env var.
AGENT_URL=$(gcloud run services describe my-adk-agent-demo \
--region us-west1 \
--format 'value(status.url)')
Create a session with the agent
curl -X POST $AGENT_URL/apps/my_agent/users/u_123/sessions/s_123 -H "Content-Type: application/json" -d '{"key1": "value1", "key2": 42}'
Ask what is Cloud Run and filter the response to only show what the agent says
curl -X POST $AGENT_URL/run \
-H "Content-Type: application/json" \
-d "{
\"appName\": \"my_agent\",
\"userId\": \"u_123\",
\"sessionId\": \"s_123\",
\"newMessage\": {
\"role\": \"user\",
\"parts\": [{ \"text\": \"What is Cloud Run?\"
}]}
}" | python3 -c "import sys, json; print(json.load(sys.stdin)[-1]['content']['parts'][0]['text'])"
and you should see something similar to the following:
Hello! I am **demo_agent**, and I'm here to help you with your Cloud Run demo. **Cloud Run** is a fully managed compute platform by Google Cloud that allows you to run **containerized applications** in a serverless environment...
9. Clean up
To avoid ongoing charges to your Google Cloud account, you can either delete the entire project (shown below) or delete the individual resources created during this codelab.
Delete the nginx, color-app, private-backend, public-frontend services
gcloud run services delete nginx-service --region $REGION --quiet
gcloud run services delete color-app --region $REGION --quiet
gcloud run services delete private-backend --region $REGION --quiet
gcloud run services delete public-frontend --region $REGION --quiet
Delete the ADK agent (note: deployed in us-west1 for this example)
gcloud run services delete my-adk-agent-demo --region us-west1 --quiet
Remove the secret stored in Secret Manager:
gcloud secrets delete my-secret --quiet
Delete the Color App service account
gcloud iam service-accounts delete color-app-sa@${PROJECT_ID}.iam.gserviceaccount.com --quiet
Delete the ADK Agent service account
gcloud iam service-accounts delete agent-sa@${PROJECT_ID}.iam.gserviceaccount.com --quiet
(Optional) Delete the Project
If you created a new project specifically for this codelab, you can delete the entire project to ensure all resources are removed at once:
# run only if you want to delete the entire project
gcloud projects delete $PROJECT_ID
10. Congratulations!
You have completed the codelab. You have walked through through the basics of getting started with Cloud Run.
What you learned
- Deploy an nginx image
- Deploy from your source code
- Rollback a deployment
- Preview a deployment
- Use the Developer Knowledge MCP server tool
- Use secret manager with Cloud Run
- Connect to an internal Cloud Run service within a VPC
- Deploy an ADK agent to Cloud Run