NestJS Runtime Environment Variables

Introduction

Here goes my experience dockerizing a NestJS application. I aimed to dockerize the entire stack for my master project, which included a Frontend built with NestJS. To achieve this, I used docker-compose to specify the required services, and configured environment variables using a .env file.

Challenge: NestJS and Environment Variables

Initially, I used static values for local development to connect the Frontend to the API service. However, I soon realized that providing the required configuration at runtime was crucial for my project. Furthermore, according to the 12 Factor Application, configuration should be externalized from the application entirely. While it is usually possible to access environment variables in a Node application with process.env.VAR_NAME, it wasn’t that simple with NestJS.

Solution: Workaround for NestJS

NestJS does a few things differently, such as inlining browser-exposed variables, hiding server runtime environment variables, and doing other custom stuff depending on the component used. To prevent NestJS from inlining the variables at build time, I had to prefix the variables with NEXT_PUBLIC.

The workaround to set environment variables at runtime goes like this:

  1. Add the NEXT_PUBLIC Prefix to the variables you are using in your code.
  2. NestJS creates a build artifact where calls to const var = process.env.NEXT_PUBLIC_MY_VAR are substituted with the value, making it const var = 123, if the variable was set to 123.
  3. I created an entrypoint.sh for my Dockerfile that replaces the values defined in .env used in my docker-compose file with those found in the runtime environment.
  4. The values are first replaced by NestJS, then by the entrypoint.sh script, which may seem hacky.
  5. Now the app should correctly

Replacing Environment Variables at Runtime

Here is the entrypoint.sh script that I used to replace environment variables:

#!/bin/sh
 
die() { echo "error: $*"; exit 1; }
 
echo "Replacing environment variables"
 
[ -f ".env" ] || die "missing .env file"
 
# replace .env vars with environment variables
grep "=" .env | while read -r line; do
    key=$(echo "$line" | cut -d'=' -f 1)
    eval value="\$$key"
    [ -z "$value" ] && continue
    echo "replacing '$key' with '$value'"
    find ./.next \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#${key}#${value}#g"
done
 
echo "Starting Nextjs"
exec "$@"

Problem: Rerunning the Variable Replacement

One caveat is that running the container the first time, and the environment values are replaced in the code, the values are static again. So if you want to update the configuration, you’ll have to reset the containers state back to the image.

You can do this fairly easy by running docker compose pull frontend.