1. Descripción general
Cloud Run es una plataforma completamente administrada que te permite ejecutar tu código directamente en la infraestructura escalable de Google. En este codelab, se demostrará cómo conectar una aplicación de Next.js en Cloud Run a una base de datos de Cloud SQL para PostgreSQL.
En este lab, aprenderás a hacer lo siguiente:
- Crea una instancia de Cloud SQL para PostgreSQL
- Implementa una aplicación en Cloud Run que se conecte a tu base de datos de Cloud SQL
2. Requisitos previos
- Si aún no tienes una Cuenta de Google, debes crear una.
- Usa una cuenta personal en lugar de una cuenta de trabajo o institución educativa. Es posible que las cuentas de trabajo y de instituciones educativas tengan restricciones que te impidan habilitar las APIs necesarias para este lab.
3. Configura el proyecto
- Accede a la consola de Google Cloud.
- Habilita la facturación en la consola de Cloud.
- Completar este lab debería costar menos de USD 1 en recursos de Cloud.
- Puedes seguir los pasos al final de este lab para borrar recursos y evitar cargos adicionales.
- Los usuarios nuevos pueden acceder a la prueba gratuita de USD 300.
- Crea un proyecto nuevo o elige reutilizar uno existente.
4. Abre el editor de Cloud Shell
- Navega al Editor de Cloud Shell.
- Si la terminal no aparece en la parte inferior de la pantalla, ábrela:
- Haz clic en el menú de hamburguesa
. - Haz clic en Terminal.
- Haz clic en Terminal nueva.

- Haz clic en el menú de hamburguesa
- En la terminal, configura tu proyecto con este comando:
- Formato:
gcloud config set project [PROJECT_ID] - Ejemplo:
gcloud config set project lab-project-id-example - Si no recuerdas el ID de tu proyecto, haz lo siguiente:
- Puedes enumerar todos los IDs de tus proyectos con el siguiente comando:
gcloud projects list | awk '/PROJECT_ID/{print $2}'

- Puedes enumerar todos los IDs de tus proyectos con el siguiente comando:
- Formato:
- Si se te solicita autorización, haz clic en Autorizar para continuar.

