Every backend service behind Players Engine runs on Cloud Run with the minimum instance count set to zero. When nobody needs a service, it doesn't exist, and it doesn't bill. The interest payment on that deal is the cold start: the first request after a quiet period has to wait for a container to be pulled, booted and made ready. This entry is about keeping that payment small — because the alternative, paying for idle instances around the clock, is exactly the kind of cost a bootstrapped platform refuses on principle.
Anatomy of the pause
A cold start isn't one delay, it's a stack of them: the platform schedules an instance, pulls the container image (or finds it cached), starts the runtime, runs every line of your top-level code, and only then hands over the request. You control almost none of the first half and almost all of the second. Which is good news, because in my measurements the second half — what your own code does before the server can accept traffic — is where a Node service usually wastes the most time.
Boot only what boot needs
The single biggest win was an audit of what actually runs at
import time. A web framework like Fastify boots fast on its own;
what drags is everything the module graph drags in with it. Every
top-level await connect(), every SDK that eagerly
phones home, every heavyweight library imported at the top of a
file "because that's where imports go" — all of it runs before
the service can say hello.
The discipline that stuck: startup code may validate, but it may not connect. Configuration is checked synchronously at boot — if an environment variable is missing, the service should die immediately and loudly, a rule I've written about in the config entry. Connections, on the other hand, are established on first use and then cached for the life of the process. Validation is microseconds; a TLS handshake to a database is not.
Lazy clients, module singletons
Concretely, every external client — document database, graph database, pub/sub, storage — lives in a module that exports a getter, not an instance. First call constructs the client and stores it at module scope; every later call gets the cached one. The first request after a cold start eats one connection setup, and requests two through ten thousand ride the warm client. It's three lines of boilerplate per client, and it converts "every service pays every connection cost at boot" into "each service pays only for what this request actually touches."
Ship less container
Image size matters less than folklore says once layers are cached in a region, but it still matters on the unlucky pulls. The checklist that earned its keep: a slim base image, production dependencies only (dev tooling never ships), build artifacts compiled ahead of time rather than transpiled on boot, and dependency hygiene — the transitive weight of a convenience library is a real number someone eventually pays in milliseconds. Running the bundle-size check is now part of reviewing any new dependency.
Measure like you mean it
It's easy to lie to yourself about cold starts because the warm path is what you test all day. Two habits keep the numbers honest. First, the structured logs tag every request with the instance's age, so "first request on a fresh instance" is a filter, not a guess. Second, what I watch is the p95 of that filtered set, not the average — the average is flattered by mild cases, and the p95 is what an actual person hits when they open the app after lunch. Watching the right number is most of the work; the fixes above came directly from seeing which services' first hits were slow and reading their boot paths.
The escape hatch we're not using yet
Cloud Run will happily keep instances warm for money — set minimum instances to one and cold starts vanish for the price of a small always-on server per service. Multiply by a dozen services and it's real money, for latency that today's traffic doesn't justify. The decision rule is written down: when a service's cold starts affect enough sessions per day that the warm-instance cost divided by affected sessions drops below what I'd pay per good first impression, that service gets a warm floor. A couple of user-facing services will cross that line first. The rest can keep sleeping — frugally.