Multi-Cloud Without the Drama: AWS for Hosting, GCP for AI
Series: Building a Side Project That Runs Itself
- The Origin Story
- The Static Site Bet
- Kotlin for a CLI Backend
- Multi-Cloud Without the Drama (this post)
- The Pipeline That Runs While I Game
- Three Months In: Retrospective
“Multi-cloud” tends to get treated as either an enterprise buzzword or an overcomplicated mess that only startups with too many AWS credits stumble into. I’m using two cloud providers for Stockadora, and the reason is embarrassingly simple: AWS is what I know, and GCP has Gemini, which is what I needed.
That’s it. No hybrid cloud strategy. No vendor lock-in mitigation plan. Two clouds because each one does a specific thing well and the integration cost was low.
The AWS Side
AWS handles everything related to storage, hosting, and querying SEC index data.
S3 does the heavy lifting. There are two buckets: one for the raw and processed JSON data that the Kotlin CLI writes to, and one for the built Astro site that gets deployed daily. S3 is boring and reliable in exactly the way you want your storage layer to be boring and reliable.
CloudFront sits in front of the static site bucket and handles CDN distribution. The configuration is straightforward: origin points to the S3 bucket, cache behaviors tuned to let HTML files expire quickly while assets get longer TTLs. The daily build pipeline includes a CloudFront cache invalidation step so new content shows up promptly after deploy.
Route 53 manages DNS for the domain. Nothing interesting here — it’s DNS, it works.
ACM (Certificate Manager) handles the TLS cert for the CloudFront distribution. ACM certificates are free and auto-renewing. This is one of those things where AWS made the right call on pricing.
Athena + Glue + Iceberg is the most interesting part of the AWS setup. I use this for querying the EDGAR full-text search index — not for serving the site, but for the data pipeline that identifies what’s been filed recently.
The EDGAR filing index is a structured dataset that gets updated quarterly. Rather than loading it into a standing database that I’d have to maintain, I modeled it as an Iceberg table in S3, cataloged it with Glue, and query it with Athena. Pay-per-query means I pay nothing when the queries aren’t running, and the index fits comfortably in S3 without needing its own compute instance.
The trade-off is query latency — Athena isn’t fast in the way an always-on database is fast. But for a batch job that runs once a day, a query that takes 10 seconds versus 100ms genuinely doesn’t matter.
The GCP Side
GCP does one thing for this project: it runs Gemini. But getting here took a detour I wasn’t expecting.
The Bedrock Chapter Nobody Asked For
When I first needed AI in the stack, I went straight to AWS Bedrock. Of course I did — I already lived in AWS, I understood the IAM model, and Bedrock let me call foundation models without leaving the comfort of one billing dashboard. It made total sense at the time.
And it worked fine. “Fine” is doing a lot of work in that sentence.
The problem wasn’t that Bedrock was bad. It was that I kept looking at the Vertex AI model catalog and feeling like I was missing out. Gemini models in particular had capabilities I wanted — the multimodal integration, the long context window, the structured output reliability. Bedrock is a solid aggregator, but it’s ultimately a proxy for other people’s models. Vertex AI felt like going closer to the source for the model I actually cared about.
So I made the switch. Bedrock out, Vertex AI in. More on why that timing worked out suspiciously well in a moment.
The Credits Hustle
Here’s the part of this story I’m most proud of, and it has nothing to do with architecture.
Early on, I applied for AWS Activate — a program for startups that provides AWS credits. Got approved. Suddenly I had a comfortable runway to build Stockadora’s first version without watching every S3 API call like a hawk. I could actually experiment: spin up Athena queries freely, iterate on the pipeline, deploy without anxiety. That breathing room was the difference between a side project that shipped and one that stalled at “I should really get around to this.”
Once the site was live and doing something real — actual data, actual pages, actual users — I used that to apply for Google Cloud for Startups. A working product is a much stronger application than a pitch deck. Approved again, this time with a meaningful chunk of GCP credits.
And that’s when the architecture split happened. Not out of some grand multi-cloud vision — out of “I now have credits in two places, I should use both of them.” I moved the AI workloads to GCP to burn through the GCP credits, kept the infrastructure on AWS where I already had everything running. The credit constraints accidentally produced a pretty sensible separation of concerns.
I’m not going to pretend this was the plan all along. It wasn’t. But it worked out, and the lesson I’d take from it is: startup credit programs are underused by solo developers. You don’t have to be a funded company to apply. If you’re building something real, apply for everything.
Back to Vertex AI
Vertex AI is where I call Gemini 2.5 Flash for filing summaries and news digest generation. The migration from Bedrock was less painful than I expected — the API surface is different but the concepts are the same, and I wasn’t deeply coupled to any Bedrock-specific features.
The model selection on Vertex is what sealed it for me. Not just Gemini, but the range of specialized models and the pace at which new ones show up. For a project that’s going to run for years, I’d rather be on a platform where I can upgrade in place than one where I’m always waiting for the proxy to catch up.
On cost: Gemini Flash is meaningfully cheaper per token than GPT-4o or Claude for high-volume use. When you’re summarizing hundreds of filings per day, the per-token cost compounds. I ran the math and Gemini was significantly better for the “lots of medium-length documents” use case — which is exactly what SEC filings are.
On multimodal: I use Gemini’s image generation capability for the news digest feature — generating a thumbnail to accompany each daily digest. Minor feature, but having multimodal in the same API call is genuinely cleaner. With Bedrock or OpenAI, I’d be juggling a separate image generation integration. With Gemini it’s one client.
Honest assessment of output quality: it’s good. Occasionally it hallucinates details from filings, which I catch with validation logic. Structured output in JSON mode is reliable for well-defined schemas. Prose quality for the news digest is what you’d expect from a frontier model — capable enough that I don’t feel the need to switch.
Keyless Auth With OIDC
I want to be precise about what “keyless” actually means here, because I’ve seen this phrase used in ways that suggest you somehow escape the fundamental problem of identity. You don’t. You just handle it differently.
The real problem with traditional credential management isn’t that passwords exist — it’s that you end up maintaining multiple passwords for multiple parties. A long-lived API key for AWS in your CI environment. Another one for GCP. A service account JSON file you rotated six months ago and aren’t sure if the old one still works. A .env file that definitely shouldn’t have been committed to the repo that one time. The complexity compounds, and you become the party responsible for managing all of it.
What OIDC and SSO federation actually do is collapse that problem: identify yourself once, to a single trusted provider, and let IAM decide what you’re allowed to do from there.
The identity part still exists — it can’t not exist. It just lives in a different place depending on where the code is running:
-
On my laptop, I run
aws sso loginorgcloud auth login. Both forward me to my Google Workspace login, which is where my actual identity lives. From that point, AWS and GCP know who I am because Google vouched for me. I’m not storing any cloud credentials locally — I’m borrowing trust from an identity system I already use every day. -
On GitHub-managed runners, I’m not identifying a human at all. GitHub handles the machine’s identity. When a job runs in my repo, GitHub generates a signed token that says “this job is
org/repo, running on branchmain, triggered by push.” AWS and GCP trust GitHub as an identity provider, so they accept that token and issue temporary credentials scoped to what that repo is allowed to do. No keys anywhere. GitHub’s infrastructure problem, not mine. -
On my self-hosted runners — yes, I have a rack in my living room with several mini PCs that run longer pipeline jobs — the machine identity does require upfront configuration. Private keys get installed, trust gets established. That work happens once, when I add a machine to the pool, not continuously for every integration I wire up.
The through-line in all three cases is the same: the cloud services aren’t asking “what’s your password?” They’re asking “can someone I already trust vouch for you?” Once that’s answered, IAM takes over and determines what’s actually permitted.
The complexity isn’t reduced. It’s redistributed. Google is handling the hard problem of human identity at scale. GitHub is handling the hard problem of ephemeral machine identity in CI. My self-hosted machines handle their own bootstrapping. I get to skip all of that and just define IAM policies.
I’ll dig into the GitHub Actions side of this in the next post — there’s enough detail there to warrant its own section. For now: no access keys, no service account JSON files, no rotation schedule. The security-heavy parts are someone else’s problem, and that’s exactly how I want it.
Terraform as the Solo-Dev Superpower
I’m not going to write yet another post about why Infrastructure as Code is good. You’ve heard it. Version control. Reproducibility. Auditability. All true, all valid, moving on.
What I will say is that the benefits hit differently when you’re the only person on the team. There’s no “ask the platform engineer who set this up” option. If something in the AWS config is wrong — a misconfigured bucket policy, an IAM role with too-broad permissions, a CloudFront behavior that’s caching the wrong things — the Terraform code is the authoritative record of what was intended. Not a console someone clicked through at 11pm six months ago. Code, in a repo, with a git history.
The audit trail matters. But what matters more to me right now is something I didn’t fully anticipate when I started: the Terraform code is the primary interface between my infrastructure and AI agents.
When I want Claude Code or Codex to help me investigate an infrastructure problem or suggest a change, I point them at the Terraform. They can read the resource definitions, trace dependencies, spot misconfigurations, and propose diffs — all without needing to navigate a cloud console or be handed credentials. The code is the full picture. That feedback loop is fast in a way that “go check the AWS console” fundamentally isn’t.
This has changed how I think about what IaC is actually for. It’s not just documentation for humans. It’s a machine-readable description of your infrastructure that AI tooling can reason about. The cleaner and more complete your Terraform, the better that collaboration works.
The multi-cloud setup is two separate provider blocks — one for AWS, one for GCP — with separate state files to keep them isolated. Cross-cloud resources that reference each other (like the OIDC trust policy in AWS that needs GCP’s token issuer URL) are wired via Terraform data sources, which keeps the dependency explicit and in the code rather than in someone’s head.
The Honest Multi-Cloud Tax
I want to be real about what having two cloud providers costs beyond the billing.
Two billing dashboards. Two cost anomaly alerts to configure. Two sets of quota limits to monitor. Two IAM systems with different models (AWS IAM policies vs. GCP IAM roles are similar in concept but different enough in implementation to require separate mental models).
At the scale Stockadora runs at, this overhead is manageable — I can see my entire monthly bill in about 30 seconds across both consoles. But I’d be lying if I said there’s no overhead. If I were building this with a team, the coordination cost of maintaining expertise across two cloud providers would be real.
For a solo developer with familiarity in both environments, it’s fine. For someone new to either cloud, I’d recommend picking one and staying there until you have a compelling reason to cross over.
Next up: the GitHub Actions pipeline that ties everything together and runs the whole show while I’m doing something else entirely.