- Deberías ver el siguiente mensaje:
Si ves unUpdated property [core/project].
WARNINGy se te preguntaDo you want to continue (Y/N)?, es probable que hayas ingresado el ID del proyecto de forma incorrecta. PresionaN, presionaEntery vuelve a intentar ejecutar el comandogcloud config set project.
5. Habilita las APIs
En la terminal, habilita las APIs:
gcloud services enable \
compute.googleapis.com \
sqladmin.googleapis.com \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
networkconnectivity.googleapis.com \
servicenetworking.googleapis.com \
cloudaicompanion.googleapis.com
Si se te solicita autorización, haz clic en Autorizar para continuar. 
Este comando puede tardar unos minutos en completarse, pero, finalmente, debería producir un mensaje de éxito similar a este:
Operation "operations/acf.p2-73d90d00-47ee-447a-b600" finished successfully.
6. Configura una cuenta de servicio
Crea y configura una cuenta de servicio de Google Cloud para que la use Cloud Run y tenga los permisos correctos para conectarse a Cloud SQL.
- Ejecuta el comando
gcloud iam service-accounts createde la siguiente manera para crear una cuenta de servicio nueva:gcloud iam service-accounts create quickstart-service-account \ --display-name="Quickstart Service Account" - Ejecuta el comando gcloud projects add-iam-policy-binding de la siguiente manera para agregar el rol de escritor de registros a la cuenta de servicio de Google Cloud que acabas de crear.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member="serviceAccount:quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --role="roles/logging.logWriter"
7. Crea una base de datos de Cloud SQL
- Crea una política de conexión de servicio para permitir la conectividad de red de Cloud Run a Cloud SQL con Private Service Connect
gcloud network-connectivity service-connection-policies create quickstart-policy \ --network=default \ --project=${GOOGLE_CLOUD_PROJECT} \ --region=us-central1 \ --service-class=google-cloud-sql \ --subnets=https://www.googleapis.com/compute/v1/projects/${GOOGLE_CLOUD_PROJECT}/regions/us-central1/subnetworks/default - Genera una contraseña única para tu base de datos
export DB_PASSWORD=$(openssl rand -base64 20) - Ejecuta el comando
gcloud sql instances createpara crear una instancia de Cloud SQLgcloud sql instances create quickstart-instance \ --project=${GOOGLE_CLOUD_PROJECT} \ --root-password=${DB_PASSWORD} \ --database-version=POSTGRES_17 \ --tier=db-perf-optimized-N-2 \ --region=us-central1 \ --ssl-mode=ENCRYPTED_ONLY \ --no-assign-ip \ --enable-private-service-connect \ --psc-auto-connections=network=projects/${GOOGLE_CLOUD_PROJECT}/global/networks/default
Este comando puede tardar unos minutos en completarse.
- Ejecuta el comando
gcloud sql databases createpara crear una base de datos de Cloud SQL dentro dequickstart-instance.gcloud sql databases create quickstart_db \ --instance=quickstart-instance
8. Prepara la aplicación
Prepara una aplicación de Next.js que responda a solicitudes HTTP.
- Para crear un proyecto de Next.js nuevo llamado
task-app, usa el siguiente comando:npx --yes create-next-app@15 task-app \ --ts \ --eslint \ --tailwind \ --no-src-dir \ --turbopack \ --app \ --no-import-alias - Cambia el directorio a
task-app:cd task-app - Instala
pgpara interactuar con la base de datos de PostgreSQL.npm install pg - Instala
@types/pgcomo una dependencia de desarrollo para usar la aplicación de Next.js de TypeScript.npm install --save-dev @types/pg - Abre el archivo
actions.tsen el editor de Cloud Shell: Ahora debería aparecer un archivo vacío en la parte superior de la pantalla. Aquí puedes editar este archivocloudshell edit app/actions.tsactions.ts.
- Copia el siguiente código y pégalo en el archivo
actions.tsabierto:'use server' import pg from 'pg'; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; const { Pool } = pg; const pool = new Pool({ host: process.env.DB_HOST, user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, ssl: { // @ts-expect-error require true is not recognized by @types/pg, but does exist on pg require: true, rejectUnauthorized: false, // required for self-signed certs // https://node-postgres.com/features/ssl#self-signed-cert } }); const tableCreationIfDoesNotExist = async () => { await pool.query(`CREATE TABLE IF NOT EXISTS tasks ( id SERIAL NOT NULL, created_at timestamp NOT NULL, status VARCHAR(255) NOT NULL default 'IN_PROGRESS', title VARCHAR(1024) NOT NULL, PRIMARY KEY (id) );`); } // CREATE export async function addNewTaskToDatabase(newTask: string) { await tableCreationIfDoesNotExist(); await pool.query(`INSERT INTO tasks(created_at, status, title) VALUES(NOW(), 'IN_PROGRESS', $1)`, [newTask]); return; } // READ export async function getTasksFromDatabase() { await tableCreationIfDoesNotExist(); const { rows } = await pool.query(`SELECT id, created_at, status, title FROM tasks ORDER BY created_at DESC LIMIT 100`); return rows; } // UPDATE export async function updateTaskInDatabase(task: Task) { await tableCreationIfDoesNotExist(); await pool.query( `UPDATE tasks SET status = $1, title = $2 WHERE id = $3`, [task.status, task.title, task.id] ); return; } // DELETE export async function deleteTaskFromDatabase(taskId: string) { await tableCreationIfDoesNotExist(); await pool.query(`DELETE FROM tasks WHERE id = $1`, [taskId]); return; } - Abre el archivo
page.tsxen el editor de Cloud Shell: Ahora debería aparecer un archivo existente en la parte superior de la pantalla. Aquí puedes editar este archivocloudshell edit app/page.tsxpage.tsx.
- Borra el contenido existente del archivo
page.tsx. - Copia el siguiente código y pégalo en el archivo
page.tsxabierto:'use client' import React, { useEffect, useState } from "react"; import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions"; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; export default function Home() { const [newTaskTitle, setNewTaskTitle] = useState(''); const [tasks, setTasks] = useState<Task[]>([]); async function getTasks() { const updatedListOfTasks = await getTasksFromDatabase(); setTasks(updatedListOfTasks); } useEffect(() => { getTasks(); }, []); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); await addNewTaskToDatabase(newTaskTitle); await getTasks(); setNewTaskTitle(''); }; async function updateTask(task: Task, newTaskValues: Partial<Task>) { await updateTaskInDatabase({ ...task, ...newTaskValues }); await getTasks(); } async function deleteTask(taskId: string) { await deleteTaskFromDatabase(taskId); await getTasks(); } return ( <main className="p-4"> <h2 className="text-2xl font-bold mb-4">To Do List</h2> <div className="flex mb-4"> <form onSubmit={handleSubmit} className="flex mb-8"> <input type="text" placeholder="New Task Title" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit" /> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap" > Add New Task </button> </form> </div> <table className="w-full"> <tbody> {tasks.map(function (task) { const isComplete = task.status === 'COMPLETE'; return ( <tr key={task.id} className="border-b border-gray-200"> <td className="py-2 px-4"> <input type="checkbox" checked={isComplete} onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })} className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500" /> </td> <td className="py-2 px-4"> <span className={`transition-all duration-300 ease-in-out ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`} > {task.title} </span> </td> <td className="py-2 px-4"> <button onClick={() => deleteTask(task.id)} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right" > Delete </button> </td> </tr> ); })} </tbody> </table> </main> ); }
La aplicación ya está lista para implementarse.
9. Implementa la aplicación en Cloud Run
- Ejecuta el comando gcloud projects add-iam-policy-binding de la siguiente manera para agregar el rol de usuario de red a la cuenta de servicio de Cloud Run para el servicio de Cloud Run que estás a punto de crear.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member "serviceAccount:service-$(gcloud projects describe ${GOOGLE_CLOUD_PROJECT} --format="value(projectNumber)")@serverless-robot-prod.iam.gserviceaccount.com" \ --role "roles/compute.networkUser"
- Ejecuta el comando gcloud projects add-iam-policy-binding de la siguiente manera para agregar el rol de escritor de Artifact Registry al usuario actual para el servicio de Cloud Run que estás a punto de crear.
gcloud projects add-iam-policy-binding ${GOOGLE_CLOUD_PROJECT} \ --member=user:$(gcloud auth list --filter=status:ACTIVE --format="value(account)") \ --role="roles/artifactregistry.writer"
- Ejecuta el siguiente comando para implementar tu aplicación en Cloud Run:
gcloud run deploy helloworld \ --region=us-central1 \ --source=. \ --set-env-vars DB_NAME="quickstart_db" \ --set-env-vars DB_USER="postgres" \ --set-env-vars DB_PASSWORD=${DB_PASSWORD} \ --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \ --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --network=default \ --subnet=default \ --allow-unauthenticated - Si se te solicita, presiona
YyEnterpara confirmar que deseas continuar:Do you want to continue (Y/n)? Y
Después de unos minutos, la aplicación debería proporcionarte una URL para que la visites.
Navega a la URL para ver tu aplicación en acción. Cada vez que visites la URL o actualices la página, verás la app de tareas.
10. Agrega una función con Gemini Code Assist
Ahora implementaste una app web con una base de datos. A continuación, agregaremos una nueva función a nuestra app de Next.js con la ayuda de la IA.
- Regresa al editor de Cloud Shell
- Volver a abrir
page.tsxcd ~/task-app cloudshell edit app/page.tsx - Navega a Gemini Code Assist en el editor de Cloud Shell:
- Haz clic en el ícono de Gemini
en la barra de herramientas que se encuentra en el lado izquierdo de la pantalla. - Si se te solicita, accede con las credenciales de tu Cuenta de Google.
- Si se te solicita que selecciones un proyecto, elige el que creaste para este codelab.

- Haz clic en el ícono de Gemini
- Ingresa la instrucción:
Add the ability to update the title of the task. The code in your output should be complete and working code.. La respuesta debería incluir fragmentos similares a los siguientes para agregar funcioneshandleEditStartyhandleEditCancel:const [editingTaskId, setEditingTaskId] = useState(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; - Reemplaza
page.tsxpor el resultado de Gemini Code Assist. A continuación, se muestra un ejemplo práctico:'use client' import React, { useEffect, useState } from "react"; import { addNewTaskToDatabase, getTasksFromDatabase, deleteTaskFromDatabase, updateTaskInDatabase } from "./actions"; type Task = { id: string; title: string; status: 'IN_PROGRESS' | 'COMPLETE'; }; export default function Home() { const [newTaskTitle, setNewTaskTitle] = useState(''); const [tasks, setTasks] = useState<Task[]>([]); const [editingTaskId, setEditingTaskId] = useState(''); const [editedTaskTitle, setEditedTaskTitle] = useState(''); async function getTasks() { const updatedListOfTasks = await getTasksFromDatabase(); setTasks(updatedListOfTasks); } useEffect(() => { getTasks(); }, []); async function handleSubmit(e: React.FormEvent<HTMLFormElement>) { e.preventDefault(); await addNewTaskToDatabase(newTaskTitle); await getTasks(); setNewTaskTitle(''); }; async function updateTask(task: Task, newTaskValues: Partial<Task>) { await updateTaskInDatabase({ ...task, ...newTaskValues }); await getTasks(); setEditingTaskId(''); setEditedTaskTitle(''); } async function deleteTask(taskId: string) { await deleteTaskFromDatabase(taskId); await getTasks(); } function handleEditStart(task: Task) { setEditingTaskId(task.id); setEditedTaskTitle(task.title); }; return ( <main className="p-4"> <h2 className="text-2xl font-bold mb-4">To Do List</h2> <div className="flex mb-4"> <form onSubmit={handleSubmit} className="flex mb-8"> <input type="text" placeholder="New Task Title" value={newTaskTitle} onChange={(e) => setNewTaskTitle(e.target.value)} className="flex-grow border border-gray-400 rounded px-3 py-2 mr-2 bg-inherit" /> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded text-nowrap" > Add New Task </button> </form> </div> <table className="w-full"> <tbody> {tasks.map(function (task) { const isComplete = task.status === 'COMPLETE'; return ( <tr key={task.id} className="border-b border-gray-200"> <td className="py-2 px-4"> <input type="checkbox" checked={isComplete} onClick={() => updateTask(task, { status: isComplete ? 'IN_PROGRESS' : 'COMPLETE' })} className="transition-transform duration-300 ease-in-out transform scale-100 checked:scale-125 checked:bg-green-500" /> </td> <td className="py-2 px-4"> {editingTaskId === task.id ? ( <form onSubmit={(e) => { e.preventDefault(); updateTask(task, { title: editedTaskTitle }); }} className="flex" > <input type="text" value={editedTaskTitle} onChange={(e) => setEditedTaskTitle(e.target.value)} onBlur={() => updateTask(task, { title: editedTaskTitle })} // Handle clicking outside input className="flex-grow border border-gray-400 rounded px-3 py-1 mr-2 bg-inherit" /> </form> ) : ( <span onClick={() => handleEditStart(task)} className={`transition-all duration-300 ease-in-out cursor-pointer ${isComplete ? 'line-through text-gray-400 opacity-50' : 'opacity-100'}`} > {task.title} </span> )} </td> <td className="py-2 px-4"> <button onClick={() => deleteTask(task.id)} className="bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded float-right" > Delete </button> </td> </tr> ); })} </tbody> </table> </main> ); }
11. Vuelve a implementar la aplicación en Cloud Run
- Ejecuta el siguiente comando para implementar tu aplicación en Cloud Run:
gcloud run deploy helloworld \ --region=us-central1 \ --source=. \ --set-env-vars DB_NAME="quickstart_db" \ --set-env-vars DB_USER="postgres" \ --set-env-vars DB_PASSWORD=${DB_PASSWORD} \ --set-env-vars DB_HOST="$(gcloud sql instances describe quickstart-instance --project=${GOOGLE_CLOUD_PROJECT} --format='value(settings.ipConfiguration.pscConfig.pscAutoConnections.ipAddress)')" \ --service-account="quickstart-service-account@${GOOGLE_CLOUD_PROJECT}.iam.gserviceaccount.com" \ --network=default \ --subnet=default \ --allow-unauthenticated - Si se te solicita, presiona
YyEnterpara confirmar que deseas continuar:Do you want to continue (Y/n)? Y
12. Felicitaciones
En este lab, aprendiste a hacer lo siguiente:
- Crea una instancia de Cloud SQL para PostgreSQL
- Implementa una aplicación en Cloud Run que se conecte a tu base de datos de Cloud SQL
Limpia
Cloud SQL no tiene un nivel gratuito y se te cobrará si sigues usándolo. Puedes borrar tu proyecto de Cloud para evitar que se generen cargos adicionales.
Si bien Cloud Run no cobra cuando el servicio no se usa, es posible que se te cobre por el almacenamiento de la imagen del contenedor en Artifact Registry. Si borras tu proyecto de Cloud, se dejan de facturar todos los recursos que usaste en ese proyecto.
Si quieres, borra el proyecto:
gcloud projects delete $GOOGLE_CLOUD_PROJECT
También puedes borrar los recursos innecesarios de tu disco de Cloud Shell. Puedes hacer lo siguiente:
- Borra el directorio del proyecto del codelab:
rm -rf ~/task-app - Advertencia. Esta próxima acción no se puede deshacer. Si quieres borrar todo el contenido de Cloud Shell para liberar espacio, puedes borrar todo tu directorio principal. Ten cuidado y asegúrate de que todo lo que quieras conservar esté guardado en otro lugar.
sudo rm -rf $HOME
Sigue aprendiendo
- Implementa una aplicación de pila completa de Next.js en Cloud Run con Cloud SQL para PostgreSQL usando el conector de Node.js de Cloud SQL
- Implementa una aplicación de Angular de pila completa en Cloud Run con Cloud SQL para PostgreSQL usando el conector de Node.js de Cloud SQL
- Implementa una aplicación de Angular de pila completa en Cloud Run con Firestore usando el SDK de Firebase Admin para Node.js
- Implementa una aplicación de pila completa de Next.js en Cloud Run con Firestore usando el SDK de Admin de Node.js