Cloud Run is attractive because it keeps the operational surface small. I can package an application as a container, deploy it without managing a server, and allow the service to scale with traffic. That makes it a good fit for small products and APIs that need a professional deployment model without a full platform team.
The command that creates a service is not the difficult part. Most deployment problems appear in the details around it.
Start with the container contract
Before involving the cloud, I want the application to behave correctly as a container on my machine.
The service must listen on the port supplied through the PORT environment variable and bind to all interfaces, not only localhost. It should start without writing required state to its local filesystem because an instance can be replaced at any time. It should also shut down cleanly when the platform sends a termination signal.
I keep the image focused on runtime needs. A multi-stage build can install dependencies and compile the application in one stage, then copy only the production output into a smaller final image. This reduces build size and limits unnecessary tools in production.
Separate configuration from the image
An image should be deployable to more than one environment. Database addresses, API credentials, and environment-specific behavior do not belong inside it.
Normal configuration can be supplied as environment variables. Secrets should come from Secret Manager and be made available to the service through a narrowly scoped service account. This also makes credential rotation possible without rebuilding source code.
The service account deserves attention. Broad project roles are convenient during an experiment but risky in production. I prefer to grant only the permissions the application needs—for example, access to a specific secret or the ability to connect to a particular database.
Think about the database separately
Cloud Run can create and remove instances in response to traffic. A relational database cannot accept an unlimited number of connections just because the application can scale.
Connection pooling, maximum instance counts, and per-instance concurrency need to be considered together. If every new container opens a large pool, a traffic spike may exhaust database connections before application compute becomes the limiting factor.
Database migrations also need one clear owner. Running migrations automatically in every starting container creates races. A dedicated release step or job is safer.
Make health visible
A successful HTTP response from the homepage does not prove that the application is healthy.
I add a lightweight health endpoint and distinguish between two questions:
- Is the process alive?
- Is it ready to handle real work?
Readiness may depend on required configuration or a database connection, but the check should remain fast and predictable. An overloaded health endpoint can create the failure it is meant to detect.
Structured logs are equally important. A production log should include enough context to connect a request to an error without including credentials or sensitive customer data. Request identifiers, severity levels, and clear error boundaries make Cloud Logging much more useful during an incident.
Automate the release
Manual deployment is acceptable while learning the platform. It is not a dependable release process.
My preferred pipeline runs the same quality gates on every release:
- install from the lockfile;
- run type checks, tests, and linting;
- build the container;
- scan or validate the resulting image;
- publish it with an immutable identifier;
- deploy that exact image;
- and verify the new revision.
Using a commit SHA as the image tag makes it possible to identify precisely what is running and to roll back to a known revision.
What “done” means
I consider a Cloud Run deployment complete when it is repeatable, observable, and recoverable—not merely reachable.
That means configuration is outside the image, secrets are protected, permissions are narrow, database connections are bounded, logs are useful, and a previous revision can be restored. Cloud Run removes a great deal of infrastructure work, but it does not remove the need for operational thinking. It gives me a smaller, clearer place to apply it.