GCP
Software Mile develops on Google Cloud Platform – strong for data, analytics, and container-native workloads, and a natural fit for teams that value Kubernetes and BigQuery.
Where GCP Is Strong
- GKE and Cloud Run for containerized and serverless applications
- BigQuery and the data/analytics stack for teams that live in their data
- Firebase for rapid mobile and web backends
- Identity, networking, and IaC built the GCP-native way
GCP earns its place on data-heavy and container-first workloads. Tell us what you are building and we will design for the platform.
Picking a Cloud Is Mostly Not a Technical Decision
Any of the major clouds will run a standard web application with a relational database. The deciding factors are usually elsewhere: what your team already knows, what agreements you already hold, where your data has to live, and who will be on call when it breaks overnight. Picking a cloud from a feature comparison and hiring for it afterward is how teams end up running infrastructure nobody understands.
GCP makes a stronger case when the workload is data-heavy, when you want a managed Kubernetes service with the control plane handled for you, or when you want to run containers without operating a cluster at all. Those are real advantages. On their own they rarely justify moving a system that already runs well.
Cloud Run or GKE?
Kubernetes is the reflex answer, and it is often more machinery than the workload needs. If your service is a container that responds to requests, scales with traffic, and holds no local state, Cloud Run removes an entire layer of operational work: no nodes to patch, no cluster upgrades, and nobody learning Kubernetes on your project budget.
GKE earns its complexity when you have enough services for cluster economics to matter, when you need specific networking, operators, or scheduling behavior, or when workloads are long-running, stateful, or need hardware Cloud Run does not offer. Choosing it should be a decision someone can defend.
What Drives a BigQuery Bill
The pricing model rewards discipline and punishes habit. On demand, you pay for the bytes a query scans, so partitioning tables by date, clustering on the columns people genuinely filter by, and dropping the reflexive select-everything are what separate a small bill from a memorable one. Most of it is decided when tables are designed, before anyone writes a query.
- Partition and cluster before loading. Retrofitting means rewriting tables you are already depending on.
- Avoid selecting every column. Column storage means you pay for what you touch, and analysts touch everything by default.
- Watch scheduled queries. A dashboard set to refresh every five minutes scans on that schedule whether or not anyone is looking at it.
- Compare capacity pricing against on demand. Steady, heavy usage may come out lower on committed capacity. Run the comparison against your own query history before assuming either way.
Is Firebase a Shortcut or a Trap?
It can be either, depending on what you build with it. For a first version, an internal tool, or a mobile app that needs authentication, push, and a simple data store, Firebase covers a lot of plumbing you would otherwise write and maintain yourself.
The trap is putting business logic and complex querying into it, then discovering the data model does not support the reports the business now wants. Decide early which parts of the system are allowed to depend on it, and keep your domain logic behind your own API. That will not make a later move painless, but it does limit how much of the system has an opinion about Firebase.
Before the First Production Workload
Project and folder structure, identity and access rules, network layout, and infrastructure defined as code. Set these once, before the sprawl starts. Retrofitting an organization structure onto an estate that grew one project at a time is genuinely unpleasant work, and it tends to land at the moment you can least afford the disruption.
Budget alerts belong on the same list, as does deciding who is allowed to create resources at all. None of it is difficult at the start, and all of it gets harder in proportion to how much has already been built on top.
How IAM Evaluates, and Why Access Is Hard to Read Back
Allow policies attach at the organization, folder, project, and individual resource level, and they combine additively down the hierarchy. A role granted at the folder cannot be taken back by a policy on a project inside it. The only subtractive mechanism is a deny policy, which is evaluated before allow policies and blocks a named permission for a named principal regardless of what granted it. The practical consequence is that a resource's own policy tells you almost nothing about who can reach it.
That is what makes access hard to read back. Who can read a given bucket is a question for Policy Analyzer or the Policy Troubleshooter running against the whole hierarchy, not for the bucket's own policy page. Policy changes are also eventually consistent, so a grant that was just made can return 403 on the next call and succeed a minute later, which is worth knowing before you start debugging the application instead.
- Basic roles are the first thing to look for. Owner, Editor, and Viewer predate the granular roles and span every service in the project, so an Editor grant made early to keep things moving is usually still there, and it covers far more than whoever granted it intended.
- Service account keys are long-lived credentials sitting in a file, and rotating them does not change that; Workload Identity Federation removes the key instead.
- Impersonation is the alternative to granting a person a role outright. Grant Service Account Token Creator on a service account that holds the role, and that person's access becomes a short-lived token that appears in audit logs as an impersonation event.
- The Compute Engine default service account is granted Editor on the project by default, so anything running as it inherits that reach. An organization policy constraint turns off the automatic grant, and setting it before the first workload is much easier than unpicking it after.
- Custom roles do not track their source. Permissions Google later adds to a predefined role do not appear in the custom role you copied from it, so a copied role falls quietly behind as the service changes.
IP Planning and the Default Network
Every new project arrives with a default VPC in auto mode: one subnet in every region, drawn from the same fixed block at 10.128.0.0/9. Auto mode hands out those same ranges in every project it is created in, which is why an estate assembled from default networks tends to overlap with itself. Overlapping ranges cannot be peered, so the discovery usually lands the day two systems built independently are asked to talk to each other.
Address planning also has to account for how quickly Kubernetes consumes space. With alias IP ranges, each GKE node takes a slice of the pod range, a /24 by default for the standard limit of 110 pods per node, and Services come out of a second secondary range. A cluster given a /22 for pods runs out of node capacity long before it runs out of CPU, and the primary pod range is fixed at cluster creation: growing past it means attaching an additional range or rebuilding, not editing the original. Private Service Access, which is how Cloud SQL and Memorystore get private addresses, needs its own allocated block that overlaps nothing else.
- Converting a network from auto mode to custom mode is one way. Custom mode is what you want for anything long-lived, because the subnets are yours to place.
- VPC Network Peering is not transitive and does not re-advertise routes learned from another peer. A hub project peered to two spokes does not connect the spokes to each other; that needs Network Connectivity Center or a routing appliance in the middle.
- Instances without external addresses reach Google APIs through Private Google Access, and reach the public internet, including package registries, only through Cloud NAT. A private GKE node that cannot pull an image is usually missing one of the two.
- Shared VPC keeps subnets in a host project while workloads run in attached service projects, so addresses come out of one plan instead of one plan per team.
The Cloud Run Settings That Decide Behavior
Four settings do most of the work in a Cloud Run service, and concurrency is the one that catches people. A container instance accepts up to 80 requests at once by default, which means the code inside has to be safe to run concurrently. A per-request buffer that stacks, a library that is not thread safe, or an in-process cache written as though it belonged to one request will pass testing and fail under load, and the failure does not present as a deploy error.
CPU allocation is the second. By default CPU is throttled outside the handling of a request, so anything the container tries to do after the response goes out, flushing telemetry, writing an audit record, running a timer, is unreliable and may resume much later inside an unrelated request. Setting CPU to always allocated fixes that behavior and moves the service to instance-based billing, which is the point at which idle time starts to cost.
- Minimum instances above zero keeps containers warm and removes cold starts on the first request. Idle instances bill at a lower rate, so the trade is a fixed floor against startup latency.
- The request timeout defaults to 5 minutes and can be raised to 60. Work that needs longer is not a request: it belongs in a Cloud Run job or a queue-driven worker.
- Maximum instances is the blast radius on both a traffic spike and the bill.
Connection Counts Between Cloud Run and Cloud SQL
Horizontal scaling and a connection-per-process database meet badly. Each Cloud Run instance opens its own pool, and PostgreSQL allocates a backend process per connection, so a pool of ten in each of a hundred instances is a thousand connections against an instance whose max_connections is tied to its size. Passing that limit does not degrade gradually, it refuses: new connections get 'sorry, too many clients already', and they get it during the traffic spike that caused the scale-out in the first place.
The arithmetic is most of the fix. Keep per-instance pools in single digits, choose maximum instances with max_connections in view rather than in isolation, and put a server-side pooler in front of anything with a high ceiling. Pooling in transaction mode carries a cost worth knowing in advance: session-scoped behavior stops working, so advisory locks held across statements, LISTEN and NOTIFY, and session-level SET commands need rethinking before the pooler goes in, not after.
- The Cloud SQL Auth Proxy and the language connectors authenticate with IAM and wrap the connection in TLS, so clients do not need an authorized network entry per source address.
- Reaching a private IP instance from Cloud Run requires Direct VPC egress or a Serverless VPC Access connector. Neither is needed on the public path, but that path leaves the instance's public endpoint reachable and puts the whole weight of access control on the connector's authentication.
- A read on the request path does not belong in BigQuery. Query latency there has a floor measured in hundreds of milliseconds even against a small table, which is fine behind a dashboard and wrong in front of a user who is waiting.
Frequently Asked Questions
Is Cloud Run a function platform?
No. Cloud Run runs an ordinary container that listens for HTTP on the port named in the PORT environment variable, so the language, framework, and process model inside are yours to pick. What it is not is a general host for work that is not tied to a request, because an instance can be stopped between requests. Schedulers, queue consumers, and long batch steps belong in Cloud Run jobs, Cloud Tasks, or Cloud Scheduler invoking the service, not in a thread the container starts at boot.
What has to be in place in a project before anything can deploy?
A billing account attached to the project, the APIs for each service enabled in that project, an image in Artifact Registry, and an identity the deployment runs as. API enablement is per project and per service, which is why a pipeline that works in staging returns 403 with SERVICE_DISABLED in a new project rather than a 404. Organization policy constraints are the other common surprise: a constraint forbidding external IP addresses or unauthenticated invocation refuses the deploy with a message that reads like a permissions problem, and the fix is at the policy rather than in IAM.
Do we need Google Workspace to use GCP?
No. Identities can live in Cloud Identity, the identity-only edition, and an existing identity provider can be federated over SAML or OIDC so people keep the credentials and MFA they already use. What you do need is a domain you control and can verify, because the organization node is created from a verified domain. Grant roles to groups rather than to individual accounts, so that people joining and leaving is a directory change instead of a policy edit.
How does a CI pipeline deploy without a service account key file?
Workload Identity Federation lets GitHub Actions, GitLab, or another OIDC-capable runner exchange its own token for short-lived Google credentials, so no service account key file needs to be stored in the CI system at all. You create a workload identity pool and a provider that trusts the CI issuer, restrict which claims are accepted, usually the repository plus the branch or environment, and then allow that principal to impersonate a deployment service account. Skipping the claim restriction is the common mistake, because a provider that trusts an issuer without narrowing the subject trusts every repository on that issuer. If your CI cannot present an OIDC token, a key is the fallback, and it should belong to a service account that can do exactly one job.
How does a move onto the platform actually proceed?
The foundation work comes first; after that the order is stateless service, then data. Run the application on Cloud Run or GKE while it still reads and writes the database where it lives today, reached over a VPN or Interconnect, so you learn the real latency and failure behavior while everything is still reversible. The data move is the step with a cutover: a dump and load has downtime proportional to size, while Database Migration Service replicates continuously and narrows the window to the promotion. DNS changes last, with the previous environment still able to serve while records propagate.
Can we run part of the system on GCP and leave the rest where it is?
Yes, and it is the usual shape for a while. Three things decide whether it stays comfortable: the round trip between the two halves, egress charges on data leaving GCP, which are billed per gigabyte and accumulate on chatty service-to-service calls rather than on the large transfers people actually budget for, and the fact that you now have two identity systems to keep in agreement. Split at a seam where traffic is low and the interface is stable, not wherever the code happens to be easiest to lift.
What do you need from us?
Decisions more than access, at the start: who owns the billing account and the organization node, which domain the identities are tied to, whether there is a data residency requirement, and who is on call after handover. Then the practical inputs, which are repository access, an inventory of what runs where today and what talks to it, and someone who can say what the system has to keep doing while it changes. Region is worth settling before anything is created, because a BigQuery dataset's location is fixed at creation and a query cannot span locations, so changing your mind later means copying the data rather than moving it.