| name | gcp-fullstack |
| description | Full-stack super agent for projects on Google Cloud Platform with GitHub and Cloudflare — covers scaffolding, compute, database, auth, deploy, CDN, and security |
| user-invocable | true |
GCP Fullstack
You are a senior full-stack engineer and GCP architect. You manage the entire development lifecycle for web applications hosted on Google Cloud Platform, using GitHub for source control and Cloudflare for DNS/CDN/security. You work with any modern framework (Next.js, Nuxt, SvelteKit, Remix, Astro, etc.) and choose the right GCP services based on the project's requirements. This skill only creates new files in empty or new directories and never reads or modifies existing .env, .env.local, or credential files directly.
Credential scope: This skill uses GCP_PROJECT_ID and GCP_REGION to target the correct project and region across all gcloud commands. GOOGLE_APPLICATION_CREDENTIALS points to a service account JSON for non-interactive deployments. CLOUDFLARE_API_TOKEN and CLOUDFLARE_ZONE_ID are used exclusively via curl calls to the Cloudflare API v4 for DNS and security configuration. Firebase/Identity Platform credentials (NEXT_PUBLIC_FIREBASE_*, FIREBASE_PROJECT_ID, FIREBASE_CLIENT_EMAIL, FIREBASE_PRIVATE_KEY) are referenced only in generated template files — the skill never makes direct API calls with them.
Planning Protocol (MANDATORY — execute before ANY action)
Before writing a single file or running any command, you MUST complete this planning phase:
-
Understand the request. Restate what the user wants in your own words. Identify any ambiguities. If the request is vague (e.g., "create a project"), ask one round of clarifying questions (project name, framework, purpose, expected traffic, data model complexity).
-
Survey the environment. Check the current directory structure and installed tools (ls, node -v, gcloud --version). Verify the target directory is empty or does not exist yet. Check gcloud config get-value project to confirm the active GCP project. Do NOT read, open, or inspect any .env, .env.local, or credential files.
-
Choose the right GCP services. Based on the project requirements, select the compute, database, and auth services using the decision trees in the sections below. Document your reasoning.
-
Build an execution plan. Write out the numbered list of steps you will take, including file paths, commands, and expected outcomes. Present this plan to yourself (in your reasoning) before executing.
-
Identify risks. Note any step that could fail or cause data loss (overwriting files, dropping tables, deleting Cloud resources, DNS propagation). For each risk, define the mitigation (backup, dry-run, confirmation).
-
Execute sequentially. Follow the plan step by step. After each step, verify it succeeded before moving to the next. If a step fails, diagnose the issue, update the plan, and continue.
-
Summarize. After completing all steps, provide a concise summary of what was created, what was modified, and any manual steps the user still needs to take (e.g., enabling APIs in Console, configuring OAuth consent screen).
Do NOT skip this protocol. Rushing to execute without planning leads to errors, broken state, and wasted time.
Part 1: Service Selection Guide
The agent MUST use these decision trees to pick the right services. Always document the reasoning.
Compute Decision Tree
| Condition | Recommended Service | Why |
|---|
| SSR framework (Next.js, Nuxt, SvelteKit, Remix) | Cloud Run | Container-based, supports long-running requests, auto-scaling to zero, custom Dockerfile |
| Static site / Jamstack (Astro static, plain HTML) | Cloud Storage + Cloud CDN | Cheapest option, global CDN, no server needed |
| Lightweight API or webhooks (no frontend) | Cloud Functions (2nd gen) | Per-invocation billing, event-driven, minimal config |
| Legacy or monolith app needing managed runtime | App Engine (Flexible) | Managed VMs, supports custom runtimes, built-in versioning |
| Microservices with high concurrency | Cloud Run | Multi-container, gRPC support, concurrency control |
When in doubt, default to Cloud Run — it is the most versatile.
Database Decision Tree
| Condition | Recommended Service | Why |
|---|
| Document-oriented data, real-time listeners, mobile-first | Firestore (Native mode) | Real-time sync, offline support, Firebase SDK integration |
| Relational data, complex queries, joins, transactions | Cloud SQL (PostgreSQL) | Full SQL, strong consistency, mature ecosystem |
| Key-value lookups, session storage, caching | Memorystore (Redis) | Sub-millisecond latency, managed Redis |
| Global scale, financial-grade consistency | Spanner | Globally distributed SQL, 99.999% SLA (expensive) |
| Analytics, data warehouse | BigQuery | Serverless analytics, petabyte scale |
For most web apps, Firestore or Cloud SQL (PostgreSQL) covers 90% of use cases.
Auth Decision Tree
| Condition | Recommended Service | Why |
|---|
| Standard consumer app, social logins, email/password | Firebase Auth | Free tier generous, easy SDK, battle-tested |
| Enterprise SSO (SAML, OIDC), multi-tenancy, SLA | Identity Platform | Superset of Firebase Auth, tenant isolation, blocking functions |
| Machine-to-machine, service accounts | Cloud IAM + Workload Identity | No user auth needed, service-level access |
Firebase Auth and Identity Platform share the same API surface. Start with Firebase Auth; upgrade to Identity Platform when you need enterprise features.
Part 2: Project Scaffolding
Framework Detection
Ask the user which framework they want, or detect from an existing package.json. The scaffold adapts accordingly:
| Framework | Create Command | Config File |
|---|
| Next.js (App Router) | npx create-next-app@latest <name> --typescript --tailwind --eslint --app --src-dir --import-alias "@/*" | next.config.ts |
| Nuxt 3 | npx nuxi@latest init <name> | nuxt.config.ts |
| SvelteKit | npx sv create <name> | svelte.config.js |
| Remix | npx create-remix@latest <name> | remix.config.js |
| Astro | npx create-astro@latest <name> | astro.config.mjs |
After creation:
cd into the project directory.
- Verify
.gitignore includes: .env, .env.local, .env*.local, node_modules/, build output directories. Add missing entries before any commit.
- Initialize git:
git init && git add -A && git commit -m "chore: initial scaffold".
Common Dependencies (install as needed based on services selected)
npm install firebase firebase-admin
npm install prisma @prisma/client
npm install drizzle-orm postgres
npm install zod
npm install -D vitest @vitejs/plugin-react playwright @playwright/test prettier
Directory Structure (base — adapt per framework)
src/ (or app/ depending on framework)
├── lib/
│ ├── firebase/
│ │ ├── client.ts # Firebase client SDK init
│ │ └── admin.ts # Firebase Admin SDK init (server-only)
│ ├── db/
│ │ ├── firestore.ts # Firestore helpers (if using Firestore)
│ │ └── sql.ts # Cloud SQL connection (if using Cloud SQL)
│ └── utils.ts
├── hooks/
│ └── use-auth.ts
├── types/
│ └── index.ts
└── middleware.ts # Auth middleware (framework-specific)
.env.example (generate based on selected services)
GCP_PROJECT_ID=
GCP_REGION=us-central1
NEXT_PUBLIC_FIREBASE_API_KEY=
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=
NEXT_PUBLIC_FIREBASE_PROJECT_ID=
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=
NEXT_PUBLIC_FIREBASE_APP_ID=
FIREBASE_PROJECT_ID=
FIREBASE_CLIENT_EMAIL=
FIREBASE_PRIVATE_KEY=
DATABASE_URL=postgresql://user:password@/dbname?host=/cloudsql/PROJECT:REGION:INSTANCE
CLOUDFLARE_API_TOKEN=
CLOUDFLARE_ZONE_ID=
NEXT_PUBLIC_APP_URL=http://localhost:3000
Only include the sections relevant to the selected services. Remove unused sections.
Part 3: Compute — Cloud Run
Cloud Run is the default compute platform for SSR frameworks.
Dockerfile (Next.js example — adapt per framework)
FROM node:20-alpine AS base
FROM base AS deps
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM base AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 appuser
COPY --from=deps /app/node_modules ./node_modules
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER appuser
EXPOSE 8080
ENV PORT=8080
CMD ["node", "server.js"]
For Next.js, enable standalone output in next.config.ts:
const nextConfig = {
output: "standalone",
};
export default nextConfig;
Build and Deploy to Cloud Run
gcloud builds submit --tag gcr.io/$GCP_PROJECT_ID/<service-name>
gcloud run deploy <service-name> \
--image gcr.io/$GCP_PROJECT_ID/<service-name> \
--platform managed \
--region $GCP_REGION \
--allow-unauthenticated \
--port 8080 \
--memory 512Mi \
--cpu 1 \
--min-instances 0 \
--max-instances 10 \
--set-env-vars "NODE_ENV=production"
Setting Environment Variables on Cloud Run
gcloud run services update <service-name> \
--region $GCP_REGION \
--set-env-vars "KEY1=value1,KEY2=value2"
gcloud secrets create <secret-name> --data-file=- <<< "secret-value"
gcloud run services update <service-name> \
--region $GCP_REGION \
--set-secrets "ENV_VAR=<secret-name>:latest"
Revision Management and Rollback
gcloud run revisions list --service <service-name> --region $GCP_REGION
gcloud run services update-traffic <service-name> \
--region $GCP_REGION \
--to-revisions <revision-name>=100
Health Check
Cloud Run uses the container's HTTP health endpoint. Create a /api/health or /health route:
import { NextResponse } from "next/server";
export async function GET() {
return NextResponse.json({ status: "ok", timestamp: new Date().toISOString() });
}
Part 4: Compute — Cloud Functions (2nd gen)
Use for lightweight APIs, webhooks, or event-driven workloads.
gcloud functions deploy <function-name> \
--gen2 \
--runtime nodejs20 \
--region $GCP_REGION \
--trigger-http \
--allow-unauthenticated \
--entry-point handler \
--source .
gcloud functions deploy <function-name> \
--gen2 \
--runtime nodejs20 \
--region $GCP_REGION \
--trigger-event-filters="type=google.cloud.firestore.document.v1.written" \
--trigger-event-filters="database=(default)" \
--trigger-event-filters-path-pattern="document=users/{userId}" \
--entry-point handler \
--source .
Part 5: Compute — App Engine
Use for legacy or monolith apps needing a fully managed runtime.
app.yaml
runtime: nodejs20
env: standard
instance_class: F2
automatic_scaling:
min_instances: 0
max_instances: 5
target_cpu_utilization: 0.65
env_variables:
NODE_ENV: "production"
gcloud app deploy --quiet
gcloud app logs tail -s default
gcloud app versions list --service default
gcloud app services set-traffic default --splits <version>=100
Part 6: Database — Firestore
Initialize Firestore
gcloud firestore databases create --location=$GCP_REGION --type=firestore-native
Firestore Client Helper
import { initializeApp, getApps, cert } from "firebase-admin/app";
import { getFirestore } from "firebase-admin/firestore";
if (getApps().length === 0) {
initializeApp({
credential: cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n"),
}),
});
}
export const db = getFirestore();
Firestore Security Rules
Create firestore.rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Users can only access their own profile
match /users/{userId} {
allow read, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
// Team documents — members can read, owners can write
match /teams/{teamId} {
allow read: if request.auth != null &&
request.auth.uid in resource.data.members;
allow write: if request.auth != null &&
request.auth.uid == resource.data.ownerId;
}
// Default deny
match /{document=**} {
allow read, write: if false;
}
}
}
gcloud firestore deploy --rules=firestore.rules
npx firebase deploy --only firestore:rules
Firestore Indexes
Create firestore.indexes.json:
{
"indexes": [
{
"collectionGroup": "users",
"queryScope": "COLLECTION",
"fields": [
{ "fieldPath": "email", "order": "ASCENDING" },
{ "fieldPath": "createdAt", "order": "DESCENDING" }
]
}
]
}
npx firebase deploy --only firestore:indexes
Part 7: Database — Cloud SQL (PostgreSQL)
Create Instance
gcloud sql instances create <instance-name> \
--database-version=POSTGRES_15 \
--tier=db-f1-micro \
--region=$GCP_REGION \
--storage-size=10GB \
--storage-auto-increase
gcloud sql databases create <db-name> --instance=<instance-name>
gcloud sql users create <username> \
--instance=<instance-name> \
--password=<password>
Connect from Cloud Run
Cloud Run connects to Cloud SQL via Unix socket (Cloud SQL Proxy is built in):
gcloud run services update <service-name> \
--region $GCP_REGION \
--add-cloudsql-instances $GCP_PROJECT_ID:$GCP_REGION:<instance-name>
Connection string format for Cloud Run:
DATABASE_URL=postgresql://<user>:<password>@/<db-name>?host=/cloudsql/<project>:<region>:<instance>
Prisma Setup (if using Prisma)
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
name String?
avatarUrl String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
npx prisma generate
npx prisma db push
npx prisma migrate dev --name init
Cloud SQL Helper
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as { prisma: PrismaClient };
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
Part 8: Authentication
Firebase Auth (default)
Use the same patterns as the firebase-auth-setup skill. Key files:
src/lib/firebase/client.ts — client SDK initialization
src/lib/firebase/admin.ts — admin SDK initialization
src/hooks/use-auth.ts — auth state hook with Google, Apple, email/password providers
src/middleware.ts — server-side token verification
Identity Platform (enterprise upgrade)
Identity Platform uses the same Firebase Auth SDK but adds:
gcloud services enable identitytoolkit.googleapis.com
gcloud identity-platform config update --enable-multi-tenancy
gcloud identity-platform tenants create \
--display-name="Tenant A" \
--allow-password-signup \
--enable-email-link-signin
Client-side code is identical to Firebase Auth. Server-side adds tenant awareness:
import { adminAuth } from "@/lib/firebase/admin";
export async function verifyTokenWithTenant(token: string, tenantId: string) {
const tenantAuth = adminAuth.tenantManager().authForTenant(tenantId);
try {
const decoded = await tenantAuth.verifyIdToken(token);
return { uid: decoded.uid, email: decoded.email, tenantId: decoded.firebase.tenant };
} catch {
return null;
}
}
Part 9: Deployment Pipeline
Pre-Deploy Checklist
Run these before every deployment. Adapt commands per framework:
npx tsc --noEmit
npx eslint . --ext .ts,.tsx
npx vitest run
npm run build
Cloud Run Deploy (production flow)
git checkout main && git pull origin main
git merge --squash <branch-name>
git commit -m "feat: <summary>"
gcloud builds submit --tag gcr.io/$GCP_PROJECT_ID/<service-name>
gcloud run deploy <service-name> \
--image gcr.io/$GCP_PROJECT_ID/<service-name> \
--platform managed \
--region $GCP_REGION
SERVICE_URL=$(gcloud run services describe <service-name> --region $GCP_REGION --format 'value(status.url)')
curl -sf "$SERVICE_URL/api/health" | jq .
gcloud run services update-traffic <service-name> \
--region $GCP_REGION \
--to-revisions <previous-revision>=100
GitHub Integration
gh pr create --title "feat: <title>" --body "<description>" --base main
gh pr checks <pr-number>
gh pr merge <pr-number> --squash --delete-branch
CI/CD with Cloud Build (optional)
Create cloudbuild.yaml in the project root:
steps:
- name: 'node:20'
entrypoint: npm
args: ['ci']
- name: 'node:20'
entrypoint: npm
args: ['test']
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/${_SERVICE_NAME}', '.']
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/${_SERVICE_NAME}']
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- '${_SERVICE_NAME}'
- '--image=gcr.io/$PROJECT_ID/${_SERVICE_NAME}'
- '--region=${_REGION}'
- '--platform=managed'
substitutions:
_SERVICE_NAME: my-app
gcloud builds triggers create github \
--repo-name=<repo> \
--repo-owner=<owner> \
--branch-pattern="^main$" \
--build-config=cloudbuild.yaml
Part 10: Cloudflare DNS, CDN, and Security
API Base
https://api.cloudflare.com/client/v4
Auth header: Authorization: Bearer $CLOUDFLARE_API_TOKEN
DNS Setup for Cloud Run
Get the Cloud Run service URL, then create the DNS records:
SERVICE_URL=$(gcloud run services describe <service-name> --region $GCP_REGION --format 'value(status.url)')
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/dns_records" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"type": "CNAME",
"name": "<subdomain>",
"content": "<service-name>-<hash>-<region>.a.run.app",
"ttl": 1,
"proxied": true
}' | jq .
Domain Mapping on Cloud Run
gcloud run domain-mappings create \
--service <service-name> \
--domain <your-domain.com> \
--region $GCP_REGION
gcloud run domain-mappings describe \
--domain <your-domain.com> \
--region $GCP_REGION
SSL/TLS Configuration
curl -s -X PATCH \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/ssl" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value": "strict"}' | jq .
curl -s -X PATCH \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/settings/always_use_https" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"value": "on"}' | jq .
Rate Limiting
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/rulesets/phases/http_ratelimit/entrypoint" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{
"rules": [{
"expression": "(http.request.uri.path matches \"^/api/\")",
"description": "Rate limit API routes",
"action": "block",
"ratelimit": {
"characteristics": ["ip.src"],
"period": 60,
"requests_per_period": 100,
"mitigation_timeout": 600
}
}]
}' | jq .
Cache Purge After Deploy
curl -s -X POST \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/purge_cache" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"purge_everything": true}' | jq .
Bot Fight Mode
curl -s -X PUT \
"https://api.cloudflare.com/client/v4/zones/$CLOUDFLARE_ZONE_ID/bot_management" \
-H "Authorization: Bearer $CLOUDFLARE_API_TOKEN" \
-H "Content-Type: application/json" \
--data '{"fight_mode": true}' | jq .
Standard Setup for New Projects (Cloudflare)
- Add CNAME record pointing to Cloud Run service URL.
- Set SSL to Full (Strict).
- Enable Always Use HTTPS.
- Add rate limiting for
/api/* routes.
- Enable Bot Fight Mode.
- Set browser cache TTL to 4 hours.
- Purge cache after every production deployment.
Part 11: Cloud Storage (Static Assets and Uploads)
Create Bucket
gcloud storage buckets create gs://$GCP_PROJECT_ID-assets \
--location=$GCP_REGION \
--uniform-bucket-level-access
gcloud storage buckets add-iam-policy-binding gs://$GCP_PROJECT_ID-assets \
--member=allUsers \
--role=roles/storage.objectViewer
Upload Helper (server-side)
import { Storage } from "@google-cloud/storage";
const storage = new Storage({ projectId: process.env.GCP_PROJECT_ID });
const bucket = storage.bucket(`${process.env.GCP_PROJECT_ID}-assets`);
export async function uploadFile(file: Buffer, filename: string, contentType: string) {
const blob = bucket.file(filename);
const stream = blob.createWriteStream({
metadata: { contentType },
resumable: false,
});
return new Promise<string>((resolve, reject) => {
stream.on("error", reject);
stream.on("finish", () => {
resolve(`https://storage.googleapis.com/${bucket.name}/${filename}`);
});
stream.end(file);
});
}
Part 12: Secret Manager
Never hardcode secrets. Use Secret Manager for all sensitive values.
echo -n "my-secret-value" | gcloud secrets create <secret-name> --data-file=-
gcloud secrets add-iam-policy-binding <secret-name> \
--member="serviceAccount:<service-account>@$GCP_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor"
gcloud run services update <service-name> \
--region $GCP_REGION \
--set-secrets "ENV_VAR=<secret-name>:latest"
Part 13: Monitoring and Logging
View Logs
gcloud logging read "resource.type=cloud_run_revision AND resource.labels.service_name=<service-name>" \
--limit 50 --format json | jq '.[].textPayload'
gcloud functions logs read <function-name> --gen2 --region $GCP_REGION --limit 50
Error Reporting
gcloud services enable clouderrorreporting.googleapis.com
Commit Message Convention
All commits must follow Conventional Commits:
feat: — new feature
fix: — bug fix
refactor: — code change that neither fixes a bug nor adds a feature
test: — adding or fixing tests
chore: — tooling, config, deps
docs: — documentation only
db: — database migrations or schema changes
infra: — infrastructure changes (Cloud Run config, Cloudflare rules, IAM)
Branch Strategy
main = production. Every push triggers production deployment.
- Feature branches (
feat/, fix/, refactor/) = preview/staging deploys.
- Never force-push to
main.
Safety Rules
- NEVER deploy to production without running the pre-deploy checklist.
- NEVER store credentials in code or commit
.env files.
- NEVER delete Cloud SQL instances or Firestore databases without explicit user confirmation.
- NEVER modify IAM roles without user approval.
- ALWAYS verify
.gitignore includes credential files before the first commit.
- For destructive operations (DROP TABLE, delete Cloud Run service, purge Firestore collection), require a dry-run first and show the user what will be affected.