Shortcut: if you'd rather not type out the scaffold, the companion repo is the exact code this step produces. Clone it and skip ahead to "The .env File."
Install uv (or skip)
# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows (PowerShell)
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"uv is Astral's fast Python package manager — one binary replaces pip + venv + pip-tools. If you'd rather use pip, every uv add X below maps to pip install X after running python -m venv .venv && source .venv/bin/activate.
Scaffold the Project
mkdir rag-on-gcp
cd rag-on-gcp
uv init --python 3.12Add the four dependencies we need:
uv add \
"cloud-sql-python-connector[pg8000]" \
google-cloud-aiplatform \
google-genai \
fastapi \
"uvicorn[standard]" \
python-dotenvWhat each one does:
| Package | Role |
|---|---|
cloud-sql-python-connector[pg8000] | Connects to Cloud SQL using IAM auth. pg8000 is the pure-Python Postgres driver it speaks through. |
google-cloud-aiplatform | Vertex AI client — used for text-embedding-005. |
google-genai | The unified Google GenAI SDK — used to call Gemini. |
fastapi + uvicorn | The web framework + the ASGI server we'll run in Cloud Run. |
python-dotenv | Loads a .env file in dev. Cloud Run uses env vars directly. |
Project Layout
mkdir -p src data sql scripts
touch src/__init__.py src/config.py src/db.py
touch sql/001_init.sql
touch .env .env.exampleYou should end up with:
rag-on-gcp/
├── data/ # Drop .txt files here later
├── sql/
│ └── 001_init.sql # Schema reference (same SQL from Step 3)
├── scripts/
├── src/
│ ├── __init__.py
│ ├── config.py # Loads env config
│ └── db.py # Cloud SQL connection helper
├── .env
├── .env.example
└── pyproject.tomlThe .env File
Three values. Two come from Step 2 and 3.
# .env
GOOGLE_CLOUD_PROJECT=your-project-id-from-step-2
GOOGLE_CLOUD_REGION=us-central1
INSTANCE_CONNECTION_NAME=your-project-id:us-central1:rag-db
DB_NAME=rag
DB_USER=your-email@gmail.comCopy the same structure into .env.example but with placeholder values — that one gets committed; .env does not.
Quick .gitignore:
cat >> .gitignore <<'EOF'
.env
.venv/
__pycache__/
*.pyc
EOFsrc/config.py — One Place for Env
"""Read environment variables once; expose them as a typed object."""
from dataclasses import dataclass
import os
from dotenv import load_dotenv
load_dotenv()
@dataclass(frozen=True)
class Config:
project: str
region: str
instance_connection_name: str
db_name: str
db_user: str
def load_config() -> Config:
def _required(key: str) -> str:
value = os.environ.get(key)
if not value:
raise RuntimeError(f"Missing required env var: {key}")
return value
return Config(
project=_required("GOOGLE_CLOUD_PROJECT"),
region=_required("GOOGLE_CLOUD_REGION"),
instance_connection_name=_required("INSTANCE_CONNECTION_NAME"),
db_name=_required("DB_NAME"),
db_user=_required("DB_USER"),
)src/db.py — The Connector
This is the file that does the magic. The Cloud SQL Python Connector uses your Application Default Credentials (set up in Step 2) to authenticate to Cloud SQL over Google's private network. No public IP, no proxy binary, no password.
"""Cloud SQL connection helper using the Python Connector + IAM auth."""
from contextlib import contextmanager
from google.cloud.sql.connector import Connector, IPTypes
import pg8000.dbapi
from .config import load_config
_config = load_config()
_connector = Connector(refresh_strategy="lazy")
def _connect() -> pg8000.dbapi.Connection:
return _connector.connect(
_config.instance_connection_name,
"pg8000",
user=_config.db_user,
db=_config.db_name,
enable_iam_auth=True,
ip_type=IPTypes.PUBLIC,
)
@contextmanager
def get_conn():
"""Yield a Postgres connection. Closes on exit."""
conn = _connect()
try:
yield conn
finally:
conn.close()Two things worth flagging:
enable_iam_auth=True— tells the connector to fetch a short-lived OAuth token from ADC and use it as the password. This is why we never set a Postgres password.ip_type=IPTypes.PUBLIC— we created the instance with a public IP. The connector still tunnels through Google's network; "public" here is about the addressing, not the routing. When we deploy to Cloud Run in Step 7, we'll switch this toPRIVATEover a VPC connector for production.
Smoke Test
Drop a tiny test script next to src/:
# scripts/test_connection.py
from src.db import get_conn
with get_conn() as conn:
cur = conn.cursor()
cur.execute("SELECT 1, current_user, current_database()")
print(cur.fetchone())Run it:
uv run python -m scripts.test_connectionYou should see something like:
(1, 'your-email@gmail.com', 'rag')That's the database confirming: you (by your IAM identity) connected to the rag database and ran a query. No password ever changed hands.
Common First-Run Failures
| Error | What it means |
|---|---|
google.auth.exceptions.DefaultCredentialsError | You skipped gcloud auth application-default login in Step 2. Run it. |
pg8000.exceptions.DatabaseError: ... password authentication failed | The IAM user wasn't created or doesn't match your email exactly. Check gcloud sql users list --instance=rag-db. |
Connection refused | The instance was stopped. gcloud sql instances patch rag-db --activation-policy=ALWAYS. |
403 Cloud SQL Admin API has not been used | API isn't enabled. Re-run gcloud services enable sqladmin.googleapis.com. |
sql/001_init.sql — Keep the Schema in the Repo
Drop the schema from Step 3 into the repo so you can re-create it from scratch if needed:
-- sql/001_init.sql
CREATE EXTENSION IF NOT EXISTS vector;
CREATE TABLE IF NOT EXISTS chunks (
id BIGSERIAL PRIMARY KEY,
source TEXT NOT NULL,
chunk_index INTEGER NOT NULL,
content TEXT NOT NULL,
embedding vector(768) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS chunks_embedding_hnsw
ON chunks
USING hnsw (embedding vector_cosine_ops);We won't ship this to Cloud Run — it's a one-time bootstrap. But future-you (or a teammate) will thank you.
What You Have Now
- A Python project with the four packages we need
- A typed
Configobject that reads env vars - A
get_conn()context manager that authenticates as your Google identity - A passing smoke test that proves end-to-end connectivity
Next: ingest some text.
Reference: Cloud SQL Python Connector · pg8000 driver · uv quickstart