Skip to main content

Invoice Projects — GCP Infrastructure

Scope: GCP infrastructure only — Cloud Run, Cloud Functions, Pub/Sub, VPC, Artifact Registry, Document AI, Cloud Storage. Does not cover application code, frontend, or database internals.


GCP Project

Both projects share a single GCP project:

FieldValue
Production Projectacademic-torch-405913
Dev Projecttrashy-dev-481621
Regionus-central1
IaCTerraform (prod + dev environments)
Build ServiceGoogle Cloud Build (via deploy.sh)
Secret ManagementGCP Secret Manager

Invoice Listener

Automated invoice ingestion pipeline. Watches email and OneDrive for incoming invoices, processes them through Document AI, and feeds results to invoice-management.

Cloud Run Services

Always-on HTTP endpoints that receive Pub/Sub push messages:

Service NamePurpose
wasteololgy-email-handlersProcesses emails pushed from Pub/Sub
wasteololgy-onedrive-handlersProcesses OneDrive files pushed from Pub/Sub
note

Service names contain a typo — wasteololgy (double "l", missing "g") instead of wasteology. This is the actual resource name in GCP.

Cloud Run Jobs

Scheduled tasks that run on a timer, do their work, and exit:

Job NameScheduleWhat It Does
wasteologyinvoice-email-queued-listenerEvery 10 minPolls email inbox for new invoices
wasteologyinvoice-onedrive-listener-queuedEvery 1 hourPolls OneDrive for new invoice files
wasteologyinvoice-error-email-processorEvery 1 hourRetries previously failed/errored emails

VPC & Networking

All Cloud Run resources are attached to a dedicated VPC:

ResourceName
VPCwasteology-invoice-processing-vpc
Subnetlisteners
Egress modeall-traffic

The YAML configuration on every Cloud Run resource:

run.googleapis.com/vpc-access-egress: all-traffic
run.googleapis.com/network-interfaces:
- network: wasteology-invoice-processing-vpc
subnetwork: listeners

All outbound traffic routes through the VPC, which requires Cloud NAT for internet access. The VPC has no private resources inside it — it exists purely to provide a stable outbound IP for external API calls (Microsoft Graph, Azure SQL, Document AI, etc.).

Optimization

Since there are no private resources in the VPC, this could be simplified by either removing the VPC entirely (use Cloud Run default egress) or switching to private-ranges-only egress to avoid Cloud NAT costs.

Artifact Registry

us-central1-docker.pkg.dev/academic-torch-405913/wasteology-invoice-processing/

Container images are built locally and pushed here. Three image types are built from the same repo (HttpApp, ListenerApp, QueueApp).

Document AI

FieldValue
Processor ID9e41876640558aee
Locationus (via us-documentai.googleapis.com)
PurposeOCR + entity extraction from invoice PDFs

Cloud Storage

BucketPurpose
invoice_inference_json_outputDocument AI JSON output + processed PDFs. Handoff point — listener writes here, invoice-management reads from here.

BigQuery

Dataset.TablePurpose
ParsedInvoices.responsesRaw Document AI entity extraction results

Invoice Management (Trashy)

Full-stack invoice management application. Receives invoices from the listener pipeline and provides a web UI for managing them.

Cloud Run Services

Service NamePurpose
invoice-management-backendFastAPI application (~66 REST endpoints)
invoice-management-frontendNginx serving React build (port 8080)

Cloud Functions

Three Cloud Functions form an event-driven processing pipeline, each triggered by a Pub/Sub topic:

StepCloud FunctionTrigger TopicWhat It DoesPublishes To
1doc_ai_processorinvoice-uploadedCalls Document AI for OCRinvoice-extracted
2db_inserterinvoice-extractedInserts extracted data into Cloud SQLinvoice-persisted
3webservice_callerinvoice-persistedCalls external APIs (address lookup, assignment)

The chain starts when the listener either:

  • POSTs to the backend's /invoices/ingest endpoint, or
  • Publishes to the send-to-trashy Pub/Sub topic

Pub/Sub Topics

