GCP Cost: The 8-Year Bill for One Decision
The page you are reading this on is the system I am about to describe.
The same codebase runs three separate live websites and a beta environment. The code that rendered this page for you on the server, the admin panel I wrote its content in, and the jobs running in the background all sit in a single repository.
For a while now, part of that code has been written by AI agents. The tools they use, the skills they call and the rules they have to follow are in the same repository — including the agent that published the article you are reading. So the repository does not only run the site; it also describes how the site is to be developed.
The starting point: a plan made in 2018
Until 2018 my site ran on Drupal, and leaving was not the result of a complaint; it was part of a plan. I needed to move away from PHP towards Node.js and Angular, but the more decisive item was this: before I can recommend something, I should be using it myself. If I am going to recommend Google Cloud and Firebase to my clients, I would rather use them directly than go looking for an answer to “so why don't you use them?” The cloud was not new to me — I was working on Azure at the time and held certifications for it; even so, I chose GCP for my own system.
I had three reasons for the architecture I picked: cost, security and simplicity. On a managed platform there is no server whose upkeep is mine, no web server I configured, and no plugin left switched on; content sits there as data, not as an executable template.
Patching does not disappear altogether, of course — it moves. In place of an operating system and a web server come runtime versions and dependencies; Cloud Functions retires its runtimes on a schedule, and Angular has a release calendar of its own. The difference is this: these patches are planned rather than urgent, and most of them happen automatically in the deployment pipeline.
The simplicity part is a personal preference, but it describes precisely: GCP asks fewer questions. When you set something up, it makes most of the decisions itself with a sensible default. Azure asks you about every fine detail — which is sometimes exactly what you want, but every question means a decision that has to be answered and then remembered for years. Fewer questions, less maintenance.
Cost and security, in 2018, were both predictions. The table further down is what those predictions came to eight years later.
That first system ran two environments from 2018 onwards: beta and supermurat.com. The pipeline, the tests and the release flow were automated from day one — the code is still there to open and read today. But calling beta a second site would not be honest: technically it was a copy, closed to SEO and hidden from the outside.
In April 2024 I opened today's repository and made two decisions: to take a big version jump in Angular, and to move development to closed source. The reason was this — the company's own site was now going to join the system, and I was planning something with more features and further along. The AI side did not exist that day; that came in 2026.
What I built
A single monorepo built on Angular: the public site, the admin panel and the shared library the two of them use. Three live websites and beta sit as four separate GCP projects — their data, their quotas and their access rights entirely separate from one another.
Content lives in Firestore: articles, pages, the menu, the footer, contact details. Pages are rendered on the server on Cloud Functions and delivered from Firebase Hosting's cache. What separates the sites is written into the build configuration, not into the code — there is no “if this is that site” condition inside a component.
Most of the maintenance work sits as manually triggered jobs: generating the sitemap, refreshing llms.txt, filling in missing description and SEO fields, refreshing translation pairs. These are not scattered scripts — they all go through the same job queue and every run leaves its own record. July's incident came out of exactly this: forgetting to run one of them.
After every deployment, tests run against the live site; if they fail, the release rolls itself back. Backup is part of the system too: every Sunday night the whole of Firestore is exported automatically.
The detail of how it works is in a separate article: The Architecture You're Visiting: How This Site Runs on GCP
The architecture's test, twice over
The system's real claim was this: to run genuinely different sites from a single codebase. It has been put to that test twice.
The first was utopical.dev in 2024 — not a copy of supermurat.com, but a site whose assets and configuration are entirely its own. The second was sevgiyle.org in 2026: a different theme, different assets and a different purpose, live on 11 May 2026. Neither needed a single change to the architecture — one environment file, one stylesheet and one deployment pipeline. Not one line of conditional code in the shared code.
You cannot verify that from the outside right now; the system has been closed source since 2024. What you can verify is the same approach as it stood between 2018 and 2024: that repository is still public and sitting in the archive.
I also know exactly what it cost, because the table below is that month's bill: the new site's share of the total was ₺2.13 (~$0.04) — four and a half cents at today's rate.
So what does this cost?
The whole of the May 2026 bill — all four environments, meaning three live websites and the beta environment together.
| Line item | What ran that month | Bill (₺) | ≈ $ |
|---|---|---|---|
| Cloud DNS — managed zone | 3.7 zones/month | ₺33.38 | $0.69 |
| Secret Manager | 8.1 secret versions/month | ₺5.67 | $0.12 |
| Cloud Scheduler | 4 jobs · 114 job-days | ₺3.05 | $0.06 |
| Cloud DNS — queries | 162,408 queries | ₺2.93 | $0.06 |
| Cloud Storage | 0.46 GiB | ₺0.43 | $0.01 |
| Cloud Run Functions | 39,739 invocations · 49,221 CPU-seconds | ₺0.00 | $0.00 |
| Firestore | 132,015 reads · 238 writes | ₺0.00 | $0.00 |
| Firebase Hosting | 2.34 GiB egress | ₺0.00 | $0.00 |
| Cloud Build | 40.65 minutes | ₺0.00 | $0.00 |
| App Check (reCAPTCHA Enterprise) | 1,729 verifications | ₺0.00 | $0.00 |
| Total | ₺45.46 | $0.94 |
The dollar column, and every dollar figure below, uses the rate on 8 September 2026 (1 USD ≈ ₺48.5). Because the bills are issued in TRY and the rate moved over these eight months, the dollar figures are approximate — the monthly total is ₺45.46 ≈ $0.94.
What is interesting about this table is not its total but its distribution. Eighty per cent of the bill is Cloud DNS. Rendering the pages on the server, the job queue, the database reads, the hosting traffic, the builds — all of it stayed inside the free tier. That tier applies per project, so four projects mean four times the room; the current limits are on Firebase's pricing page. So the money that is paid goes to domain zones, secrets and the scheduler, not to computation.
Why Cloud DNS?
Because I want to manage everything from one place, and on domain resolution the quality of service is not something I negotiate over. Azure's and AWS's equivalents cost roughly the same; most of the free alternatives make no SLA/SLO commitment. Resolution is the site's bottom layer — saving money there means tying everything above it to a service with no guarantee. It does not trouble me that this is the largest line on the bill; it is a deliberate decision.
A date hidden inside the bill
The Cloud Scheduler line shows 114 units, and yet the system has exactly one kind of scheduled job, running once a week. The reason is this: Cloud Scheduler bills per day a job stands defined, not per trigger — whether it runs or not. Each of the four projects has one job; three of them were defined for all 31 days of May, and the fourth was created on the 11th. 3 × 31 + 21 = 114. And because the free allowance (three jobs) is granted per billing account rather than per project, the fourth one becomes chargeable. The scope of the free tier varies by service — Firestore, Functions and Hosting count per project, Cloud Scheduler per billing account. That is precisely where the most common arithmetic mistake in multi-project setups is made.
The same fractional structure is in the DNS line: 3.7 zones/month. Three zones for the whole month, and a fourth for about two thirds of it — around 11 May again. Two separate line items say the same thing independently of each other: sevgiyle.org was born that day. Above, I took that date from the day its pipeline was set up; the bill points at the same day.
Over the first eight months of 2026 I paid ₺414 (~$8.5) in total: between ₺28 (~$0.58) and ₺143 (~$2.95) a month, averaging ₺52 (~$1.07). I am not offering that figure as “the cloud is cheap”, because this is not a high-traffic site. What I am offering is something else: three live websites and the beta environment beside them, pages rendered on the server, an admin panel, search, bot protection, a weekly automatic backup and a pipeline that rolls back a failed deployment — all of it up and ready every second. At this scale, that is the price of being available.
July: the month the system reported itself
In July 2026 automated alert e-mails started arriving. The cause was not traffic, it was me: I had deleted sitemap.xml from its Firestore collection and forgotten to run the job that regenerates it. With the source missing, the promise chain in the function serving that request settled on no path at all; the request did not error, it simply waited. Cloud Functions' 60-second timeout would step in and cancel the request itself. The logs showed it plainly — every sitemap.xml request burned CPU for a minute and was then cut off by the timeout. The pages a visitor browses carried on working.
Here is the interesting part: no test caught this. The live suite that runs after deployment was browsing pages, not sitemap.xml — and the pages were working perfectly well. Because no exception was thrown, classic error tracking was silent too. What reported it was the platform's own monitoring: the automated alert, the logs and the system reports.
The bill was not the diagnosis but the consequence. That month it went up to ₺143 (~$2.95) — nearly three times the month before. The real lesson out of this: a timeout is a safety net, not a solution. Because Firebase cut the request at 60 seconds and protected itself, the system did not go down and no visitor noticed a thing; but every one of those 60 seconds was written onto the bill. A fault being swallowed silently does not mean it is not there — only monitoring will tell you that.
The second net watches the money directly: GCP's budget management sends an e-mail when a threshold is crossed and, depending on the setting, can suspend the service too. A timeout keeps the system standing; a budget alert makes you aware. They are different jobs and both are necessary.
The file went back, the promise chain was fixed to settle on every path, and August's bill returned to ₺56 (~$1.16).
The real fix: two layers
Putting the file back was not the fix; the real work happened in two layers.
The first is in the code. The route serving these endpoints now produces a response in every case: if the source is missing, the request does not hang, it returns a 404. The subtlety is here — that address is not a fixed list, it matches a family of names. So names like llms-full.txt, which a crawler would guess easily and which have no document behind them, fall onto the same path. What a generated output should do when its source is missing is fail gracefully, not break. My mistake was forgetting to run a job; the real defect was that forgetting could produce this outcome at all.
And this was more critical than I first thought: the SSR function runs on at most two instances, and hanging requests share that concurrency with every page on the site. So July's fault did not only burn money — had it coincided with slightly heavier bot traffic, it could have slowed the site down as well. That I learned about it through the bill means I got off lightly.
The second is in the tests. Until that day, the live suite running after deployment only browsed pages; now it also requests sitemap.xml, llms.txt and robots.txt, and verifies that sitemap.xml really returns a sitemap — because the function can return an error page with a 200 as well. If the same fault ever arrives with a deployment, the deployment no longer goes green; the system returns to the previous working release by itself.
What I have not closed
This net does not close every hole, and I need to write down the ones that are left:
- A fault arriving from the content side has no release to roll back to. In a situation like mine there is no deployment involved, so that line never engages at all; there, the alerts are still what protect me.
- I have no uptime check. Deliberately: polling the system continuously from the outside is a load of its own and does not pay for itself at this scale. So my phrase “up every second” above is an observation, not a measurement — and July showed where it ends.
- The ceiling on spending is tied down in two places. The function runs on at most two instances, and there is a threshold defined in budget management; if it is crossed I hear about it, and depending on the setting the service can be stopped too. I am not sharing the figure, but it is not an unlimited card.
- Visitor numbers are not in this article. Analytics is there, but because it runs subject to cookie consent it shows a lower bound rather than the real number. I would rather not present a figure for something I cannot measure properly — which is exactly why I can present the bill: there is no estimate in it.
Is this over-engineering?
For a few websites it may well look like over-engineering. Looked at purely as keeping those sites up, it is.
But the bill for over-engineering is usually money, and here that bill is a dollar a month — this system's “excess” is not costing anyone dearly. The real price is the one the table never shows: that price is time.
I spent that time deliberately, because this system has a second job to do for me: learning. It has been the best way I have found of keeping up with technology, genuinely applying what I learn, and getting to know a platform from the inside. Theory without practice takes you only so far; keeping a system in production for years teaches you what no course can — what breaks, when it breaks, and what it costs.
I have one regret, and it belongs precisely under this heading: I should have started learning AI far earlier. I could not have built the agent layer in 2024 — the tools were not mature enough that day to carry it. But when they did mature, I was not ready; I started learning after they had become useful, and that delay belongs to me, not to the tools. In a system that claims to put learning at its centre, ending up behind the technology rather than ahead of it was my most expensive mistake.
I need to draw a distinction as well: what I build in my own laboratory and what I build for a client are not the same thing. Here I carry four environments, four test suites and the agent layer on purpose, because this is a laboratory too. In a client's system, need sets the measure.
Why I am telling you this
Most client work cannot be written up. This project can: it is ours, it runs in production, and you can try everything I have described on the page you are on right now. That was the decision I made back in 2018 — to run what I recommend, first.
The three things I have done in this article are exactly the work we do in client systems:
- Reading the bill. Most teams do not know what the largest line on their cloud bill is. Knowing usually comes before saving.
- Finding the silent fault. A system whose pages load, which throws no errors and whose tests stay green while it burns money is the most expensive system there is.
- Writing the decision down. “Why Cloud DNS?” has to have an answer; every decision without one turns, a few years later, into a line nobody dares touch.
If one of those three is missing in your system, let's talk. And the easiest way to start is this article itself: let's look at your bill together. We will work out what your largest line is, why it is sitting there, and whether it really needs to be — the same thing I did to my own bill above.
Everything we do is on the our services page, and the GCP side in the GCP consulting article; if you would rather write directly, the contact page is here.