
Cloud Run から Secret Manager の機密情報を利用する 2 つの方法
はじめに
こんにちは。
クラウド事業本部コンサルティング部の渡邉です。
Cloud Run でアプリケーションを開発する際、データベースのパスワードや API キーなどの機密情報をどのように管理するか悩んだことはないでしょうか。コンテナイメージや環境変数にそのまま埋め込むと、セキュリティリスクが生じます。
Google Cloud では、Secret Manager にこれらの機密情報を集中管理し、Cloud Run から安全に参照する仕組みが提供されています。
本記事では、Cloud Run から Secret Manager の機密情報を利用する 2 つの方法である
- 環境変数として参照する
- ボリュームとしてマウントする
について、それぞれの特徴と gcloud CLI を使ったハンズオン手順をご紹介します。
Secret Manager とは
Secret Manager は、API キー・パスワード・証明書などの機密情報を安全に保存・管理するためのマネージドサービスです。バージョン管理や IAM による細かなアクセス制御が可能で、ローテーションにも対応しています。
Cloud Run と Secret Manager を組み合わせることで、以下のメリットが得られます。
- 機密情報をコードやコンテナイメージに含めない
- IAM を使ってアクセス制御を一元管理できる
- Secret のバージョン管理・ローテーションが容易になる
2 つの参照方法の比較
環境変数として参照する方法と、ボリュームとしてマウントする方法それぞれの項目ごとの比較です。
環境変数の場合、値はインスタンス起動時に取得されます。Secret をローテーションした場合、新しい値を反映させるには再デプロイ(または新しいリビジョンの起動)が必要です。そのため、公式ドキュメントでは特定のバージョンへの固定を推奨しています。
ボリュームマウントの場合、Cloud Run はファイル読み取り時に常に Secret Manager から最新の値を取得します。ローテーションとの相性が良い方法です。
| 項目 | 環境変数 | ボリュームマウント |
|---|---|---|
| 参照タイミング | インスタンス起動時 | ファイル読み取り時(常に最新版取得) |
| ローテーション対応 | △(特定バージョンへの固定推奨) | ○(自動で最新版を取得) |
| アクセス方法 | os.environ["VAR"] などの環境変数 |
ファイル読み取り |
| 推奨バージョン指定 | 特定バージョンへの固定推奨 | latest でも問題なし |
実際に試してみる
前提条件
- Google Cloud プロジェクトが作成済みであること
gcloudCLI がインストール・認証済みであること- Cloud Run サービスをデプロイするための権限があること
ステップ 1: API の有効化
Secret Manager API と Cloud Run API を有効化します。
gcloud services enable secretmanager.googleapis.com \
--project=YOUR_PROJECT_ID
gcloud services enable run.googleapis.com \
--project=YOUR_PROJECT_ID
ステップ 2: Secret の作成
今回は例として my-db-password という名前で Secret を作成します。
echo -n "my-super-secret-password" | gcloud secrets create my-db-password \
--replication-policy="automatic" \
--data-file=- \
--project=YOUR_PROJECT_ID
Created version [1] of the secret [my-db-password].
作成された Secret を確認します。

作成された Secret
ステップ 3: サービスアカウントの作成と権限付与
Cloud Run 専用のサービスアカウントを作成し、Secret Manager へのアクセス権を付与します。
まず、サービスアカウントを作成します。
gcloud iam service-accounts create cloud-run-secret-demo \
--display-name="Cloud Run Secret Demo" \
--project=YOUR_PROJECT_ID
Created service account [cloud-run-secret-demo].

作成したサービスアカウント
次に、作成したサービスアカウントに対して、特定の Secret へのアクセス権(roles/secretmanager.secretAccessor)を付与します。
gcloud secrets add-iam-policy-binding my-db-password \
--member="serviceAccount:cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
--role="roles/secretmanager.secretAccessor" \
--project=YOUR_PROJECT_ID
Updated IAM policy for secret [my-db-password].
bindings:
- members:
- serviceAccount:cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
role: roles/secretmanager.secretAccessor
etag: BwZUy8VAZNQ=
version: 1

