Tag: Cloud Run

  • Azure Functions vs Cloud Run: We Ran the Same Worker on Both

    Pick a serverless platform and you’re picking a default for the next five years of your stack. Most comparisons of Azure Functions vs Google Cloud Run are written from the docs. This one isn’t — we deployed the same worker to both, in production, on the free tiers, and watched what happened.

    The worker is simple on purpose: it takes a webhook, does a little work, writes a record, returns JSON. The kind of glue every real system has dozens of. Boring is exactly what you want when you’re measuring the platform and not the app.

    The short answer

    If you just want the verdict: Cloud Run wins for anything containerized and anything where you care about not storing deploy keys. Azure Functions wins when your automation already lives in the Microsoft ecosystem and benefits from Logic Apps, Event Grid, and Entra sitting right next door. Both run our worker for $0/month. The tie-breakers are deploy security and what else is in the neighborhood.

    Now the detail.

    Deploying the same worker

    This is where the two platforms feel most different, and where Google Cloud quietly pulls ahead.

    How we do it

    Azure Functions Google Cloud Run Verdict
    Unit of deploy Function app (code + host) Container image Cloud Run if you’re already containerized
    Deploy auth Publish profile / service principal Workload Identity Federation — no stored keys Cloud Run, decisively
    Cold start Noticeable on Consumption plan Negligible at our scale Cloud Run
    Local dev parity Functions Core Tools (good) “It’s just a container” (great) Cloud Run

    The headline is the deploy auth. Our Cloud Run workers deploy from GitHub Actions using Workload Identity Federation — GitHub proves its identity to Google with a short-lived token, and no service-account key is ever stored in the repo. That’s not a convenience; it’s the single biggest reduction in credential risk you can make in a CI/CD pipeline. Azure Functions can get close with OIDC + a service principal, but the container-native, keyless Cloud Run path was simpler to lock down and is the model we standardized on.

    What the free tier actually gives you

    Both platforms have genuinely generous always-free serverless tiers. The numbers that matter for a glue worker:

    How we do it

    Metric Azure Functions Google Cloud Run Verdict
    Free requests/month 1,000,000 2,000,000 Google — 2× headroom
    Free compute 400,000 GB-s 360,000 GiB-s + 180,000 vCPU-s Roughly even
    Scale to zero Yes (Consumption) Yes Tie
    Max instances control Yes Yes (and per-service concurrency) Cloud Run, slightly
    Our actual bill $0 $0 Tie where it counts

    At our volume — thousands of invocations a month, not millions — both are free and stay free. The 2M-vs-1M request gap only matters if you’re genuinely high-traffic. For most glue workloads, you will never see a bill on either.

    The neighborhood effect

    A serverless function is rarely alone. It fires because something happened and it triggers something else afterward. That’s where the ecosystems diverge — and where Azure earns its keep.

    • Azure Functions sits next to Logic Apps (4,000 free built-in actions/month), Event Grid (100,000 free operations/month), and Entra ID for identity. If your automation is event-driven and Microsoft-centric, the glue around the function is already there and already free.
    • Cloud Run sits next to Eventarc, Cloud Workflows, Pub/Sub, and Cloud Scheduler — the same pattern on Google’s side, equally capable.

    Neither is “better” in the abstract. The right answer is whichever cloud your other services already live in. A function that triggers a Logic App next door beats a function that has to reach across clouds to do the same thing.

    What surprised us

    • Cloud Run cold starts basically disappeared. At our concurrency the container was warm often enough that we stopped thinking about it. Azure Functions on the Consumption plan had more noticeable cold starts for the same workload.
    • Azure’s free side-resources are real. Functions itself is free, but watch the storage account and Application Insights it provisions alongside — those can accrue tiny charges. Set a budget alert on day one.
    • Keyless deploy changed our security posture more than any single config. Once the repo holds zero secrets for deploys, an entire category of “leaked key” incidents just can’t happen.

    The takeaway

    For a containerized, security-conscious, GitHub-Actions-driven stack, Cloud Run is our default — the keyless deploy and the request headroom settle it. But “default” isn’t “only”: when a workload belongs in the Microsoft ecosystem — triggered by Microsoft events, feeding Microsoft services, governed by Entra — Azure Functions is the right tool, and it runs for the same $0.

    Run the same worker on both for a week. The platform stops being a religious debate and becomes a placement decision: put the work where its neighbors already are.

    This is part of our “Two Clouds, One Site” series — we run the same media property on both Azure and Google Cloud, on the free tiers, and write up what we learn. The lab lives on tygart.media; the findings publish here.

    Frequently asked questions

    Is Azure Functions or Cloud Run cheaper?
    For typical glue workloads, both are free and stay free. Cloud Run offers more free requests per month (2M vs 1M) and Azure offers 400,000 GB-seconds of free compute. At thousands of invocations a month you will not see a bill on either; the cost difference only appears at high traffic.

    Which is more secure to deploy?
    Cloud Run, because it supports keyless deploys via Workload Identity Federation — GitHub Actions authenticates with a short-lived token and no service-account key is stored in the repo. Azure Functions can approximate this with OIDC and a service principal, but the container-native keyless path is simpler to secure.

    Can I run the same code on both Azure Functions and Cloud Run?
    Yes. If you package the worker as a container, Cloud Run runs it directly and Azure Functions can run it via a custom handler or containerized function. We deploy the same worker logic to both; the differences are in deploy tooling and the surrounding event services, not the code.

    When should I choose Azure Functions over Cloud Run?
    Choose Azure Functions when your automation already lives in the Microsoft ecosystem — triggered by Event Grid, orchestrated by Logic Apps, or governed by Entra ID. Co-locating the function with the services it talks to beats reaching across clouds.

    Do serverless cold starts matter on either platform?
    At moderate concurrency, Cloud Run cold starts were negligible in our testing because the container stayed warm. Azure Functions on the Consumption plan showed more noticeable cold starts for the same workload. For latency-sensitive endpoints, test under your real traffic before deciding.

  • The $0 Cloud Stack: Running a Real Media Site on Azure and Google Cloud Free Tiers

    Most “Azure vs Google Cloud” articles are written by people who run neither in production. They paraphrase the pricing pages and call it a comparison.

    We do something different: we run the same media property on both clouds at the same time — and the entire thing costs $0/month. Google Cloud is the live operational stack. Azure is a parallel “newsroom” of always-free services running on a dedicated lab domain, tygart.media, mirroring each capability of the live site. Two clouds, one operation, both AI ecosystems watching it work.

    This is the desk-by-desk breakdown — what each cloud actually does for us, where the free tier runs out, and which one wins each specific job. No theory. This is the running system.

    Why run on both clouds at once

    There’s a strategic reason beyond “free is fun.” Search and AI assistants don’t share a brain. Google’s models optimize for Google’s index; Microsoft’s Copilot and Bing optimize for Microsoft’s graph. When ~84% of your organic traffic comes from Bing, having your stack only inside Google’s telemetry is a blind spot.

    Running enrichment through Azure puts the same content inside Microsoft’s service graph the same way Google Cloud puts it inside Google’s. You stop guessing how each ecosystem sees you, because you’re operating inside both.

    The serverless compute plane

    The heart of the stack: code that runs after you push a file and close the laptop.

    How we do it

    Azure Google Cloud Verdict
    Service Azure Functions Cloud Run Cloud Run for containers; Functions for glue
    Free ceiling 1M requests/month 2M requests/month Google, on raw headroom
    Deploy model Functions Core Tools / GitHub Actions Keyless deploy via Workload Identity Federation Google — no stored keys is a real security win
    What surprised us Generous, but watch billable side resources Cold starts negligible at our scale
    Our bill $0 $0 Tie where it counts

    Pick Cloud Run if you’re already containerized and want keyless CI/CD. Pick Azure Functions if your automation lives in the Microsoft ecosystem and you want Logic Apps next door.

    The content enrichment desks

    This is where Azure’s always-free tier quietly outclasses expectations — a full newsroom of AI services that never bill at our volume.

    How we do it

    Job Azure Google Cloud Verdict
    Translation Translator — 2M chars/mo free (~300 articles) Cloud Translation Azure — bigger perpetual free ceiling
    Article audio Neural TTS — 500K chars/mo Cloud Text-to-Speech Toss-up; both natural
    Entity extraction (for GEO) AI Language — 5K records/mo Cloud Natural Language Azure — likely the same signal family Bing uses
    Site search Azure AI Search — 3 indexes free Vertex AI Search Azure — it’s the engine behind Bing

    The entity-extraction line matters most. We feed articles through Azure AI Language to pull named entities and key phrases, then saturate the content with them. We’re optimizing for the same entity signals Microsoft’s own systems use to select content — which is the whole game when Bing drives most of your traffic.

    The storage and front-end layer

    How we do it

    Job Azure Google Cloud Verdict
    Document store Cosmos DB — 1,000 RU/s + 25GB free Firestore Azure — Cosmos free tier is generous (one per subscription)
    Relational Azure SQL — serverless free Cloud SQL (no perpetual free) Azure, clearly
    Static hosting Static Web Apps — 100GB bandwidth Firebase Hosting Tie; both excellent

    For a small operations ledger or a knowledge base, Azure’s always-free Cosmos DB and serverless SQL are the standout — Google Cloud has no equivalent perpetual-free relational tier.

    What it actually costs: nothing (if you’re disciplined)

    The honest caveat: free compute can still trigger billable side resources. A “free” VM drags along disks, public IPs, and monitoring logs that bill immediately with no throttling. The discipline that keeps the bill at zero:

    1. Deploy from the free-services blade, not the general catalog.
    2. Set a budget alert on day one — before you provision anything.
    3. Prefer serverless over VMs — the consumption tiers reset monthly and don’t drag side resources.
    4. One Cosmos DB free tier per subscription — plan around it.

    Do that, and a real, AI-enriched media property runs across two clouds for $0.

    The takeaway

    Single-cloud is a bet that one ecosystem’s view of your content is the only one that matters. When the traffic data says otherwise — when most of your readers arrive through the other company’s search and AI — bilateral cloud stops being a novelty and becomes the obvious posture. The free tiers make it cost nothing but discipline.

    Frequently asked questions

    Is it really free to run on both Azure and Google Cloud?
    Yes, at small-site scale. Both clouds offer always-free serverless tiers (Azure Functions 1M requests/month, Cloud Run 2M requests/month) plus free AI, storage, and hosting services. The cost risk is billable side resources like VM disks and public IPs — avoidable by staying serverless and setting a budget alert.

    Which is better for serverless, Azure or Google Cloud?
    Cloud Run wins on raw request headroom (2M vs 1M/month) and keyless deploys via Workload Identity Federation. Azure Functions wins if your automation already lives in the Microsoft ecosystem and benefits from Logic Apps and Event Grid next door.

    Why would you run the same site on two clouds?
    AI ecosystems don’t share telemetry. Google’s models favor Google’s index; Bing and Copilot favor Microsoft’s graph. If a large share of your traffic comes from Bing, running enrichment through Azure puts your content inside Microsoft’s service graph instead of leaving it a blind spot.

    Does Azure have a better free tier than Google Cloud?
    For perpetual always-free services, Azure is broader — 65+ always-free services including Cosmos DB (1,000 RU/s + 25GB) and serverless Azure SQL, which Google Cloud has no direct perpetual-free equivalent for. Google Cloud wins on serverless request volume and keyless security.

    What’s the catch with Azure’s always-free tier?
    Limits reset monthly and overages bill immediately with no throttling. Free VMs also trigger billable disks, public IPs, and monitoring logs. Deploy from the free-services blade, prefer serverless, and set a budget alert before provisioning.

  • GCP-Powered CRM Touch Calendar Automation for Restoration Companies: Architecture Guide

    GCP-Powered CRM Touch Calendar Automation for Restoration Companies: Architecture Guide

    Who this is for: Your IT person, your developer, or a technical contractor. This brief describes a production-grade automation architecture for the restoration company CRM touch calendar using Google Cloud Platform (GCP). It assumes basic familiarity with cloud infrastructure, command line tools, and web APIs. It does not require deep expertise in any single area — the implementation is intentionally modular so that each component can be handed to a different person if needed.

    The business strategy this automates is in Your CRM Is Not a Lead Database. The manual version of this system is in the Email Automation Setup Guide. This brief is for teams who want to reduce ongoing manual work and build a more robust, scalable version of the same workflow.


    What This Architecture Automates

    The manual system requires a person to: export contacts from the CRM, validate emails, import to Mailchimp or Brevo, configure each campaign, schedule it, and log results back to Notion. For 4–6 campaigns per year, this is manageable manually. For a company running 10–15 campaigns across multiple divisions or service areas, or for an agency running this system for multiple restoration clients, a GCP automation layer eliminates the recurring labor.

    What this architecture handles automatically:

    • Scheduled contact export from ServiceTitan or Jobber via API
    • Segmentation and deduplication logic
    • Email validation pass before import
    • Contact import to Mailchimp or Brevo
    • Campaign creation from template stored in Cloud Storage
    • Campaign scheduling per the calendar in Notion
    • Results logging back to Notion after send

    What still requires human review:

    • Email copy review before scheduling (always — no automation should skip this)
    • Reply triage and qualitative logging
    • Warmth scoring and super-connector identification

    Prerequisites

    • A Google Cloud Platform account with billing enabled (new GCP accounts include $300 in free credits)
    • ServiceTitan or Jobber API access (ServiceTitan requires contacting their enterprise team; Jobber API is available on Connect plan and above at $119–$169/month)
    • Mailchimp account with API access (available on all paid plans) OR Brevo with API access (all plans)
    • Notion account with Notion API integration enabled (free at notion.com/my-integrations)
    • Basic Python familiarity (this implementation uses Python 3.11+)

    Architecture Overview

    ┌─────────────────────────────────────────────────────────────┐
    │                    TRIGGER LAYER                            │
    │  Cloud Scheduler → cron job on campaign dates from Notion   │
    └────────────────────────────┬────────────────────────────────┘
                                 │
    ┌────────────────────────────▼────────────────────────────────┐
    │                 ORCHESTRATION LAYER (Cloud Run)             │
    │  campaign-runner service — reads Notion calendar,           │
    │  determines which campaigns are due, triggers pipeline      │
    └────────────────────────────┬────────────────────────────────┘
                                 │
             ┌───────────────────┼───────────────────┐
             │                   │                   │
    ┌────────▼────────┐ ┌───────▼────────┐ ┌────────▼────────┐
    │  CONTACT SYNC   │ │ TEMPLATE STORE │ │  RESULTS LOGGER │
    │  Cloud Run      │ │  Cloud Storage │ │  Cloud Run      │
    │  - CRM export   │ │  - Email copy  │ │  - Poll email   │
    │  - Segment      │ │  - Subject     │ │    platform     │
    │  - Dedupe       │ │    variants    │ │  - Write Notion │
    │  - Validate     │ │  - Prompt lib  │ │  - Update touch │
    │  - Import to    │ │                │ │    log          │
    │    email        │ └────────────────┘ └─────────────────┘
    │    platform     │
    └─────────────────┘
    

    Component 1: GCP Project Setup

    # Install gcloud CLI and authenticate
    gcloud auth login
    gcloud projects create restoration-crm-[yourcompany] --name="Restoration CRM Automation"
    gcloud config set project restoration-crm-[yourcompany]
    
    # Enable required APIs
    gcloud services enable \
      run.googleapis.com \
      cloudscheduler.googleapis.com \
      secretmanager.googleapis.com \
      storage.googleapis.com
    
    # Create service account for the automation
    gcloud iam service-accounts create crm-automation-sa \
      --display-name="CRM Automation Service Account"
    
    # Grant necessary permissions
    gcloud projects add-iam-policy-binding restoration-crm-[yourcompany] \
      --member="serviceAccount:crm-automation-sa@restoration-crm-[yourcompany].iam.gserviceaccount.com" \
      --role="roles/run.invoker"
    

    Component 2: Secret Manager for API Credentials

    Store all API credentials in GCP Secret Manager. Never hardcode credentials in source code.

    # Store each credential as a separate secret
    echo -n "your-servicetitan-api-key" | gcloud secrets create servicetitan-api-key \
      --data-file=-
    
    echo -n "your-jobber-api-key" | gcloud secrets create jobber-api-key \
      --data-file=-
    
    echo -n "your-mailchimp-api-key" | gcloud secrets create mailchimp-api-key \
      --data-file=-
    
    echo -n "your-notion-token" | gcloud secrets create notion-token \
      --data-file=-
    
    # In Python, access secrets like this:
    # from google.cloud import secretmanager
    # client = secretmanager.SecretManagerServiceClient()
    # name = f"projects/{project_id}/secrets/{secret_id}/versions/latest"
    # response = client.access_secret_version(request={"name": name})
    # secret_value = response.payload.data.decode("UTF-8")
    

    Component 3: Contact Sync Service

    This Cloud Run service handles the contact export → segment → validate → import pipeline. Deploy as a container triggered by the orchestration layer.

    # contact_sync/main.py
    
    import os
    import json
    import requests
    from google.cloud import secretmanager
    
    def get_secret(secret_id):
        client = secretmanager.SecretManagerServiceClient()
        project_id = os.environ.get("GCP_PROJECT_ID")
        name = f"projects/{project_id}/secrets/{secret_id}/versions/latest"
        response = client.access_secret_version(request={"name": name})
        return response.payload.data.decode("UTF-8")
    
    def export_jobber_contacts():
        """Export residential clients from Jobber API"""
        api_key = get_secret("jobber-api-key")
        
        # Jobber uses GraphQL API
        query = """
        query GetClients($after: String) {
          clients(first: 100, after: $after) {
            nodes {
              id
              firstName
              lastName
              emails { address isPrimary }
              tags { label }
              jobs(first: 1) {
                nodes { jobType completedAt }
              }
            }
            pageInfo { hasNextPage endCursor }
          }
        }
        """
        
        headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
        contacts = []
        cursor = None
        
        while True:
            variables = {"after": cursor} if cursor else {}
            response = requests.post(
                "https://api.getjobber.com/api/graphql",
                headers=headers,
                json={"query": query, "variables": variables}
            )
            data = response.json()
            clients = data["data"]["clients"]
            
            for client in clients["nodes"]:
                email = next(
                    (e["address"] for e in client["emails"] if e["isPrimary"]),
                    client["emails"][0]["address"] if client["emails"] else None
                )
                if not email:
                    continue
                
                # Determine segment based on tags
                tags = [t["label"].lower() for t in client["tags"]]
                if "residential" in tags or not any(t in tags for t in ["commercial", "adjuster", "vendor"]):
                    segment = "Homeowner"
                elif any(t in tags for t in ["adjuster", "agent", "insurance"]):
                    segment = "Industry"
                else:
                    segment = "Trade"
                
                # Get most recent job type
                job_type = None
                if client["jobs"]["nodes"]:
                    job_type = client["jobs"]["nodes"][0].get("jobType", "")
                
                contacts.append({
                    "first_name": client["firstName"],
                    "last_name": client["lastName"],
                    "email": email.lower().strip(),
                    "segment": segment,
                    "job_type": job_type or ""
                })
            
            if not clients["pageInfo"]["hasNextPage"]:
                break
            cursor = clients["pageInfo"]["endCursor"]
        
        return contacts
    
    def deduplicate_contacts(contacts):
        """Remove duplicate emails, keep most recent record"""
        seen = {}
        for contact in contacts:
            email = contact["email"]
            if email not in seen:
                seen[email] = contact
        return list(seen.values())
    
    def segment_contacts(contacts):
        """Split into three segment lists"""
        segments = {"Homeowner": [], "Industry": [], "Trade": []}
        for contact in contacts:
            seg = contact.get("segment", "Homeowner")
            if seg in segments:
                segments[seg].append(contact)
        return segments
    
    def import_to_mailchimp(contacts, tag, api_key, list_id):
        """Batch import contacts to Mailchimp with tag"""
        
        # Mailchimp batch operations (max 500 per call)
        batch_size = 500
        
        for i in range(0, len(contacts), batch_size):
            batch = contacts[i:i+batch_size]
            
            operations = []
            for contact in batch:
                operations.append({
                    "method": "PUT",
                    "path": f"/lists/{list_id}/members/{contact['email'].encode().hex()}",
                    "body": json.dumps({
                        "email_address": contact["email"],
                        "status_if_new": "subscribed",
                        "merge_fields": {
                            "FNAME": contact.get("first_name", ""),
                            "LNAME": contact.get("last_name", ""),
                            "JOB_TYPE": contact.get("job_type", "")
                        },
                        "tags": [tag]
                    })
                })
            
            response = requests.post(
                "https://us1.api.mailchimp.com/3.0/batches",
                auth=("anystring", api_key),
                json={"operations": operations}
            )
            
            if response.status_code not in [200, 201]:
                raise Exception(f"Mailchimp batch import failed: {response.text}")
        
        return len(contacts)
    
    def run_contact_sync(request):
        """Main Cloud Run handler"""
        mailchimp_api_key = get_secret("mailchimp-api-key")
        mailchimp_list_id = os.environ.get("MAILCHIMP_LIST_ID")
        
        contacts = export_jobber_contacts()
        contacts = deduplicate_contacts(contacts)
        segments = segment_contacts(contacts)
        
        results = {}
        for segment_name, segment_contacts in segments.items():
            count = import_to_mailchimp(
                segment_contacts,
                tag=segment_name,
                api_key=mailchimp_api_key,
                list_id=mailchimp_list_id
            )
            results[segment_name] = count
        
        return json.dumps({"status": "success", "imported": results})
    

    Component 4: Cloud Scheduler Trigger

    Cloud Scheduler runs the orchestration service on the campaign dates stored in your Notion calendar. The scheduler checks Notion weekly for upcoming campaigns and pre-triggers the contact sync 7 days before each scheduled send.

    # Create weekly scheduler job
    gcloud scheduler jobs create http crm-weekly-check \
      --schedule="0 9 * * 1" \
      --uri="https://[cloud-run-url]/check-upcoming-campaigns" \
      --oidc-service-account-email="crm-automation-sa@[project].iam.gserviceaccount.com" \
      --time-zone="America/Los_Angeles" \
      --location="us-west1"
    

    The orchestration service reads your Notion Campaign Calendar database, finds any campaigns with a send date within the next 7 days and a Status of “Scheduled,” and triggers the contact sync and campaign creation pipeline for each one.


    Component 5: Results Logger

    After each campaign sends, this service polls the Mailchimp or Brevo API for campaign analytics and writes them back to your Notion Campaign Calendar database.

    # results_logger/main.py (simplified)
    
    def log_campaign_results(campaign_id, notion_page_id):
        mailchimp_api_key = get_secret("mailchimp-api-key")
        notion_token = get_secret("notion-token")
        
        # Get Mailchimp campaign report
        response = requests.get(
            f"https://us1.api.mailchimp.com/3.0/reports/{campaign_id}",
            auth=("anystring", mailchimp_api_key)
        )
        report = response.json()
        
        open_rate = report.get("opens", {}).get("open_rate", 0)
        
        # Update Notion page
        notion_headers = {
            "Authorization": f"Bearer {notion_token}",
            "Content-Type": "application/json",
            "Notion-Version": "2022-06-28"
        }
        
        requests.patch(
            f"https://api.notion.com/v1/pages/{notion_page_id}",
            headers=notion_headers,
            json={
                "properties": {
                    "Status": {"select": {"name": "Sent"}},
                    "Open Rate": {"number": round(open_rate * 100, 1)}
                }
            }
        )
    

    Estimated Monthly GCP Costs

    For a single restoration company running 6 campaigns per year:

    Service Usage Monthly Cost
    Cloud Run (contact sync) 6 invocations/year, <5min each <$1
    Cloud Scheduler 52 weekly checks/year $0.10
    Cloud Storage (templates) Minimal storage <$0.01
    Secret Manager 4 secrets, <1000 accesses/month <$0.10
    Total <$2/month

    For an agency running this system for 10 restoration clients simultaneously, the cost scales linearly — approximately $15–20/month in GCP costs for the full multi-client operation. The manual labor savings at that scale are significant: an estimated 8–12 hours per month of manual campaign setup eliminated.


    Deployment Checklist

    • GCP project created and APIs enabled
    • Service account created with appropriate permissions
    • All API credentials stored in Secret Manager
    • Contact sync service containerized and deployed to Cloud Run
    • Cloud Scheduler job created and tested
    • Notion Campaign Calendar database connected
    • Results logger deployed and tested with a historical campaign
    • Full end-to-end test run on a staging contact list before live deployment

    Full documentation for each GCP service referenced here: cloud.google.com/run/docs, cloud.google.com/scheduler/docs, cloud.google.com/secret-manager/docs.


  • GCP Content Pipeline Setup for AI-Native WordPress Publishers

    GCP Content Pipeline Setup for AI-Native WordPress Publishers

    What Is a GCP Content Pipeline?
    A GCP Content Pipeline is a Google Cloud-hosted infrastructure stack that connects Claude AI to your WordPress sites — bypassing rate limits, WAF blocks, and IP restrictions — and automates content publishing, image generation, and knowledge storage at scale. It’s the back-end that lets a one-person operation run like a 10-person content team.

    Most content agencies are running Claude in a browser tab and copy-pasting into WordPress. That works until you’re managing 5 sites, 20 posts a week, and a client who needs 200 articles in 30 days.

    We run 122+ Cloud Run services across a single GCP project. WordPress REST API calls route through a proxy that handles authentication, IP allowlisting, and retry logic automatically. Imagen 4 generates featured images with IPTC metadata injected before upload. A BigQuery knowledge ledger stores 925 embedded content chunks for persistent AI memory across sessions.

    We’ve now productized this infrastructure so you can skip the 18 months it took us to build it.

    Who This Is For

    Content agencies, SEO publishers, and AI-native operators running multiple WordPress sites who need content velocity that exceeds what a human-in-the-loop browser session can deliver. If you’re publishing fewer than 20 posts a week across fewer than 3 sites, you probably don’t need this yet. If you’re above that threshold and still doing it manually — you’re leaving serious capacity on the table.

    What We Build

    • WP Proxy (Cloud Run) — Single authenticated gateway to all your WordPress sites. Handles Basic auth, app passwords, WAF bypass, and retry logic. One endpoint to rule all sites.
    • Claude AI Publisher — Cloud Run service that accepts article briefs, calls Claude API, optimizes for SEO/AEO/GEO, and publishes directly to WordPress REST API. Fully automated brief-to-publish.
    • Imagen 4 Proxy — GCP Vertex AI image generation endpoint. Accepts prompts, returns WebP images with IPTC/XMP metadata injected, uploads to WordPress media library. Four-tier quality routing: Fast → Standard → Ultra → Flagship.
    • BigQuery Knowledge Ledger — Persistent AI memory layer. Content chunks embedded via Vertex AI text-embedding-005, stored in BigQuery, queryable across sessions. Ends the “start from scratch” problem every time a new Claude session opens.
    • Batch API Router — Routes non-time-sensitive jobs (taxonomy, schema, meta cleanup) to Anthropic Batch API at 50% cost. Routes real-time jobs to standard API. Automatic tier selection.

    What You Get vs. DIY vs. n8n/Zapier

    Tygart Media GCP Build DIY from scratch No-code automation (n8n/Zapier)
    WordPress WAF bypass built in You figure it out
    Imagen 4 image generation
    BigQuery persistent AI memory
    Anthropic Batch API cost routing
    Claude model tier routing
    Proven at 20+ posts/day Unknown

    What We Deliver

    Item Included
    WP Proxy Cloud Run service deployed to your GCP project
    Claude AI Publisher Cloud Run service
    Imagen 4 proxy with IPTC injection
    BigQuery knowledge ledger (schema + initial seed)
    Batch API routing logic
    Model tier routing configuration (Haiku/Sonnet/Opus)
    Site credential registry for all your WordPress sites
    Technical walkthrough + handoff documentation
    30-day async support

    Prerequisites

    You need: a Google Cloud account (we can help set one up), at least one WordPress site with REST API enabled, and an Anthropic API key. Vertex AI access (for Imagen 4) requires a brief GCP onboarding — we walk you through it.

    Ready to Stop Copy-Pasting Into WordPress?

    Tell us how many sites you’re managing, your current publishing volume, and where the friction is. We’ll tell you exactly which services to build first.

    will@tygartmedia.com

    Email only. No sales call required. No commitment to reply.

    Frequently Asked Questions

    Do I need to know how to use Google Cloud?

    No. We build and deploy everything. You’ll need a GCP account and billing enabled — we handle the rest and document every service so you can maintain it independently.

    How is this different from using Claude directly in a browser?

    Browser sessions have no memory, no automation, no direct WordPress integration, and no cost optimization. This infrastructure runs asynchronously, publishes directly to WordPress via REST API, stores content history in BigQuery, and routes jobs to the cheapest model tier that can handle the task.

    Which WordPress hosting providers does the proxy support?

    We’ve tested and configured routing for WP Engine, Flywheel, SiteGround, Cloudflare-protected sites, Apache/ModSecurity servers, and GCP Compute Engine. Most hosting environments work out of the box — a handful need custom WAF bypass headers, which we configure per-site.

    What does the BigQuery knowledge ledger actually do?

    It stores content chunks (articles, SOPs, client notes, research) as vector embeddings. When you start a new AI session, you query the ledger instead of re-pasting context. Your AI assistant starts with history, not a blank slate.

    What’s the ongoing GCP cost?

    Highly variable by volume. For a 10-site agency publishing 50 posts/week with image generation, expect $50–$200/month in GCP costs. Cloud Run scales to zero when idle, so you’re not paying for downtime.

    Can this be expanded after initial setup?

    Yes — the architecture is modular. Each Cloud Run service is independent. We can add newsroom services, variant engines, social publishing pipelines, or site-specific publishers on top of the core stack.

    Last updated: April 2026

  • Digital Fortress — GCP Security Architecture

    Digital Fortress — GCP Security Architecture

    {“@context”: “https://schema.org”, “@type”: “Article”, “headline”: “Digital Fortress u2014 GCP Security Architecture”, “url”: “https://tygartmedia.com/digital-fortress-architecture-gcp-security/”, “datePublished”: “2026-04-04T01:51:02”, “dateModified”: “2026-04-04T01:51:02”, “author”: {“@type”: “Person”, “name”: “Will Tygart”}, “publisher”: {“@type”: “Organization”, “name”: “Tygart Media”, “url”: “https://tygartmedia.com”}}{“@context”: “https://schema.org”, “@type”: “BreadcrumbList”, “itemListElement”: [{“@type”: “ListItem”, “position”: 1, “name”: “Home”, “item”: “https://tygartmedia.com”}, {“@type”: “ListItem”, “position”: 2, “name”: “Digital Fortress u2014 GCP Security Architecture”, “item”: “https://tygartmedia.com/digital-fortress-architecture-gcp-security/”}]}

  • Wp Proxy Pattern Cloud Run — Article Hero Images Visual

    Wp Proxy Pattern Cloud Run — Article Hero Images Visual

    {“@context”: “https://schema.org”, “@type”: “Article”, “headline”: “Wp Proxy Pattern Cloud Run u2014 Article Hero Images Visual”, “url”: “https://tygartmedia.com/wp-proxy-pattern-cloud-run/”, “datePublished”: “2026-04-04T01:34:21”, “dateModified”: “2026-04-04T01:34:21”, “author”: {“@type”: “Person”, “name”: “Will Tygart”}, “publisher”: {“@type”: “Organization”, “name”: “Tygart Media”, “url”: “https://tygartmedia.com”}, “mainEntityOfPage”: {“@type”: “WebPage”, “@id”: “https://tygartmedia.com/wp-proxy-pattern-cloud-run/”}}{“@context”: “https://schema.org”, “@type”: “BreadcrumbList”, “itemListElement”: [{“@type”: “ListItem”, “position”: 1, “name”: “Home”, “item”: “https://tygartmedia.com”}, {“@type”: “ListItem”, “position”: 2, “name”: “Wp Proxy Pattern Cloud Run u2014 Article Hero Images Visual”, “item”: “https://tygartmedia.com/wp-proxy-pattern-cloud-run/”}]}

    About This Image

    This image is part of the Article Hero Images collection in the Tygart Media visual library. Every image produced by Tygart Media is AI-generated using Google Vertex AI (Imagen), converted to WebP format, and injected with full IPTC/XMP metadata before publication.

    Technical Details

    • Format: WEBP
    • Collection: Article Hero Images
    • Media ID: 357
    • Pipeline: Vertex AI Imagen → WebP → IPTC/XMP → WordPress

    Image Licensing

    All images in the Tygart Media visual library are produced in-house using AI image generation and are owned by Tygart Media.