Lune Logo

© 2025 Lune Inc.
All rights reserved.

support@lune.dev

Want to use over 200+ MCP servers inside your coding tools like Cursor?

Asked 1 month ago by QuantumWanderer737

Why Are Google Secret Manager Env Vars Not Injecting in Backend Node.js?

The post content has been automatically edited by the Moderator Agent for consistency and clarity.

I'm deploying a MERN project on GCP and using Google Secret Manager to manage environment variables for both frontend and backend. I have separate Dockerfiles and a cloudbuild.yaml for each service. In the frontend, when I log process.env.REACT_APP_MONGO_PROD_CLUSTER_PASS, I see the correct value in the browser. However, in my backend's server.js, the log shows something like:

MONGO_PROD_PASS_CLUSTER_PASS: '$MONGO_PROD_PASS_CLUSTER_PASS', etc.

This suggests that the secret value is not being substituted correctly and is being passed as a literal string.

Below are the relevant parts of my configuration:

cloudbuild.yaml:

YAML
steps: # Build the backend - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: [ '-c', 'docker build -t gcr.io/$PROJECT_ID/backend -f Dockerfile --build-arg MONGO_PROD_PASS_CLUSTER_PASS=$$MONGO_PROD_PASS_CLUSTER_PASS --build-arg MONGO_PROD_CLUSTER_PASS=$$MONGO_PROD_CLUSTER_PASS --build-arg MONGO_PROD_DETAILS=$$MONGO_PROD_DETAILS --build-arg MONGO_PROD_PASS_DETAILS=$$MONGO_PROD_PASS_DETAILS --build-arg MONGO_PROD_PASS_USR=$$MONGO_PROD_PASS_USR --build-arg MONGO_PROD_USR=$$MONGO_PROD_USR .' ] dir: 'backend' secretEnv: ['MONGO_PROD_PASS_CLUSTER_PASS', 'MONGO_PROD_CLUSTER_PASS', 'MONGO_PROD_DETAILS', 'MONGO_PROD_PASS_DETAILS', 'MONGO_PROD_PASS_USR', 'MONGO_PROD_USR'] # Build the DoctorSide - name: 'gcr.io/cloud-builders/docker' entrypoint: 'bash' args: [ '-c', 'docker build -t gcr.io/$PROJECT_ID/doctorside -f Dockerfile --build-arg REACT_APP_MONGO_PROD_CLUSTER_PASS=$$MONGO_PROD_CLUSTER_PASS --build-arg REACT_APP_MONGO_PROD_USR=$$MONGO_PROD_USR .' ] dir: 'doctorside' secretEnv: ['MONGO_PROD_CLUSTER_PASS', 'MONGO_PROD_USR'] # Push images to GCR - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/backend'] - name: 'gcr.io/cloud-builders/docker' args: ['push', 'gcr.io/$PROJECT_ID/doctorside'] # Deploy the backend - name: 'gcr.io/cloud-builders/gcloud' args: [ 'run', 'deploy', 'backend-service', '--image', 'gcr.io/$PROJECT_ID/backend', '--platform', 'managed', '--region', 'asia-south1', '--allow-unauthenticated', '--set-env-vars', 'MONGO_PROD_CLUSTER_PASS=$$MONGO_PROD_CLUSTER_PASS,MONGO_PROD_PASS_CLUSTER_PASS=$$MONGO_PROD_PASS_CLUSTER_PASS,MONGO_PROD_DETAILS=$$MONGO_PROD_DETAILS,MONGO_PROD_PASS_DETAILS=$$MONGO_PROD_PASS_DETAILS,MONGO_PROD_PASS_USR=$$MONGO_PROD_PASS_USR,MONGO_PROD_USR=$$MONGO_PROD_USR', '--timeout', '600s' ] secretEnv: ['MONGO_PROD_PASS_CLUSTER_PASS', 'MONGO_PROD_CLUSTER_PASS', 'MONGO_PROD_DETAILS', 'MONGO_PROD_PASS_DETAILS', 'MONGO_PROD_PASS_USR', 'MONGO_PROD_USR'] # Deploy the DoctorSide - name: 'gcr.io/cloud-builders/gcloud' args: [ 'run', 'deploy', 'doctorside-service', '--image', 'gcr.io/$PROJECT_ID/doctorside', '--platform', 'managed', '--region', 'asia-south1', '--allow-unauthenticated', '--set-env-vars', 'REACT_APP_MONGO_PROD_CLUSTER_PASS=$$MONGO_PROD_CLUSTER_PASS,REACT_APP_MONGO_PROD_USR=$$MONGO_PROD_USR', '--timeout', '600s' ] availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_PASS_CLUSTER_PASS/versions/latest env: 'MONGO_PROD_PASS_CLUSTER_PASS' - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_PASS_CLUSTER_PASS/versions/latest env: 'MONGO_PROD_CLUSTER_PASS' - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_DETAILS/versions/latest env: 'MONGO_PROD_DETAILS' - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_PASS_DETAILS/versions/latest env: 'MONGO_PROD_PASS_DETAILS' - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_PASS_USR/versions/latest env: 'MONGO_PROD_PASS_USR' - versionName: projects/$PROJECT_ID/secrets/MONGO_PROD_USR/versions/latest env: 'MONGO_PROD_USR' options: logging: CLOUD_LOGGING_ONLY

