Good news everyone: We finally managed to make deploying serverless containers
as simple as
gcloud run deploy --image=[IMAGE]. This command deploys an
application to Cloud Run with the given docker image, but what does really
happen behind the scenes to make this happen?
Recently I reimplemented this
gcloud run deploy command in a program, using
the API calls through client libraries, so I’ll explain a bit how this command
works under the covers.
If you’re trying to deploy to Cloud Run programmatically, this guide might help,
however you’re far better off using the
gcloud run services replace command
(which works like
kubectl apply, with YAML manifests).
Cloud Run API endpoints
Normally, Cloud Run API is at
run.googleapis.com. However, each region of
Cloud Run actually has a different control plane. So your requests go to
regional endpoint, which looks like:
As you may notice, the URL follows the Kubernetes API style, which is:
This is not a coincidence: Cloud Run implements the Knative API
(which is modeled on top of Kubernetes API). Therefore, the inner workings of
this API is designed to work with Knative tooling (such as
kn CLI or
kubectl), more on this below.
Knative API under the hood
After collecting deployment options from the command-line options (like name,
image, CPU, environment variables…) the command builds an in-memory Knative
After that, the object is serialized into a JSON object (in case you did not
kubectl apply also converts your YAMLs to JSON on the wire) and
submitted to the Cloud Run API endpoint.
Kubernetes Namespaces are used to identify GCP Project IDs
Cloud Run uses the
metadata.namespace field of Knative API objects to indicate
which GCP project the resource belongs to. For example, listing Cloud Run apps
Creating a Service
Creating a Service follows the Kubernetes API convention. After all, a Service is a CRD under apiGroup=serving.knative.dev and apiVersion=v1, just like it would be on a Kubernetes cluster –despite we’re not doing Kubernetes here at all.
The request involves sending the JSON-serialized object to this endpoint in a POST request:
Updating a Service
If you need to make an update to a Service, you need to GET the Service first, make the updates you want and PUT that back to the REST API object:
Doing this will generate a new Revision, with a random name like
If you want to generate nicer names gcloud provides with numbers like
hello-00001-jfhpd, you can update
spec.template.metadata.name (sets Revision
name) as part of the updated object.
Wait… if this behaves like Kubernetes API Server, can’t I use
Technically yes, but not yet. You could craft a special kubeconfig file or
specify kubectl options like (
--server) and point it to the Cloud
Run API endpoint, but it still wouldn’t work, since currently:
- Cloud Run API does not support the API discovery endpoints that kubectl needs
- Cloud Run API does not support strategic merge
which is used in
Waiting for deployment readiness
kubectl apply which just fires & forgets deployments, the
gcloud command actually waits until the Service reaches a “ready” state.
This is done by polling the Service (GET) and look at the
field. A successful deployment will have a condition like:
status: conditions: - type: Ready status: "True"
A failed deployment will have a condition listing an error message like this:
status: conditions: - type: Ready status: "Unknown" # or "False" if deployed but failed to become ready message: "Cloud Run failed to deploy the application [...]"
Making the application publicly accessible
If you have specified
--allow-unauthenticated to make your application
publicly accessible, this involves setting IAM bindings on the Service you
To see how it is done under the covers, run this with
gcloud run services add-iam-policy-binding NAME \ --member=allUsers --role=roles/run.invoker
To see my reimplementation of
gcloud run deploy using GCP Go client libraries,
Hope you had fun! Let me know on Twitter if this helped you implement a tool or
automation around the Cloud Run API.