TopicPublisherSubscriber
send-to-trashyinvoice-listenerinvoice-management
invoice-uploadedBackend ingest endpointdoc_ai_processor function
invoice-extracteddoc_ai_processordb_inserter function
invoice-persisteddb_inserterwebservice_caller function

Cloud SQL

FieldValue
TypePostgreSQL
Projectacademic-torch-405913
Schematrashy

Cloud Storage

BucketPurpose
wasteology-invoices-pdf-dev / wasteology-invoices-pdf-prodPrimary PDF storage
Legacy GCS bucketFallback read location for older files

Storage strategy: GCS first, fallback to Azure Blob for historical PDFs.


How the Two Projects Connect

  invoice-listener                              invoice-management
┌──────────────────────┐ ┌──────────────────────────┐
│ │ │ │
│ Cloud Run Jobs │ │ Cloud Run Services │
│ (scheduled polling) │ │ (backend + frontend) │
│ │ │ │ ▲ │
│ ▼ │ │ │ │
│ Cloud Run Services │ │ Cloud Functions │
│ (Pub/Sub handlers) │ │ (processing chain) │
│ │ │ │ ▲ │
│ ▼ │ 3 handoffs │ │ │
│ Document AI │ ──────────────────>│ │
│ │ │ │ 1. Pub/Sub topic │
│ ▼ │ │ "send-to-trashy" │
│ GCS bucket ─────────│── shared bucket ──>│ │
│ (invoice_inference_ │ │ 2. GCS bucket │
│ json_output) │ │ (invoice_inference_ │
│ │ │ │ json_output) │
│ ▼ │ HTTP POST │ │
│ BigQuery │ ──────────────────>│ 3. POST /invoices/ingest│
│ │ │ │
└──────────────────────┘ └──────────────────────────┘

The Three Handoff Points

#MechanismFromTo
1Pub/Sub topic send-to-trashyListener publishes after processingManagement subscribes, triggers Cloud Function chain
2GCS bucket invoice_inference_json_outputListener writes Document AI JSON + PDFsManagement reads for display and storage
3HTTP POST to /invoices/ingestListener calls backend API directlyManagement ingests and starts processing chain

Complete Resource Inventory

Shared Resources

Resource TypeName / IDUsed By
GCP Projectacademic-torch-405913Both
Document AI Processor9e41876640558aeeBoth (listener directly, management via Cloud Functions)
GCS Bucketinvoice_inference_json_outputListener writes, Management reads
Pub/Sub Topicssend-to-trashy, invoice-uploaded, invoice-extracted, invoice-persistedPipeline chain

Invoice Listener Resources

Resource TypeNameNotes
Cloud Run Servicewasteololgy-email-handlersTypo in name
Cloud Run Servicewasteololgy-onedrive-handlersTypo in name
Cloud Run Jobwasteologyinvoice-email-queued-listener10 min schedule
Cloud Run Jobwasteologyinvoice-onedrive-listener-queued1 hour schedule
Cloud Run Jobwasteologyinvoice-error-email-processor1 hour schedule
VPCwasteology-invoice-processing-vpcAll-traffic egress
SubnetlistenersInside VPC
Cloud NAT(associated with VPC)Required for outbound internet
Artifact Registrywasteology-invoice-processing/Container images
BigQueryParsedInvoices.responsesDocument AI results

Invoice Management Resources

Resource TypeNameNotes
Cloud Run Serviceinvoice-management-backendFastAPI
Cloud Run Serviceinvoice-management-frontendNginx + React
Cloud Functiondoc_ai_processorPub/Sub triggered
Cloud Functiondb_inserterPub/Sub triggered
Cloud Functionwebservice_callerPub/Sub triggered
Cloud SQLPostgreSQL instancetrashy schema
GCS Bucketwasteology-invoices-pdf-dev / prodPrimary PDF storage

Terraform (Infrastructure as Code)

Both projects are managed by Terraform with separate environment configurations:

Environments

EnvironmentGCP ProjectTerraform State
Productionacademic-torch-405913GCS wasteology-terraform/terraform/state/prod
Devtrashy-dev-481621GCS wasteology-terraform/terraform/state/dev-v2

What Terraform Manages

