In my previous guide on setting up a simple CI/CD pipeline for FastAPI, we used --set-env-vars
to pass configuration to Cloud Run. While convenient, it's not a secure way to manage secrets like API keys or database passwords. For production environments on Google Cloud, the recommended practice is to use Google Secret Manager.
In local development, we often use a .env
file to store these details. In production, a dedicated secret management tool is essential. This guide will show you how to use Google Secret Manager to securely provide secrets to your FastAPI application running on Cloud Run.
First, you need to enable the Secret Manager API for your Google Cloud project.
To allow Cloud Run to access the secret, the Cloud Run service identity must have the following role roles/secretmanager.secretAccessor
.
Now, let's create a secret to store a value, like a database password. To do this, you'll need the "Secret Manager Admin" (roles/secretmanager.admin
) IAM role on your project.
POSTGRES_PASSWORD
).Secret Manager automatically versions your secrets. Each time you update the value, a new version is created. This allows you to pin your application to a specific version of a secret for stability.
You can mount secrets into your Cloud Run service as environment variables. When you do this, the secret's value is exposed to your application just like any other environment variable. This works perfectly with the Pydantic Settings approach described in my article on Centralizing Your FastAPI Configuration Settings.
When a secret is mounted as an environment variable, Cloud Run retrieves its value before the instance starts. If the secret cannot be fetched - due to a permission error, for example - the instance will fail to start. This prevents your application from running without the credentials it needs.
When configuring your Cloud Run service, you can choose to mount a specific version of a secret or always use the latest
version. Google recommends pinning to a specific version for production services to avoid unexpected changes.
cloudbuild.yaml
To use secrets in your Cloud Build pipeline, you need to declare them in your cloudbuild.yaml
file. This is useful for steps that need access to secrets, like running database migrations with Alembic.
Here’s an example of a cloudbuild.yaml
that makes secrets available to a migration step.
steps: # Step 1: Build the Docker image - name: 'gcr.io/cloud-builders/docker' args: ['build', '-t', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA', '.'] # Step 2: Run Database Migrations using the newly built image # secretEnv makes the secrets available as environment variables for this step only - name: 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA' entrypoint: 'alembic' args: ['upgrade', 'head'] secretEnv: ['POSTGRES_USER', 'POSTGRES_PASSWORD', 'POSTGRES_HOST', 'POSTGRES_DATABASE_NAME'] # Step 3: Push the Docker image to Google Container Registry - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA'] # Step 4: Deploy the Docker image to Google Cloud Run # This step mounts the secrets directly into the running service - name: 'gcr.io/cloud-builders/gcloud' args: - 'run' - 'deploy' - 'my-app' # Your service name - '--image=gcr.io/$PROJECT_ID/my-app:$COMMIT_SHA' - '--region=us-central1' - '--project=$PROJECT_ID' # Mount secrets into the Cloud Run service - '--set-secrets=POSTGRES_USER=POSTGRES_USER:latest,POSTGRES_PASSWORD=POSTGRES_PASSWORD:latest,POSTGRES_HOST=POSTGRES_HOST:latest,POSTGRES_DATABASE_NAME=POSTGRES_DATABASE_NAME:latest' # This section makes secrets available to the build steps availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/POSTGRES_USER/versions/latest env: 'POSTGRES_USER' - versionName: projects/$PROJECT_ID/secrets/POSTGRES_PASSWORD/versions/latest env: 'POSTGRES_PASSWORD' - versionName: projects/$PROJECT_ID/secrets/POSTGRES_HOST/versions/latest env: 'POSTGRES_HOST' - versionName: projects/$PROJECT_ID/secrets/POSTGRES_DATABASE_NAME/versions/latest env: 'POSTGRES_DATABASE_NAME' options: logging: CLOUD_LOGGING_ONLY
There are two key parts here:
availableSecrets
: This block at the bottom of the file tells Cloud Build which secrets from Secret Manager to fetch. The env
key assigns a short name to be used within the build pipeline.--set-secrets
flag: In the run deploy
step, this flag tells Cloud Run to mount the specified secrets into the running container as environment variables. The format is [ENV_VAR_NAME]=[SECRET_NAME]:[VERSION]
. This is the secure and recommended way to expose secrets to your final application.The example cloudbuild.yaml
also shows how to run database migrations as a build step. For a detailed walkthrough, you can read my guide on running database migrations with Alembic in Google Cloud Build. To keep your deployments fast and cost-effective, it's also important to create optimized, small images, which I cover in my guide on slimmer FastAPI Docker images with multi-stage builds.
By using Secret Manager, you separate your sensitive data from your source code and build configuration, leading to a more secure and manageable deployment process.
Q: Why is using Secret Manager better than --set-env-vars
?
A: The --set-env-vars
flag stores your variables in plain text within the service configuration. Anyone with permission to view the Cloud Run service can see them. Secret Manager stores secrets in an encrypted format and provides access through tightly controlled IAM permissions, which is much more secure.
Q: Should I use the latest
version or a pinned version of a secret?
A: For production, it's safer to pin to a specific version (e.g., POSTGRES_PASSWORD:1
). This prevents your application from breaking if a secret is updated unexpectedly. Use latest
for development or non-critical services where you always want the newest value.
Q: How does my FastAPI application read these secrets? A: Your application reads them just like any other environment variable. If you've configured Pydantic Settings to read from environment variables, it will work automatically without any code changes.
Q: What happens if my Cloud Build step fails with a permission error?
A: This usually means the Cloud Build service account ([PROJECT_NUMBER]@cloudbuild.gserviceaccount.com
) does not have the "Secret Manager Secret Accessor" role. You need to grant this role in the IAM & Admin section of the Cloud Console.
If you're new to deploying FastAPI applications with CI/CD on Google Cloud, check out my guide: Simple CI/CD for Your FastAPI App with Google Cloud Build and Cloud Run. This article covers how to automate builds and deployments using Cloud Build and Cloud Run, providing a solid foundation for integrating secrets management as described here.
David Muraya is a Solutions Architect specializing in Python, FastAPI, and Cloud Infrastructure. He is passionate about building scalable, production-ready applications and sharing his knowledge with the developer community. You can connect with him on LinkedIn.
Enjoyed this blog post? Check out these related posts!
Building a Flexible Memcached Client for FastAPI
Flexible, Safe, and Efficient Caching for FastAPI with Memcached and aiomcache
Read More..
Managing Background Tasks in FastAPI: BackgroundTasks vs ARQ + Redis
A practical guide to background processing in FastAPI, comparing built-in BackgroundTasks with ARQ and Redis for scalable async job queues.
Read More..
Centralizing Your FastAPI Configuration Settings with Pydantic Settings
How to Organize and Load FastAPI Configuration Settings from a .env File Using Pydantic Settings
Read More..
Simple CI/CD for Your FastAPI App with Google Cloud Build and Cloud Run
Push code, deploy automatically: A simple CI/CD guide for your web app.
Read More..
Have a project in mind? Send me an email at hello@davidmuraya.com and let's bring your ideas to life. I am always available for exciting discussions.