Cloud Run services are fundamentally stateless, meaning they don’t retain data or configuration between requests. Environment variables are the primary mechanism for injecting configuration into these ephemeral containers, allowing you to customize their behavior without rebuilding the container image.
Let’s see this in action. Imagine a simple Python Flask app that serves a greeting. We want to make the greeting message configurable.
# app.py
from flask import Flask
import os
app = Flask(__name__)
@app.route('/')
def hello_world():
greeting = os.environ.get('GREETING_MESSAGE', 'Hello') # Default to 'Hello' if not set
name = os.environ.get('TARGET_NAME', 'World')
return f'{greeting}, {name}!'
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 8080)))
To deploy this to Cloud Run, we’d build a container image and then create a service. When creating the service, we can specify environment variables.
Here’s how you’d do it with gcloud:
gcloud run deploy my-greeting-app \
--image gcr.io/my-project/my-greeting-app:latest \
--platform managed \
--region us-central1 \
--set-env-vars GREETING_MESSAGE="Greetings" \
--set-env-vars TARGET_NAME="Cloud Runner" \
--allow-unauthenticated
After deployment, accessing https://my-greeting-app-xyz.run.app would yield:
Greetings, Cloud Runner!
If we update the service to change the TARGET_NAME:
gcloud run deploy my-greeting-app \
--image gcr.io/my-project/my-greeting-app:latest \
--platform managed \
--region us-central1 \
--update-env-vars TARGET_NAME="Developer" \
--allow-unauthenticated
Now, accessing the same URL results in:
Greetings, Developer!
This demonstrates how environment variables allow dynamic configuration of your running services.
The core problem environment variables solve in Cloud Run is managing application configuration in a stateless, containerized environment. Instead of baking configuration into the container image (which would require a new build for every change), you provide it at runtime. This separation of code and configuration is a fundamental tenet of modern cloud-native development.
Internally, when Cloud Run provisions a container instance for your service, it injects these environment variables into the container’s operating system environment. Your application code then simply reads these variables using standard language mechanisms (like os.environ.get() in Python, process.env in Node.js, or System.getenv() in Java).
The key levers you control are:
- Variable Name: This is the key your application code will use to look up the value. Conventionally, these are uppercase.
- Variable Value: This is the actual configuration data. Values are strings. For complex data, you’d typically use formats like JSON and parse them within your application.
- Scope: Environment variables are specific to a Cloud Run service revision. Updating variables creates a new revision.
- Secrets: For sensitive information like API keys or database passwords, you should use Cloud Run’s integration with Secret Manager. Environment variables themselves are not encrypted in transit or at rest within Cloud Run’s control plane, though the values are encrypted when stored by Google Cloud. Directly referencing secrets is the secure approach.
When you set environment variables, Cloud Run makes them available to your container. This is done by modifying the container’s env field in the Kubernetes Pod spec that Cloud Run manages under the hood. Each env entry is a key-value pair. For example, --set-env-vars GREETING_MESSAGE="Greetings" translates to an entry like:
env:
- name: GREETING_MESSAGE
value: "Greetings"
When a container starts, the operating system populates its environment block with these variables, allowing your application process to query them. This is a standard Linux mechanism, so your application doesn’t need any special Cloud Run-specific libraries to access them. The PORT variable, often used by web frameworks to determine which port to listen on, is also a common environment variable provided by Cloud Run itself, defaulting to 8080 but can be overridden.
You can also use environment variables to control application behavior that affects how Cloud Run itself operates, such as setting the NODE_ENV to production in Node.js applications to ensure optimized performance and logging. This is achieved by simply defining the variable like any other: --set-env-vars NODE_ENV=production. The runtime environment within the container then picks up this variable and uses it to configure the Node.js process accordingly.
The most surprising thing about environment variables in Cloud Run is how their lifecycle is tied to revisions. Unlike traditional servers where you might update a configuration file and restart a process, in Cloud Run, changing an environment variable always results in a new service revision being created. This is a direct consequence of Cloud Run’s immutable infrastructure and declarative configuration model. You don’t "edit" an existing running service’s environment; you deploy a new version of the service with the desired configuration. This ensures that rollbacks are as simple as re-deploying a previous revision, and that your running instances are always consistent with their declared configuration.
The next concept you’ll likely encounter is how to manage sensitive data using Secret Manager integration.