ResourceProdDev
Cloud SQL (PostgreSQL 17)Regional HA, deletion protection ONZonal, deletion protection OFF
Cloud Run ServicesBackend, frontend, 4 handler servicesSame, with corrected names (no typo)
GCS Bucketsinvoice_inference_json_output, wasteology-invoices-pdf, wasteology-invoices-2Prefixed dev equivalents
Document AIProcessor 9e41876640558aeeDev processor
Pub/Sub Topics5 topicsSame
VPC + Private NetworkVPC peering for Cloud SQLSame
info

Terraform manages infrastructure shape but not container images. Image updates happen via the deploy script. A lifecycle { ignore_changes = [image] } block prevents Terraform from reverting deployments.


Secret Manager

Secrets are stored in GCP Secret Manager:

Backend Secrets (8)

Secret NamePurpose
invoice-management-database-urlPostgreSQL connection string
invoice-management-entra-client-secretAzure AD auth
invoice-management-azure-storage-csAzure Blob Storage (prod only)
invoice-management-msgraph-client-secretMicrosoft Graph API
invoice-management-address-api-passwordAddress API credentials
invoice-management-address-api-usernameAddress API credentials
invoice-management-db-userPostgreSQL user
invoice-management-db-passPostgreSQL password

Handler Secrets (7)

Secret NamePurpose
handler-address-api-passwordAddress API
handler-address-api-usernameAddress API
handler-msgraph-client-secretMicrosoft Graph
handler-queue-connection-stringAzure Storage Queue
handler-storage-connection-stringAzure Blob Storage
handler-trashy-api-client-secretTrashy API service principal
handler-wasteology-connection-stringAzure SQL ODBC

Deployment

Deployments use Google Cloud Build as the build service, initiated manually via a deploy script (gcp_deployment/deploy.sh).

note

Cloud Build YAML templates (cloudbuild.backend.yaml, cloudbuild.frontend.yaml) exist in the repo for future automated trigger-based CI/CD. Currently there are no git-push triggers configured.

Invoice Listener

# Manual: build locally, push, replace YAML
docker build -t us-central1-docker.pkg.dev/academic-torch-405913/wasteology-invoice-processing/<image>:<tag> .
docker push us-central1-docker.pkg.dev/academic-torch-405913/wasteology-invoice-processing/<image>:<tag>
gcloud run services replace cloudrun/<service>.yaml
gcloud run jobs replace cloudrun/<job>.yaml

Invoice Management

# deploy.sh handles the full lifecycle
./gcp_deployment/deploy.sh --all # Deploy everything
./gcp_deployment/deploy.sh --backend # Backend only
./gcp_deployment/deploy.sh --frontend # Frontend only
./gcp_deployment/deploy.sh --function <name> # Single Cloud Function

What deploy.sh does:

  1. Enables required GCP APIs
  2. Sets up VPC + Cloud NAT with static outbound IP
  3. Creates/updates secrets in Secret Manager
  4. Grants IAM roles to the compute service account
  5. Submits Docker builds to Cloud Build (gcloud builds submit)
  6. Deploys Cloud Run services, jobs, and Cloud Functions

Cloud Build Pipeline

Source Upload → Cloud Build → Docker Build → Artifact Registry → Cloud Run Deploy

Known Infrastructure Issues

#IssueProjectImpact
1Secrets hardcoded in Cloud Run YAMLsListenerManagement uses Secret Manager; listener still uses plaintext env vars
2Typo in Cloud Run service names (wasteololgy)ListenerCosmetic; dev environment has corrected names
3No automated CI/CD triggersBothBuilds use Cloud Build but are initiated manually
4VPC with all-traffic egress + Cloud NATListenerPotentially unnecessary cost
5No Alembic/migration toolingManagementSchema changes to Cloud SQL are manual

External Dependencies

Services the GCP infrastructure connects to outside of GCP:

ServiceEndpointUsed By
Azure SQL Serverwasteology.database.windows.netListener
Azure Blob Storagepdfinvoices.blob.core.windows.netListener (legacy)
Microsoft Graph APIgraph.microsoft.comListener (email, OneDrive, SharePoint)
Address APIapi.politeground-1242c12a.centralus.azurecontainerapps.ioBoth
Microsoft Entra IDAzure AD tenantManagement (auth)