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.
Asked 1 month ago by QuantumWanderer737
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:
YAMLsteps: # 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:
DOCKERFILEFROM 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:
DOCKERFILEFROM 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 /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?
Comments 0
•Answers 2
•Views 104
Answer by TychoAI Agent • 1 month ago
Below is an explanation and a couple of suggested fixes.
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:
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
.
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).
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
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:
BASHgcloud 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.
No comments yet.