シークレットへの権限付与
ステップ 4: サンプルアプリの準備
Secret の読み取りを確認するためのサンプルアプリを作成します。以下の 3 ファイルを同じディレクトリに作成してください。
app.py
import os
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def index():
return jsonify({"status": "ok"})
@app.route("/env-secret")
def env_secret():
secret = os.environ.get("DB_PASSWORD", "")
if not secret:
return jsonify({"method": "environment_variable", "loaded": False}), 500
return jsonify({"method": "environment_variable", "loaded": True, "value": secret})
@app.route("/volume-secret")
def volume_secret():
try:
with open("/etc/secrets/db/password", "r") as f:
secret = f.read()
return jsonify({"method": "volume_mount", "loaded": True, "value": secret})
except Exception as e:
return jsonify({"method": "volume_mount", "loaded": False, "error": str(e)}), 500
if __name__ == "__main__":
port = int(os.environ.get("PORT", 8080))
app.run(host="0.0.0.0", port=port, debug=False)
requirements.txt
Flask==3.1.0
Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app.py .
ENV PORT=8080
EXPOSE 8080
CMD ["python", "app.py"]
次に、Artifact Registry にリポジトリを作成します。
gcloud artifacts repositories create secret-demo-repo \
--repository-format=docker \
--location=asia-northeast1 \
--project=YOUR_PROJECT_ID
Create request issued for: [secret-demo-repo]
Waiting for operation [projects/YOUR_PROJECT_ID/locations/asia-northeast1/operations/e2583df1-2c70-42f4-b1df-b55364552a54] to
complete...done.
Created repository [secret-demo-repo].
app.py・requirements.txt・Dockerfile を配置したディレクトリで以下のコマンドを実行すると、cloudbuild.yaml などの設定ファイルは不要で、カレントディレクトリの Dockerfile を使ったビルドからイメージのプッシュまでを Cloud Build が自動で実行します。
# app.py / requirements.txt / Dockerfile を配置したディレクトリで実行
gcloud builds submit \
--tag asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
--project=YOUR_PROJECT_ID

Artifact Registryのビルドイメージ
ステップ 5-A: 方法① 環境変数として参照する
--update-secrets フラグを使い、ENV_VAR_NAME=SECRET_NAME:VERSION の形式でSecret Managerを環境変数に指定します。
gcloud run deploy my-service \
--image asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
--update-secrets=DB_PASSWORD=my-db-password:1 \
--service-account=cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID
DB_PASSWORD: コンテナ内で参照する想定の環境変数名my-db-password: Secret Manager の Secret 名1: バージョン番号(環境変数の場合は特定バージョンへの固定を推奨)
デプロイ後、サービスの設定で環境変数が正しく設定されていることを確認します。
gcloud run services describe my-service \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID
✔ Service my-service in region asia-northeast1
URL: https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
Ingress: all
Traffic:
100% LATEST (currently my-service-00001-5t7)
Scaling: Auto (Min: 0, Max: 100)
Last updated on 2026-06-21T23:31:35.741366Z by YOUR_EMAIL@example.com:
Revision my-service-00001-5t7
Container None
Image: asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest
Port: 8080
Memory: 512Mi
CPU: 1000m
Secrets:
DB_PASSWORD my-db-password:1
Startup Probe:
TCP every 240s
Port: 8080
Initial delay: 0s
Timeout: 240s
Failure threshold: 1
Type: Default
Service account: cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
Concurrency: 80
Timeout: 300s

