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.