deploying to google cloud
gcp deployment
google cloud run
google kubernetes engine
ci/cd pipeline

Deploying to Google Cloud: A Practical How-To Guide

Deploying to Google Cloud: A Practical How-To Guide

You’ve picked Google Cloud, opened the console, and immediately encountered the main challenge. Not how to deploy code, but where to deploy it.

A startup CTO usually wants speed without hiring a platform team too early. An enterprise product lead usually wants guardrails, auditability, and fewer deployment surprises. Teams using nearshore developers have one more requirement: the setup has to be understandable, repeatable, and resilient when people hand work across time zones.

That’s why deploying to google cloud is less about clicking “deploy” and more about making a few durable decisions early. Google Cloud has the scale to support that choice. GCP runs across 40 cloud regions, 121 zones, and 187 network edge locations, and its startup customer base grew 28.1% in 2023-2024 according to Google Cloud Platform statistics compiled here. Those numbers matter because they translate into practical options: you can deploy close to users, support growth without re-platforming, and avoid building around a tiny regional footprint.

Teams don’t fail because Google Cloud is too complex. They fail because they choose the wrong service for their operating model, ship without basic deployment discipline, and only think about cost or observability after the first production issue.

Starting Your Journey Deploying to Google Cloud

A familiar scenario goes like this. The product is gaining traction, the team has a containerized app, and someone says, “Let’s just put it on GCP.” Then the choices show up. Cloud Run, GKE, App Engine, Compute Engine. All of them can run your software. None of them create the same operational burden.

The wrong first move usually isn’t catastrophic. It’s worse than that. It works well enough to stick around, all the while slowing delivery, complicating debugging, or pushing cloud spend in the wrong direction.

For a first major rollout, I’d frame the decision around three business questions:

  • How much platform work can your team own? If your developers are already stretched, don’t buy yourself a cluster unless the product needs one.

  • How often do you need to release?
    Teams shipping features every few days need a path that doesn’t turn deployments into ceremonies.

  • What kind of variability do you expect?
    Steady internal workloads, bursty public APIs, and multi-service products should not be deployed the same way.

Practical rule: Your first GCP deployment should reduce decisions during release day, not increase them.

A lot of GCP confusion comes from thinking in services first. Think in operating model first. If you want a fast path from git push to production, your shortlist is smaller than the catalog suggests. If you need deep control over networking, workloads, and service composition, your shortlist changes again.

That’s the lens that matters for deploying to google cloud in a way that your team can keep running six months from now.

Choosing Your Deployment Service The Core Four

Your deployment service shapes team behavior. It decides who gets paged, how much release friction you tolerate, and whether your engineers spend time shipping product or maintaining platform plumbing.

DORA research found that elite performers achieve 973 times more frequent code deployments than low performers, according to Google Cloud’s State of DevOps resource. That doesn’t happen by motivation alone. It happens when the platform supports small, repeatable, low-drama releases.

GCP deployment services comparison

Service Best For Operational Overhead Scalability Model Example Use Case
Cloud Run Containerized web apps, APIs, event-driven services Low Automatic serverless scaling Public API, internal admin app, mobile backend
App Engine Conventional web apps that benefit from strong platform defaults Low to moderate Managed platform scaling Content app or business portal with limited infra customization
GKE Multi-service systems, platform teams, complex runtime control High Kubernetes-based scaling and orchestration SaaS platform with several services and shared infra needs
Compute Engine Legacy apps, custom runtimes, VM-first operations Moderate to high VM scaling you manage Software that depends on OS-level control or lift-and-shift migration

Cloud Run when speed matters more than knobs

If a client asks for the safest default for a new product, Cloud Run is usually first on my list. You package the app as a container, deploy it, and Google handles the underlying runtime. That removes a lot of failure points for teams that don’t want to own nodes, control planes, or cluster policy on day one.