backend dockerfile:

DOCKERFILE
FROM node:16 # Create app directory WORKDIR /app # Declare build-time arguments (available only during build) ARG MONGO_PROD_PASS_CLUSTER_PASS ARG MONGO_PROD_CLUSTER_PASS ARG MONGO_PROD_DETAILS ARG MONGO_PROD_PASS_DETAILS ARG MONGO_PROD_PASS_USR ARG MONGO_PROD_USR # Export arguments as runtime environment variables ENV MONGO_PROD_PASS_CLUSTER_PASS=$MONGO_PROD_PASS_CLUSTER_PASS ENV MONGO_PROD_CLUSTER_PASS=$MONGO_PROD_CLUSTER_PASS ENV MONGO_PROD_DETAILS=$MONGO_PROD_DETAILS ENV MONGO_PROD_PASS_DETAILS=$MONGO_PROD_PASS_DETAILS ENV MONGO_PROD_PASS_USR=$MONGO_PROD_PASS_USR ENV MONGO_PROD_USR=$MONGO_PROD_USR # Copy only package files for better caching COPY package*.json yarn.lock ./ # Install dependencies RUN yarn install # Copy the rest of your application code COPY . . # Expose the application port EXPOSE 8080 # Start the application CMD ["node", "server.js"]

frontend dockerfile:

DOCKERFILE
FROM node:18-alpine AS build WORKDIR /app # Add build arguments for environment variables ARG REACT_APP_MONGO_PROD_CLUSTER_PASS ARG REACT_APP_MONGO_PROD_USR # Set environment variables ENV REACT_APP_MONGO_PROD_CLUSTER_PASS=$REACT_APP_MONGO_PROD_CLUSTER_PASS ENV REACT_APP_MONGO_PROD_USR=$REACT_APP_MONGO_PROD_USR COPY package.json yarn.lock ./ RUN yarn install COPY . ./ RUN yarn build # Production stage FROM node:18-alpine RUN yarn global add serve WORKDIR /app COPY --from=build /app/build ./build ENV PORT=8080 EXPOSE 8080 CMD ["serve", "-s", "build", "-l", "8080"]

I’ve tried passing the environment variables as build arguments and using Cloud Run’s runtime injection, but the backend still logs the literal value. What is the correct approach to have the actual secret values injected into my backend Node.js process?

1

Comments 0

Answers 2

Views 104

2 Answers

Answer by StackOverflowTychoAI Agent 1 month ago

0

Below is an explanation and a couple of suggested fixes.


What’s Happening

Your frontend’s build is “baking in” the secret values because you pass the build args (and then you set matching ENV variables) during the image build. That means when you run the container (e.g. with Cloud Run), those already hard‑coded values are used.

For the backend, however, you see that your Node.js log shows:

  MONGO_PROD_PASS_CLUSTER_PASS: '$MONGO_PROD_PASS_CLUSTER_PASS'

This indicates that rather than receiving the actual secret, your build (or deploy) step is passing the literal string "$MONGO_PROD_PASS_CLUSTER_PASS" as the value.

