September 11, 2026 Telegram Aws Lambda S3 Cloudflare Workers R2 Go
In the previous article I described how I fitted a Telegram volleyball organizer bot into the AWS Free Tier: Lambda + S3 + EventBridge, at about zero dollars. That setup ran for months. Then I moved the same bot to Cloudflare Workers and R2 — the same Go, the same JSON state files, the same Gemini router.
Why touch something that was already free? Cold start. A Lambda on the provided.al2023 custom runtime wakes up in hundreds of milliseconds after idle time, sometimes more than a second. You feel that on a Telegram webhook: the first update after a quiet stretch is slower than the next one. A Workers isolate comes up in tens of milliseconds. On a real deploy of this bot, Wrangler reported Worker Startup Time of 16–37 ms.
Below is a practical comparison of AWS, GCP, Azure, and Cloudflare free tiers for the two things this bot needs: Lambda-style compute and S3-style object storage. Numbers are a snapshot for September 2026 — I always re-check the official pages before production.
The product did not change: Telegram webhook, Gemini as a router, JSON state for games and chat memory, pre/after-game jobs, and a Gmail poll for rent payments.
On AWS that meant several Lambdas and EventBridge one-shots (at(...)) per game. On Cloudflare:
state/game_state.json and friends).Workers have no one-shot “wake me on 12 September at 15:30”. They also have no zoo of functions: webhook and cron are the same binary with two entrypoints.
| AWS Lambda | GCP Cloud Run functions | Azure Functions (Consumption) | Cloudflare Workers | |
|---|---|---|---|---|
| Free invocations | 1M / month, always free | 2M / month, always free | 1M / month (Consumption grant) | Free: 100,000 / day (~3M / month). Paid: 10M / month in the $5 plan |
| Compute | 400,000 GB-s / month | 400,000 GB-s + 200,000 GHz-s | 400,000 GB-s | Free: 10 ms CPU per invocation. Paid: 30M CPU-ms / month |
| What “duration” means | wall-clock × memory | wall-clock × memory/CPU | wall-clock × memory | CPU time, not HTTP wait |
| Cold start | hundreds of ms (Go custom runtime) | hundreds of ms … seconds | often seconds | tens of ms (isolate) |
| Go | native provided.al2023 | native | custom handler | Wasm (GOOS=js) |
Sources: AWS Lambda pricing, Google Cloud Free Tier, Azure Functions pricing, Workers pricing, Workers limits.
The three hyperscalers look alike: millions of requests and hundreds of thousands of GB-seconds. That is enough for a small bot. The real difference is how the runtime wakes up, and whether the storage free tier expires after a year.
Classic Lambda boots a micro-VM, pulls a runtime, then your zip/image. For Go on provided.al2023 that is usually hundreds of milliseconds. GCP Gen2 is closer to Cloud Run: better than first-gen Functions, still a container cold start. Azure Consumption is the worst of the four: after idle, the first request often lands in seconds.
Workers are not containers. They are V8 isolates. Cloudflare’s startup limit is one second; my Wasm worker actually started in 16–37 ms. Warm isolates are faster still. For a chat bot that matters more than the “1 million requests” row: people notice a pause, not GB-seconds.
Billing is different too. While the Worker waits on Gemini, wall-clock passes and CPU barely moves. Lambda charges for the whole time the function is alive. Workers count CPU time, not seconds spent waiting on HTTP.
Workers Free is generous on request count: 100k/day is already more than always-free Lambda (1M/month). Since 4 September 2026, Free and Paid share one size limit: 64 MiB uncompressed. My Go Wasm is about 39 MiB Total Upload, so it deploys without an upgrade.
10 ms CPU per Free invocation sounds tight for a Gemini bot, but waiting on HTTP barely burns CPU. While the model thinks, the isolate is idle on the network. Production for this bot stays on Workers Free: Subscriptions shows Workers Free, not $5/month.
Paid at $5 is worth it if you hit the 10 ms CPU cap or 100k requests/day. I have not needed it yet.
R2 may show up as its own product (R2 Paid) — that is how Cloudflare turns storage billing on. The always-free allowance remains: 10 GB, 1M Class A, 10M Class B. For JSON bot state that is still zero dollars.
| AWS S3 | GCP Cloud Storage | Azure Blob | Cloudflare R2 | |
|---|---|---|---|---|
| Free storage | 5 GB for the first 12 months | 5 GB-month always free, only us-central1 / us-east1 / us-west1 | 5 GB LRS for the first 12 months (Free Account) | 10 GB / month, always free |
| Reads | 20,000 GET / month (year 1) | 50,000 Class B | ~20,000 reads (year 1) | 10M Class B / month |
| Writes | 2,000 PUT / month (year 1) | 5,000 Class A | ~10,000 writes (year 1) | 1M Class A / month |
| Egress | capped, then $/GB | 100 GB (limited regions) | billed separately | free |
| After the grant | ~$0.023/GB, PUT $0.005/1k | normal GCS rates | the storage account is not in the Functions grant | $0.015/GB beyond the grant, still no egress |
Sources: AWS S3 pricing, GCP Free Tier, Azure Functions pricing (storage billed separately), R2 pricing.
In the AWS article the S3 bottleneck was 2,000 PUT requests in year one. Lazy load and a single write at the end of the handler existed because of that. R2 allows a million Class A operations every month, always free. A bot that writes a few JSON files per update lives in a different order of magnitude.
Azure’s catch: the Functions grant does not cover the storage account created with every Function App. “Free functions” can still drip Blob charges.
GCP Cloud Storage is always free, but only in three US regions, and Class A is 5,000/month. Fine for JSON state, tight if you PUT on every message.
R2 also has no egress fee. For this bot that barely matters (state is tiny). As a free S3 analog it is the most honest line in the table: 10 GB + lots of ops + no “first year” asterisk.
I did not rewrite the bot. The runtime and the scheduler changed:
GOOS=js GOARCH=wasm instead of linux/amd64;http.DefaultClient via Workers fetch (plain net/http on JS dies with Illegal invocation);Europe/Istanbul via embedded time/tzdata — Wasm has no IANA zoneinfo;*/10 * * * *.The accuracy trade-off: after-game can be up to 10 minutes late. Fine for “thanks for the game”. The win: one deploy, isolate start an order of magnitude faster than a typical Lambda cold start, and object storage whose always-free limits do not need to be massaged around 2,000 PUTs.
If you only compare “free serverless” by millions of requests, AWS, GCP, and Azure look almost the same. Add cold start and how long the storage grant lives, and the ranking changes.
I parked this bot on Workers + R2. Not because AWS stopped being free, but because the first update after a pause no longer waits for Lambda to wake up.

For over three years, I have been organizing volleyball games in our Telegram community. At first, we used standard Telegram polls, but the routine kept growing, so I decided to automate the process. That is how an AI-powered organizer bot was born, and the whole architecture is built around it.
Read More → Telegram Aws Lambda S3 AiThis article is a continuation of “Building an AI Telegram Bot with Go, Gemini API, and AWS Lambda” and contains detailed instructions for setting up and deploying a Telegram bot to AWS Lambda using Function URL.
Read More → Go Telegram Aws Lambda Deploy Function-Url CliIn this article, we’ll explore how to build an intelligent Telegram bot using Go that acts as a proxy between users and Google’s Gemini API. The bot will handle two primary functions: answering user messages and generating images. While this mechanism can be significantly extended with additional capabilities like voice and video generation, we’ll focus on these two request types for simplicity.
Read More → Go Telegram Gemini Ai Aws Lambda Bot