Cloud Run works especially well when the application is stateless, HTTP-based, and doesn’t need custom orchestration. That includes APIs, dashboards, webhook processors, and many mobile backends. The release workflow is also easier to standardize across distributed teams.

Cloud Run is a bad fit when your app depends on Kubernetes-native patterns, sidecars, tight service mesh integration, or detailed scheduling behavior. When teams force those needs into Cloud Run, they usually end up inventing workarounds that are harder than just using GKE.

App Engine when convention beats flexibility

App Engine still makes sense for teams that want a managed platform experience and are comfortable staying inside its opinionated model. It can be a clean choice for straightforward web applications where the business value comes from shipping features, not customizing infrastructure.

The catch is that App Engine often sits in an awkward middle ground. It’s simpler than GKE, but for container-first teams Cloud Run often feels more natural. If you’re deciding between those two, it helps to understand the platform trade-offs in a more focused comparison like this guide on App Engine on GCP.

I’d choose App Engine when the app matches the platform well and the team wants minimal infrastructure decision-making. I wouldn’t choose it for a system that’s already moving toward containers, microservices, or broader Kubernetes adoption.

GKE when control is the product

Google Kubernetes Engine is the right answer when you need Kubernetes, not when you merely recognize the name. That distinction saves teams a lot of pain.

GKE shines when you have multiple services, distinct deployment policies, environment promotion needs, internal platform standards, or infrastructure patterns that benefit from Kubernetes primitives. It’s also the right place when your team already knows how to operate Kubernetes and can do it without turning every sprint into a platform sprint.

If your developers are still learning containers, GKE is usually too much platform too early.

GKE gets expensive in human time before it gets expensive in cloud spend. You need to think about node pools, manifests, rollout behavior, secrets, RBAC, ingress, and cluster hygiene. That’s not a problem if the product needs it. It is a problem if the main goal is “run this API reliably.”

If you need a second opinion grounded in practical infrastructure choices, I often point teams to references like Tbourke Solutions' cloud services because they frame cloud work around operational needs, not just service labels.

Compute Engine when the app doesn’t want to be modernized yet

Compute Engine is the honest choice for workloads that don’t fit managed abstractions cleanly. Some applications need OS-level access, custom packages, or migration paths that would be risky to refactor right away.

This is often the right interim landing zone for legacy systems. It lets you get onto GCP without pretending the application is already cloud-native. But it comes with the responsibility of running VMs properly. Patching, instance hardening, startup behavior, process supervision, and capacity management all become your problem.

A practical decision filter

Use this when you’re stuck between options:

  • Pick Cloud Run if your app is stateless, containerized, and your team values deployment simplicity.
  • Pick App Engine if your app fits a managed platform model and you want convention-heavy delivery.
  • Pick GKE if you need Kubernetes features and have the operational maturity to use them well.
  • Pick Compute Engine if the workload needs VM control or you’re handling a careful migration.

The mistake I see most often is ambition-driven selection. Teams choose the most powerful service because they assume they’ll “grow into it.” Most don’t. They just inherit overhead early.

Your First Deployments to Cloud Run and GKE

For modern apps, two services often chosen are Cloud Run and GKE. One optimizes for speed and low operational load. The other optimizes for control.

This is the point where deploying to google cloud stops being conceptual and becomes muscle memory.

A hand placing a container onto an orange cloud labeled GKE, next to a blue Cloud Run cloud.

Deploying a container to Cloud Run

Assume you have a simple web service. It listens on a port, reads environment variables, and writes structured logs to stdout.

A minimal Dockerfile might look like this:

FROM node:20-slim
WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY . .

ENV PORT=8080
CMD ["npm", "start"]

That file matters because Cloud Run expects a container that starts cleanly, binds to the assigned port, and exits predictably when something is wrong. Don’t hide startup failures behind shell scripts unless you have a real reason.

Before deployment, set up the basics:

  1. Create or select a GCP project
  2. Enable billing
  3. Install and authenticate the Google Cloud CLI
  4. Enable the APIs you need
  5. Choose one region and stick to it initially

