DEV Community

Cover image for Static No More: Transforming Frontend Deployment with Dynamic Configuration
Dominik
Dominik

Posted on

Static No More: Transforming Frontend Deployment with Dynamic Configuration

tldr;

Leverage bundler tools and a runtime script to inject dynamic configurations into static frontend deployments, enabling a single build to adapt to multiple environments seamlessly.

Preface

If you're serious about your DevOps practices, you likely aim for a build once, deploy anywhere approach. The idea behind this approach is that you build your application artifact (e.g. a Docker image) once and with the tweaking of configuration parameters you can deploy it to multiple environments, such as staging and production. This approach is also described in the popular Twelve Factor App methodology.

In your backend applications it is quite easy to achieve that. You have a running process and you can simply access your environment variables at runtime.

In static frontend applications however, this is not as easy. You probably have some sort of web server that serves your pre-built files. There is no process environment you can access from your client-side JavaScript code. So how can you make sure in such cases to be able to deploy the same artifact to any environment?

Leverage your bundler's capabilities

The following passages are based on Vite, but the concepts are applicable to other frontend JS tools as well.

Vite allows for the injection environment variables at build time. Environment variables prefixed with VITE_ are bundled with your application. These variables prefixed with VITE_ become accessible in your code as import.meta.env.VITE_<VARIABLE_NAME>.

If you let vite inject this .env file during the build process, the first two ones will be accessible via import.meta.env.VITE_BACKEND_API_URL and import.meta.env.VITE_GOOGLE_MAPS_KEY. SOME_SECRET is not prefixed with VITE_, so it will be disregarded.

VITE_BACKEND_API_URL=https://example.com/api
VITE_GOOGLE_MAPS_KEY=12345qwerty
SOME_SECRET=mySecret
Enter fullscreen mode Exit fullscreen mode

Awareness of this capability is crucial for achieving our ultimate goal.

The final trick

Rather than passing the actual values, we use placeholder values:

VITE_BACKEND_API_URL=MY_APP_PREFIX_BACKEND_API_URL
VITE_GOOGLE_MAPS_KEY=MY_APP_PREFIX_GOOGLE_MAPS_KEY
Enter fullscreen mode Exit fullscreen mode

Just before starting the application, we run a script that replaces all occurrences of MY_APP_PREFIX_<VARIABLE_NAME> in the bundle with the correct values. Here is a small bash script that you can run in your container right before starting the actual application. This script assumes that we're serving the files with an nginx web server from the standard directory /usr/share/nginx/html.

#!/bin/sh
RELEVANT_ENV_VARS=$(env | grep MY_APP_PREFIX)
for i in $RELEVANT_ENV_VARS
do
    ENV_VAR_NAME=$(echo $i | cut -d '=' -f 1)
    ENV_VAR_VALUE=$(echo $i | cut -d '=' -f 2-)

    find /usr/share/nginx/html -type f -name '*.js' -exec sed -i "s|${ENV_VAR_NAME}|${ENV_VAR_VALUE}|g" '{}' +
done
Enter fullscreen mode Exit fullscreen mode

Let's dissect this script line by line.

  • RELEVANT_ENV_VARS=$(env | grep MY_APP_PREFIX) - we identify relevant environment variables by searching for those with the specified prefix.
  • for i in $RELEVANT_ENV_VARS - we loop over each of the found variables.
  • ENV_VAR_NAME=$(echo $i | cut -d '=' -f 1) - the left hand side of an environment variable declaration is the name. cut is the equivalent of the widely available split('=') function in other languages.
  • ENV_VAR_VALUE=$(echo $i | cut -d '=' -f 2-) - the right hand side of an environment variable declaration is the value.
  • find /usr/share/nginx/html -type f -name '*.js' - we search for all JavaScript files in the html folder
    • -exec sed -i "s|${key}|${value}|g" '{}' + - for all found files execute a sed command that replaces all occurrences of the variable name with the variable value.

All you now have to do is run this script before serving your application and adding your environment variables to the execution context of your container. Here is a small Dockerfile example. The example assumes you have a built bundle in dist/my-app

FROM nginx:alpine

RUN apk add --no-cache bash

WORKDIR /usr/share/nginx/html

COPY dist/my-app .

COPY replace-envs.sh /usr/local/bin/replace-envs.sh

EXPOSE 8080

CMD /bin/bash -c "/usr/local/bin/replace-envs.sh && exec nginx -g 'daemon off;'"
Enter fullscreen mode Exit fullscreen mode

This is it! If you now run your container and pass the environment variables accordingly, they will be replaced before starting the actual nginx process. In a docker-compose.yml this could look like this:

version: '3'
services:
  my-app:
    image: my-app # replace with your image
    ports:
      - '8080:8080'
    environment:
      - MY_APP_PREFIX_BACKEND_API_URL=https://example.com/api
      - MY_APP_PREFIX_GOOGLE_MAPS_KEY=12345qwerty
Enter fullscreen mode Exit fullscreen mode

Conclusion

By leveraging the ability of modern bundlers such as Vite to inject environment-specific variables and using a clever script to replace placeholders with actual environment values at runtime, developers can maintain a single artifact across multiple environments. In addition to streamlining the deployment process, this methodology aligns with the best practices outlined in the Twelve-Factor App Methodology, particularly with respect to app configuration. Implementing these practices ensures that your frontend deployments are as flexible and efficient as your backend, paving the way for a more robust and scalable application infrastructure.

Sources

Vite, Docker, Twelve Factor Apps, Stack Overflow

Top comments (0)