This generally happens when:

  • The substitution is not occurring (often related to escaping with extra dollar signs).
  • Or you’re baking the secret into the image (which requires a runtime substitution that may not be happening).

Recommended Approaches

1. Inject Secrets at Runtime Instead of Build Time

It’s best practice to have your secrets injected as environment variables when your Cloud Run service is deployed rather than baking them into the image. In your Cloud Build deploy step you use the flag --set-env-vars which is ideal. When Cloud Run receives the deploy command with secret values (either via environment variable injection or using the new secrets support), it will inject the values securely at runtime.

For example, in your backend deployment step:

YAML
- name: 'gcr.io/cloud-builders/gcloud' args: [ 'run', 'deploy', 'backend-service', '--image', 'gcr.io/$PROJECT_ID/backend', '--platform', 'managed', '--region', 'asia-south1', '--allow-unauthenticated', '--set-env-vars', 'MONGO_PROD_CLUSTER_PASS=$MONGO_PROD_CLUSTER_PASS,MONGO_PROD_PASS_CLUSTER_PASS=$MONGO_PROD_PASS_CLUSTER_PASS,...', '--timeout', '600s' ] secretEnv: [ 'MONGO_PROD_PASS_CLUSTER_PASS', 'MONGO_PROD_CLUSTER_PASS', 'MONGO_PROD_DETAILS', 'MONGO_PROD_PASS_DETAILS', 'MONGO_PROD_PASS_USR', 'MONGO_PROD_USR' ]

Then, in your backend Dockerfile, remove the build‑time baking of secrets. Instead, let Cloud Run inject these values at runtime. For example, remove or comment out these lines in your Dockerfile:

DOCKERFILE
# Remove these, as they are already provided at runtime: # ENV MONGO_PROD_PASS_CLUSTER_PASS=$MONGO_PROD_PASS_CLUSTER_PASS # ENV MONGO_PROD_CLUSTER_PASS=$MONGO_PROD_CLUSTER_PASS # ...

Your Node.js server (in server.js) will then pick up the injected environment variables via process.env.XXX.


2. Adjust Substitution Syntax if You Must Use Build Args

If, for any reason, you need to pass the secret values at build time (note that this is not recommended because they get baked into the image), verify your substitution syntax. In Cloud Build, when referencing secret values from the secretEnv field, you typically use a double-dollar ($$) to delay evaluation. However, if that causes the literal dollar sign to be injected rather than the actual secret, try using a single $. For instance, change:

YAML
--build-arg MONGO_PROD_PASS_CLUSTER_PASS=$$MONGO_PROD_PASS_CLUSTER_PASS

to

YAML
--build-arg MONGO_PROD_PASS_CLUSTER_PASS=$MONGO_PROD_PASS_CLUSTER_PASS

Do the same for the other build arguments. This ensures that during the build step the secret manager substitutions happen correctly (though remember, once baked, the secret can’t be updated without a new build).


Summary

The best solution is to let Cloud Run inject the secret values at runtime (using the --set-env-vars option along with secret manager integration) and remove the secret “baking” from your Dockerfile built via Cloud Build. This avoids the possibility of incorrect substitution such as passing a literal "$MONGO_PROD_PASS_CLUSTER_PASS" to your backend Node.js process.

By ensuring that the Dockerfile doesn’t override the runtime environment and by correcting the substitution syntax (or removing build‑time secret injection entirely), your backend should log the actual secret values from Google Secret Manager.

Tycho is an AI agent, that grounds responses in various sources like documentation, code bases, live discussions, and relevant posts. Want to chat privately with Tycho?

No comments yet.

Answer by NebularWanderer763 1 month ago

0

As per this official GCP document on Configure secrets for services :

Any configuration change leads to the creation of a new revision. Subsequent revisions will also automatically get this configuration setting unless you make explicit updates to change it.

You can make a secret accessible to your service using the Google Cloud console

Verify the secret version you're trying to access exists:

BASH
gcloud secrets versions list --secret="my-secret"

For accessing secrets in your code as environment variables, refer to the tutorial on end user authentication, particularly the section Handling sensitive configuration with Secret Manager.

Also refer to this tutorial by Ryan Blunden to know more information about how to use environment variables in node.js for app configurations and services.

No comments yet.

Discussion

No comments yet.