Then build and push the image:

gcloud auth login
gcloud config set project YOUR_PROJECT_ID

gcloud services enable run.googleapis.com cloudbuild.googleapis.com artifactregistry.googleapis.com

gcloud artifacts repositories create app-repo \
  --repository-format=docker \
  --location=us-central1

gcloud builds submit \
  --tag us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/web-service:latest

A few opinions here. Use Artifact Registry, not a random external image store for your first deployment. Keep image naming boring and consistent. Tagging conventions matter more once automation starts, but even now you want something every developer can read quickly.

Deploy to Cloud Run like this:

gcloud run deploy web-service \
  --image us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/web-service:latest \
  --region us-central1 \
  --platform managed \
  --allow-unauthenticated \
  --set-env-vars APP_ENV=production

That gives you a public URL if the service is internet-facing. For internal services, remove public access and front it differently.

What to check after the Cloud Run deploy

Don’t stop at “deployment succeeded.” Check the things that break in real projects:

  • Startup behavior
    Open the service and confirm the app boots without hidden dependency failures.

  • Logs
    Verify your application writes readable logs to standard output. If logs are messy on day one, incident response gets painful fast.

  • Configuration
    Validate environment variables, secrets injection, and region assumptions before traffic increases.

  • Request handling
    Confirm health endpoints, auth middleware, and external integrations work in the deployed environment, not just locally.

The first production deployment isn’t proof that the architecture is right. It’s proof that the release path works once.

Deploying to GKE for a multi-service setup

GKE is the better path when your product already has multiple services or needs Kubernetes-native deployment behavior. The setup is longer because the platform is richer.

Start by creating a cluster:

gcloud services enable container.googleapis.com

gcloud container clusters create-auto app-cluster \
  --region us-central1

gcloud container clusters get-credentials app-cluster \
  --region us-central1

I prefer starting with a managed cluster configuration that reduces operational noise. Early on, the focus should be on learning workload deployment patterns, not tuning every cluster detail.

Next, build and push the service image:

gcloud builds submit \
  --tag us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/api-service:latest

Now create a Kubernetes deployment manifest.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: api-service
  template:
    metadata:
      labels:
        app: api-service
    spec:
      containers:
        - name: api-service
          image: us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/api-service:latest
          ports:
            - containerPort: 8080
          env:
            - name: APP_ENV
              value: "production"
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"

The resource settings aren’t decoration. If you skip them, you make scheduling and runtime behavior harder to reason about. Teams often postpone requests and limits because “it’s only the first deploy.” That creates unstable behavior later when more services share the cluster.

Expose it with a service:

apiVersion: v1
kind: Service
metadata:
  name: api-service
spec:
  type: LoadBalancer
  selector:
    app: api-service
  ports:
    - port: 80
      targetPort: 8080

Apply both files:

kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
kubectl get pods
kubectl get services

What usually goes wrong on the first GKE rollout

GKE problems are rarely “Kubernetes is broken.” They’re usually one of these:

Issue What it looks like What to fix
Bad image reference Pods never start Confirm the pushed image path and tag
Missing env vars App starts, then crashes Move required runtime config into manifests or secret references
No resource requests Unstable scheduling behavior Set realistic requests and limits early
Wrong port mapping Service exists, app unreachable Align containerPort, targetPort, and app listener
Weak rollout discipline New version breaks traffic Use staged rollout patterns instead of replacing everything at once

Choosing between these two after you’ve done both

Once you’ve deployed a sample service to each platform, the difference becomes obvious.

Cloud Run feels like application delivery.
GKE feels like platform engineering.

That’s why many teams do well with a split model. Public APIs, admin tools, and lightweight services land on Cloud Run. Core multi-service systems, internal platforms, or workloads that need Kubernetes-native controls land on GKE. You don’t need ideological purity here. You need a release model your team can operate confidently.

Automating Deployments with a CI/CD Pipeline

