Cloud Run’s "Build and Deploy from Source" feature is basically a magic trick that lets you skip Dockerfiles and container registries entirely, but the real magic is how it lets you iterate on your application code at the speed of a Git commit.
Let’s see it in action. Imagine you have a simple Python Flask app:
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello from Cloud Run Source!'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
And a requirements.txt:
Flask==2.3.2
gunicorn==20.1.0
Normally, you’d write a Dockerfile, build it, push it to Artifact Registry, and then deploy. With "Build and Deploy from Source," you just need to tell Cloud Run how to build your app. This is done via a cloudbuild.yaml file, which is Cloud Build’s configuration. For a Python app, it might look like this:
steps:
- name: 'gcr.io/k8s-skaffold/skaffold'
entrypoint: 'bash'
args:
- '-c'
- |
export IMAGE_NAME=gcr.io/$PROJECT_ID/my-python-app:$COMMIT_SHA
echo "Building image: $IMAGE_NAME"
gcloud builds submit --tag $IMAGE_NAME .
echo "Image built and pushed to Artifact Registry."
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args: ['run', 'deploy', 'my-python-app', '--image', 'gcr.io/$PROJECT_ID/my-python-app:$COMMIT_SHA', '--platform', 'managed', '--region', 'us-central1', '--allow-unauthenticated']
images:
- name: 'gcr.io/$PROJECT_ID/my-python-app:$COMMIT_SHA'
options:
logging: CLOUD_LOGGING_ONLY
When you deploy this with gcloud run deploy my-python-app --source . --platform managed --region us-central1 --allow-unauthenticated, Cloud Build kicks in. It uses skaffold (a Google-provided builder) to detect your app’s language (Python in this case). It then automatically generates and runs a Dockerfile behind the scenes, installs dependencies, and builds your container image. Finally, it deploys that image to Cloud Run.
The real power here is in the integration. You push your code to a connected GitHub or Cloud Source repository. Cloud Build watches for changes. On a new commit, it automatically rebuilds your container and redeploys your Cloud Run service. This means your production environment can be updated within minutes of you hitting git push.
The mental model is that Cloud Run, when given the --source flag, acts as an orchestrator for Cloud Build. Cloud Build is the workhorse that actually performs the containerization. It uses language-specific builders (like the one for Python that knows to run pip install -r requirements.txt and use gunicorn) to create a runnable image. This image is then automatically pushed to Artifact Registry, and the gcloud run deploy command uses that newly built image.
The cloudbuild.yaml allows for customization. For instance, if your Python app needs specific build-time arguments or a different entrypoint than gunicorn, you can define those steps. The entrypoint and args in the cloudbuild.yaml are executed by the specified name image. Here, gcr.io/k8s-skaffold/skaffold is used for the build step, and gcr.io/google.com/cloudsdktool/cloud-sdk for the deployment step. The args for the build step are actually running gcloud builds submit, which is a bit meta but effective. The images section tells Cloud Build which images to push to Artifact Registry.
What most people don’t realize is that Cloud Build uses a set of pre-built builder images for common languages. These builders encapsulate the logic for setting up the build environment, installing dependencies, and preparing the application for execution. For Python, it knows to look for requirements.txt and how to package a web application using a WSGI server like gunicorn. You don’t need to explicitly define these steps in your cloudbuild.yaml unless you need to deviate from the defaults.
The next step is understanding how to manage different environments (dev, staging, prod) using separate Cloud Run services and potentially different source branches.