Skip to main content

Layered Secrets and Environment Variable Management for Cloud Functions / CloudBase Run

In one sentence: Separate the environment variables for Cloud Functions / CloudBase Run / frontend into dev / staging / prod configurations — local development uses .env + .gitignore, CI uses tcb login --apiKeyId/--apiKey to inject permanent keys + pipeline variables, production uses Console environment variables — ensuring no secret ever enters git history or a frontend bundle.

Estimated time: 30 minutes | Difficulty: Intermediate

Applicable Scenarios

  • You have deployed Cloud Functions / CloudBase Run services containing LLM API keys, database passwords, or third-party credentials.
  • You want to upgrade from "scattered local .env files and a Console that someone updates manually for production" to a standardized workflow.
  • You are setting up a CI pipeline (Tencent's internal Git / CODING / GitHub Actions) and need the pipeline to access Secrets without putting them in the repository.

Not applicable:

  • Solo personal projects with no compliance requirements — .env + .gitignore is sufficient.
  • Projects using a dedicated third-party secret manager (HashiCorp Vault, AWS Secrets Manager) — those have their own established workflows.

What Not to Do

Starting with what you must not do is more important than how to do it right:

Anti-patternWhy it's wrong
Hardcoding secrets in source codeThey live in git history forever — rotating once doesn't save you; even private repositories carry this risk
Putting secrets in package.json or package-lock.jsonSame as above, and these files are almost always committed
Leaving real values in .env.example "as reference"Someone copies it and forgets to change the values — real secrets enter git
Putting API keys in WeChat Mini Program / web frontend codeFrontend code is accessible to end users via DevTools or decompilation; treat every frontend variable as public
Debugging with console.log(process.env.OPENAI_KEY) and not removing it before commitLogs enter monitoring systems; anyone with log access has the key
Sharing one production key across the whole teamWhen anyone leaves, the entire key set must be rotated
Pasting a key in a commit message or PR description "for reference"Searching sk- in history exposes it
Committing .env to a private repository thinking "it's not public"Team membership changes, repository migrations, and backup leaks are all attack vectors; private does not mean secure

Three-Layer Environment Model

┌─────────────────────┐
│ Local Dev │ .env (local) + .gitignore + .env.example (template)
└─────────────────────┘

┌─────────────────────┐
│ CI Pipeline │ Pipeline variables (Tencent's internal Git / CODING) + tcb login --apiKeyId/--apiKey
└─────────────────────┘

┌─────────────────────┐
│ Production (prod) │ CloudBase Console "Environment Variables"
└─────────────────────┘

Each layer explained below.

Layer 1: Local Development

.env + .gitignore

Create these in the project root:

# Local dev config (never commit this)
touch .env
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore
echo "*.env" >> .gitignore

.env content (adjust key names to your project):

# LLM gateway proxy token for local dev
PROXY_ACCESS_TOKEN=local-dev-token-xxxxxxxxxxxxxxxx

# Real upstream LLM key
UPSTREAM_API_KEY=sk-xxxxxxxxxxxxxxxxxxxxxxxx

# Local dev PostgreSQL connection (local Docker instance)
PG_HOST=127.0.0.1
PG_PASSWORD=local-pg-password

Commit a template for teammates — with no real values:

touch .env.example

.env.example:

# Copy this file to .env and fill in real values
PROXY_ACCESS_TOKEN=
UPSTREAM_API_KEY=

PG_HOST=
PG_PASSWORD=

.env.example is safe to commit — it's just a skeleton with no values. New team members clone the repo, run cp .env.example .env, then fill in values from internal documentation.

Reading .env in Node.js code

Install dotenv in Cloud Function and CloudBase Run Node.js projects:

npm install --save-dev dotenv

At the very top of the entry file:

// Only load .env in local dev; in CloudBase, environment variables are injected by the platform
if (process.env.NODE_ENV !== 'production') {
require('dotenv').config();
}

const apiKey = process.env.UPSTREAM_API_KEY;

When deployed to CloudBase, NODE_ENV=production is set by the platform by default, so it won't attempt to read .env. This prevents accidentally bundling .env into the deployment and having it loaded.

.gitignore Checklist

Before any commit, verify .gitignore includes at least these (adjust as needed):

# Environment variables
.env
.env.*
!.env.example
*.env

# CloudBase CLI cache
.cloudbase/
cloudbaserc.json.local

# Auto-generated key files
tcb_custom_login.json
*.pem
*.key
firebase-adminsdk-*.json
service-account*.json

!.env.example is the exception that allows the template file into git.

Layer 2: CI Pipeline

CI cannot use a local .env — it runs on a separate machine with no .env in the repository. Use pipeline variables instead.

Configuring pipeline variables

Using Tencent's internal Git (CODING / GitLab works the same) as an example:

  1. Repository → "Settings → Pipeline → Pipeline Variables"
  2. Add variables, e.g. TCB_DEPLOY_TOKEN, PROD_UPSTREAM_API_KEY, PROD_PG_PASSWORD
  3. Enable "Protected" + "Masked" (Protected limits the variable to protected branches; Masked shows *** in logs)

Non-interactive login for CloudBase CLI in CI

CloudBase CLI cannot use the browser login flow in CI (no one to click Confirm). Use tcb login --apiKeyId/--apiKey to explicitly inject Tencent Cloud permanent keys (distinct from the local browser-based tcb login):

# 1. Non-interactive login with permanent keys
tcb login --apiKeyId $TENCENT_SECRET_ID --apiKey $TENCENT_SECRET_KEY

# 2. Deploy
tcb fn deploy llm-proxy --httpFn -e your-env-id

Create TENCENT_SECRET_ID / TENCENT_SECRET_KEY in the Tencent Cloud CAM Console. Use these only for CI deployment — developers should use the browser-based tcb login locally (temporary credentials, following least privilege).

Never echo $TENCENT_SECRET_KEY in CI steps — if logs are ever exposed, the key is public. Pipeline masking is a last line of defense, not a primary control.

Pushing application-layer secrets to Cloud Functions in CI

Deploy code and inject business secrets:

# 1. Log in CLI with permanent keys
tcb login --apiKeyId $TENCENT_SECRET_ID --apiKey $TENCENT_SECRET_KEY

# 2. Deploy function code
tcb fn deploy llm-proxy --httpFn -e your-env-id

# 3. Update environment variables via CloudBase OpenAPI / @cloudbase/manager-node
# (See manager-node's functions.updateFunctionConfig for the specific API)

Alternatively, write environment variables into cloudbaserc.json's functions.envVariables field to have them updated during deployment. Do not commit secrets from cloudbaserc.json to git — use cloudbaserc.local.json + .gitignore, or replace placeholder values with real ones in CI using envsubst / sed before deployment.

Refer to the current version of @cloudbase/cli for exact commands — the environment variable management subcommands vary across CLI versions; check tcb fn --help for actual options.

Layer 3: Production Environment

Production secrets ultimately live in CloudBase Console Environment Variables:

ResourceConfiguration path
Cloud Functions (event / Web)Console → Cloud Functions → Function Details → Function Config → Environment Variables
CloudBase Run servicesConsole → Cloud Hosting → Service Details → Service Settings → Version Management → configure when creating a new version

Cloud Hosting and Cloud Functions handle environment variables differently:

DimensionCloud FunctionsCloudBase Run
Effect after changeTakes effect on next invocation (hot-updated on instance)Requires publishing a new version and shifting traffic
Bound to versionNo (function-level)Yes (per service version, each version is independent)
Dockerfile defaultN/AENV KEY=VALUE sets defaults; Console variables of the same name take precedence

Cross-environment (dev / staging / prod) naming convention

If production, staging, and dev each have their own CloudBase Environment, use the same variable name in each — the same key UPSTREAM_API_KEY holds different values in prod vs. staging automatically.

If all three environments share one CloudBase Environment (not recommended but common), use prefixes:

PROD_UPSTREAM_API_KEY=...
STAGING_UPSTREAM_API_KEY=...
DEV_UPSTREAM_API_KEY=...

Select the right one in code based on process.env.STAGE:

const stage = process.env.STAGE || 'dev';
const apiKey = process.env[`${stage.toUpperCase()}_UPSTREAM_API_KEY`];

Recommended: use three separate Environments. Resource isolation is always more reliable than naming conventions.

Special note on frontend secrets

Any environment variable that appears in frontend code is not a secret, including:

  • Next.js NEXT_PUBLIC_*
  • Vite VITE_*
  • Any config value in a WeChat Mini Program

These are statically embedded into JS bundles at build time. Anyone can read them with browser DevTools or the WeChat developer tool.

The correct approach: let the frontend only hold a public endpoint URL; keep secrets in Cloud Function / CloudBase Run backends. For example:

Wrong: const OPENAI_KEY = 'sk-xxx'; // in Mini Program code
Correct: const PROXY_URL = 'https://your-env.service.tcloudbase.com/llm-proxy'; // in Mini Program code
// OPENAI_KEY stays in Cloud Function environment variables

If you have already hardcoded a key in frontend code, that key is compromised. No code change undoes this — you must rotate it.

What to Do If a Key Is Already Leaked

Rotation procedures differ by service. The most common ones:

OpenAI

  1. Log in to platform.openai.com → API Keys
  2. Find the leaked key and click Revoke (immediately invalidated — all calls return 401)
  3. Create a new secret key
  4. Update the key in every Cloud Function / CloudBase Run Environment Variable that uses it
  5. Check the billing page for anomalous usage during the exposure window

Tencent Cloud CAM Permanent Keys

  1. CAM Console → Access Keys
  2. Find the leaked SecretId, click "Disable", then delete it
  3. Create a new SecretId / SecretKey pair
  4. Update CI pipeline variables
  5. Immediately check Operation Logs for anomalous API calls during the exposure window

WeChat Mini Program AppSecret

  1. WeChat Open Platform → Development Settings → Reset AppSecret
  2. The old AppSecret is immediately invalidated (no grace period, unlike OpenAI — this is a hard cut)
  3. Immediately update all Cloud Function environment variables that use it and redeploy; otherwise production breaks instantly

Database Password

  1. Log in via DMC tool → Account Management → Reset Password
  2. Update all Cloud Function / CloudBase Run environment variables that use it
  3. Existing sessions remain connected until they close, but new connections will fail immediately

After every rotation, do one more thing: investigate how the key ended up in git. Was it a missed commit check? Did someone accidentally treat .env as .env.example? Was CI log masking not enabled? The root cause matters more than the rotation — otherwise it will happen again.

Pre-production Checklist

Run through this before any production deployment:

  • .gitignore includes .env, .env.* (except .env.example), tcb_custom_login.json, *.pem
  • Searched repository history for sk-, AKID, -----BEGIN PRIVATE — confirmed no secrets remain (git log -S "sk-" -- '*.js')
  • CI uses permanent keys + masking; local dev uses tcb login web-flow
  • No API keys in frontend code (grep for sk-, Bearer , apiKey:)
  • All Cloud Function / CloudBase Run environment variables are injected from Console or CI — none in code
  • Team has shared documentation covering which variable lives under which CAM sub-account and who owns rotation
  • Critical keys (database / payment) have usage alerts configured to trigger on anomalous volume

Common Errors

SymptomCauseFix
process.env.X is undefined after deploymentConsole environment variable not added, or function not redeployedAfter adding a variable in the Console, trigger a redeployment (Cloud Functions pull new values, but redeploying is the safe path)
Changed NEXT_PUBLIC_*, restarted service, frontend still has old valueThese variables are injected at build time — restart does not update themRe-run npm run build and redeploy
.env accidentally committed to git.gitignore was misconfiguredgit rm --cached .env, add to .gitignore, then rotate all exposed keys (they remain visible in git history — adding to .gitignore does not erase history)
CI logs show *** but deployed function returns 401CI variable name was mistyped — masking hides the mistakeAdd a diagnostic step like `printenv
cloudbaserc.json placeholder not replaced before deployPlaceholder went directly into Cloud Function environment variablesAdd a grep step in CI to check for \${.*} unresolved markers before deploying
Production Mini Program login breaks immediately after AppSecret resetCloud Function environment variables not updated firstEnforce the order: "update environment variables first, then reset AppSecret"
Same key used in both staging and productionNo environment isolationUse three separate CloudBase Environments for proper isolation

Next Steps

Once this baseline is in place, consider:

  • connect-openai-api-cloud-function — The best-practice reference for LLM key isolation; all rules from this recipe apply directly.
  • deploy-nextjs-to-cloudbase-run — Environment variable layering for Next.js (NEXT_PUBLIC_* vs. server-side).
  • add-rag-with-pgvector-cloudbase — A compound scenario managing both database passwords and LLM keys simultaneously.