Manual deployments feel manageable until the team grows, the release cadence increases, or somebody deploys the wrong image on a Friday. Then the process becomes a business problem, not a developer preference.

A good GCP pipeline removes guesswork. Code moves through the same path every time. Builds are reproducible. Artifacts are traceable. Promotion rules are visible. That consistency matters even more when developers, QA, and product stakeholders are working across shared but not identical hours.

An illustration of a software development pipeline showing code moving through commit, build, test, and cloud deployment stages.

The managed pipeline stack that works

A practical native stack on GCP usually looks like this:

  • Source repository
    GitHub, GitLab, or another hosted repository can trigger the pipeline.

  • Cloud Build
    Builds the container, runs tests, and produces a deployable artifact.

  • Artifact Registry
    Stores the versioned image you actually promote.

  • Cloud Deploy
    Orchestrates staged delivery into environments like dev, staging, and production.

This structure matters because it separates build from release. A built image is one thing. A promoted image is another. Teams that mix those steps too casually struggle to answer basic production questions later.

Why Cloud Deploy is more than rollout automation

Cloud Deploy gives you managed delivery with operational visibility. Google describes built-in metrics like Deployment frequency and Deployment failure rate in the Cloud Deploy documentation. Those aren’t vanity metrics. They tell you whether the team is releasing in small, healthy increments or batching risk into larger drops.

That’s the useful connection to delivery performance. Faster deployment isn’t valuable by itself. Faster deployment with stable outcomes is valuable.

A pipeline is mature when engineers trust it enough to deploy during working hours without a war room.

A simple Cloud Build config for a containerized service might look like this:

steps:
  - name: 'gcr.io/cloud-builders/docker'
    args: ['build', '-t', 'us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/web-service:$COMMIT_SHA', '.']

  - name: 'gcr.io/cloud-builders/docker'
    args: ['push', 'us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/web-service:$COMMIT_SHA']

images:
  - 'us-central1-docker.pkg.dev/YOUR_PROJECT_ID/app-repo/web-service:$COMMIT_SHA'

This is intentionally simple. In a real project, add test steps before push, and don’t promote images that haven’t passed them.

Sensible rollout behavior for distributed teams

For teams handing work across time zones, deployment automation should do three things well:

  1. Keep artifacts immutable
    Promote a specific image tag through environments. Don’t rebuild per environment unless there’s a strong compliance reason.

  2. Require approval at the right boundary
    Production should have a clear approval or controlled promotion step. Development environments usually shouldn’t.

  3. Make rollback boring
    Rolling back should mean promoting the last known good release, not rebuilding a mystery version.

If you’re also validating content, localization, or generated assets inside CI, niche tools can fit cleanly into the same pipeline. For example, teams working on multilingual products sometimes add checks from TranslateBot CI setup so non-code assets don’t become a last-minute release blocker.

Blue-green and canary thinking

You don’t need elaborate release engineering on day one, but you do need safe deployment patterns as the product matters more. Blue-green and canary approaches help you reduce blast radius, especially on GKE and mature Cloud Run workflows. If you want the mental model before implementation, this overview of blue-green deployment is a useful primer.

The practical point is simple. Don’t make every release all-or-nothing. Give the system a way to prove the change is healthy before full exposure.

Securing and Observing Your Deployed Application

Teams often treat security and observability as cleanup work after launch. That’s backwards. If you can’t control who deploys, what runs, and how failures surface, production is fragile even when the code is good.

Google’s internal change management approach uses a structured rollout with roughly a one-week progression and bake time, and Google notes that even elite teams operate with 0-15% change failure rates in that model in its change management guidance. The lesson for customer workloads is straightforward: safe delivery depends on guardrails, not heroics.

A hand-drawn illustration of a cloud icon with a protective shield and an eye watching above.

Lock down identity before you tune anything else

Most first-time GCP environments are too permissive. Somebody gets broad project access because it’s convenient, service accounts inherit more power than they need, and the deployment path works until audit or incident review exposes the mess.