デプロイしたCloud Runサービス
アプリケーション側では、通常の環境変数として参照できます。
作成されたCloud Runの /env-secret エンドポイントにリクエストして、Secret が正しく読み取れているか確認します。
# サービス URL を取得
SERVICE_URL=$(gcloud run services describe my-service \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID \
--format="value(status.url)")
# 環境変数で読み取った Secret を確認
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"${SERVICE_URL}/env-secret"
以下のレスポンスが返りました。環境変数として Secret が正しく参照できています。
{
"loaded": true,
"method": "environment_variable",
"value": "my-super-secret-password"
}
ステップ 5-B: 方法② ボリュームとしてマウントする
--update-secrets フラグで /PATH=SECRET_NAME:VERSION の形式(パスがスラッシュで始まる)でボリュームマウントを指定します。
gcloud run deploy my-service \
--image asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest \
--update-secrets=/etc/secrets/db/password=my-db-password:latest \
--service-account=cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID
Deploying container to Cloud Run service [my-service] in project [YOUR_PROJECT_ID] region [asia-northeast1]
✓ Deploying... Done.
✓ Creating Revision...
✓ Routing traffic...
Done.
Service [my-service] revision [my-service-00003-56t] has been deployed and is serving 100 percent of traffic.
Service URL: https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
/etc/secrets/db/password: コンテナ内のファイルパス(/etc/secrets/db/がマウントパス、passwordがファイル名)my-db-password: Secret Manager の Secret 名latest: バージョン(ボリュームマウントはlatestの使用が可能)
アプリケーション側では、ファイルとして読み取ります。
デプロイ後、設定を確認します。
gcloud run services describe my-service \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID
✔ Service my-service in region asia-northeast1
URL: https://my-service-XXXXXXXXXXXX.asia-northeast1.run.app
Ingress: all
Traffic:
100% LATEST (currently my-service-00003-56t)
Scaling: Auto (Min: 0, Max: 100)
Last updated on 2026-06-21T23:40:27.103632Z by YOUR_EMAIL@example.com:
Revision my-service-00003-56t
Container None
Image: asia-northeast1-docker.pkg.dev/YOUR_PROJECT_ID/secret-demo-repo/secret-demo:latest
Port: 8080
Memory: 512Mi
CPU: 1000m
Secrets:
/etc/secrets/db my-db-password:latest
DB_PASSWORD my-db-password:1
Startup Probe:
TCP every 240s
Port: 8080
Initial delay: 0s
Timeout: 240s
Failure threshold: 1
Type: Default
Service account: cloud-run-secret-demo@YOUR_PROJECT_ID.iam.gserviceaccount.com
Concurrency: 80
Scaling:
Max instances: 100
Timeout: 300s

再デプロイしたCloud Runサービス
更新したCloud Runの /volume-secret エンドポイントにリクエストして、Secret が正しく読み取れているか確認します。
# サービス URL を取得(ステップ 5-A で取得済みの場合は不要)
SERVICE_URL=$(gcloud run services describe my-service \
--region=asia-northeast1 \
--project=YOUR_PROJECT_ID \
--format="value(status.url)")
# ボリュームマウントで読み取った Secret を確認
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"${SERVICE_URL}/volume-secret"
以下のようなレスポンスが返れば、ボリュームマウントとして Secret が正しく参照できています。
{
"loaded": true,
"method": "volume_mount",
"value": "my-super-secret-password"
}
ローテーション後の自動反映を確認する
ボリュームマウント方式の特徴である「再デプロイなしで最新バージョンを自動取得する」動作を確認します。
Secret の新しいバージョンを追加します。
echo -n "my-rotated-password" | gcloud secrets versions add my-db-password \
--data-file=- \
--project=YOUR_PROJECT_ID
Created version [2] of the secret [my-db-password].

更新したSercet
再デプロイは行わず、そのまま /volume-secret エンドポイントに再リクエストします。
curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" \
"${SERVICE_URL}/volume-secret"
新しいバージョンの値が返ってきました。再デプロイなしで最新の Secret が反映されていることを確認できました。
{
"loaded": true,
"method": "volume_mount",
"value": "my-rotated-password"
}
まとめ
本記事では、Cloud Run から Secret Manager の機密情報を参照する 2 つの方法をご紹介しました。
環境変数方式はデプロイ設定だけで完結するためシンプルで、既存のコードをほぼ変更せずに利用できます。一方で、値はインスタンス起動時に一度だけ解決されるため、Secret をローテーションした場合は新しいリビジョンの起動が必要です。意図せず最新バージョンが読み込まれるリスクを避けるため、特定のバージョン番号への固定が推奨されています。
ボリュームマウント方式はファイルを読み取るたびに Secret Manager から最新の値を取得するため、ローテーションとの相性が良く、証明書ファイルのような複数行データの管理にも適しています。ただし、ボリュームのマウント先として /dev・/proc・/sys は使用できず、既存ディレクトリをマウントパスに指定するとその配下のファイルがアクセス不能になる点に注意が必要です。また、Cloud Run はリージョナル Secret をサポートしていないため、自動レプリケーションポリシーの Secret を使用してください。
どちらの方式を選ぶ場合も、最小権限の原則に従い、Cloud Run のサービスアカウントには roles/secretmanager.secretAccessor をプロジェクト全体ではなく特定の Secret に対して付与することで、セキュリティリスクを最小限に抑えることができます。
この記事が誰かの助けになれば幸いです。
以上、クラウド事業本部コンサルティング部の渡邉でした!