Start with least privilege:

  • Separate human and workload access
    Developers shouldn’t deploy using personal credentials in routine workflows. Pipelines should use service accounts with narrowly scoped permissions.

  • Create role boundaries by job
    The team that reviews logs doesn’t need the same rights as the team that modifies production services.

  • Avoid reusing one powerful service account everywhere
    Per-service identities are easier to reason about and easier to rotate or restrict later.

Network design should reflect exposure, not convenience

A lot of applications don’t need to expose every component directly to the public internet. Treat ingress as a business decision. Ask which services must be reachable externally and which should stay private behind internal communication paths.

Common mistakes include opening more than necessary, mixing internal and public workloads carelessly, and ignoring egress behavior until an integration issue appears. Good network design is rarely glamorous, but it prevents a lot of avoidable incidents.

Secure networking usually means fewer reachable things, fewer broad rules, and fewer assumptions hidden in someone’s memory.

Observability that helps during incidents

If logs, metrics, and alerts are an afterthought, you’ll discover that during the first production problem. Cloud Logging and Cloud Monitoring can do a lot, but the key is choosing the signals that map to user pain and deployment health.

Start with:

Signal Why it matters First check
Request errors Users feel these immediately Error spikes after deploy
Latency Degradation often shows up before outage Slow endpoints by service revision
Container restarts or failed instances Runtime instability points to config or code issues Crash loops and startup failures
Deployment events Helps correlate incidents with changes What changed just before impact

You also want dashboards that answer operational questions quickly. Is the problem global or isolated? Did it start after a rollout? Is one service failing or is a dependency upstream causing the issue?

For teams formalizing that setup, these application monitoring best practices are a solid complement to the platform-native tooling.

Optimizing Costs and Managing Your Cloud Spend

Cloud cost control isn’t a one-time setup task. It’s an operating discipline.

The reason teams get surprised by cloud bills isn’t usually reckless spending. It’s unmanaged defaults, weak visibility, and the false belief that managed services optimize themselves. They don’t. They simplify operations, but you still have to choose good settings and revisit them as traffic and architecture change.

A concrete example shows the risk. A common pitfall in Cloud Run is concurrency tuning. According to this Cloud Run cost optimization discussion, 25% of Cloud Run users overspend by up to 2x due to unoptimized concurrency limits, with the default set at 80 and a higher ceiling available. The practical takeaway isn’t “raise concurrency everywhere.” It’s that default settings can become expensive if they don’t match your request profile and application behavior.

What active cost management looks like

A sane process usually includes these habits:

  • Estimate before launch
    Use the pricing calculator to model the likely shape of traffic and core services before production pressure arrives.

  • Set billing alerts early
    Alerts won’t reduce spend, but they will reduce the time between overspend and response.

  • Review service behavior, not just totals
    If one service scales awkwardly, retries too much, or keeps idle capacity unnecessarily, the monthly total won’t tell you why.

  • Match pricing strategy to workload shape
    Predictable workloads and bursty workloads shouldn’t be purchased or tuned the same way.

The real management question

The question isn’t whether your team can check the bill. It’s whether someone can interpret it and act. That means understanding why a service cost changed, whether the change was justified, and what knob is safe to adjust without hurting the product.

Cost optimization works best when one person owns the review loop and another validates the impact on reliability.

For startups, that owner might be the CTO or lead engineer. For larger organizations, it’s often shared between engineering and finance. For teams using staff augmentation, the strongest setups usually assign cloud cost review to someone close enough to delivery that they can connect spend back to architecture and release decisions.

Managed cloud is flexible. That flexibility is exactly why you need active governance. If you treat cost as a monthly surprise, it will keep behaving like one.


If you’re planning your first major GCP rollout and want experienced hands on the delivery model, architecture, and ongoing operations, Nerdify can help with product development and nearshore team augmentation that fits how modern engineering teams ship.