This is a summary of popular methods for incorporating the "Build once, deploy everywhere" principal of DevOps when delivering any type of server based application. The main idea is to use the same bundle for all environments, from testing to production. This approach enables easy deployment and testability and is considered a fundamental principle of continuous delivery. Supporting resources are included below and will be referenced where possible.
In static web applications, the configuration is baked in at build time, meaning you most likely need to create individual build artifacts for dev, test, uat, live etc. This can turn into a significant time sink if the build process is even remotely lengthy.
In a ReactJS (create-react-app) application, In we commonly utilise different configuration parameters using the REACT_APP.env properties—but those must be set at compile time. Create-react-app expects you to rebuild the application for each target environment, which violates the principle. And it seems that it is the most common approach nowadays. But what if you want, or need, to follow the build once, deploy many principle?
The goal is to decouple environment-specific configuration from the build process, allowing the same build artifact to be deployed to multiple environments with different configurations.
There are a number of different strategies to achieve this goal but some of the most popular are listed below with their relative effort/complexity, pros and cons.
1. Dynamic Configuration via Public Folder:
- Approach: Store environment-specific configuration files like
config.js
in the public folder of the React application, which is not processed by Webpack. - Effort: Moderate setup effort to organize configuration files and modify the build pipeline.
- Pros: Simplifies the CI/CD pipeline by using the same build for all environments.
- Cons: Configuration files are publicly accessible, which might be a security concern.
- Source: Discussed in the Cevo blog.
2. Environment Variable Injection at Runtime:
- Approach: Use tools like React-Inject-Env to inject environment variables into the application at runtime rather than during the build process.
- Effort: Requires initial setup in the CI/CD pipeline to ensure correct variable injection.
- Pros: Highly flexible and allows for last-minute configuration changes.
- Cons: May require additional tooling or setup in the deployment environment.
- Source: A Build Once, Deploy Many Implementation Guide for React Apps with GitHub Actions and React-Inject-Env
3. External Configuration File:
- Approach: Use an external
config.json
that is fetched at application startup to load configuration dynamically. - Effort: Low to moderate effort, involving creating and managing the config file outside the build process.
- Pros: Easy to update without needing to rebuild or redeploy the application.
- Cons: Potential delay in application startup due to the fetch operation.
- Source: Explained in detail on mikesir87's blog.
4. Using CI/CD Pipeline for Environment Switching:
- Approach: Automate the switching of environment configurations during the deployment phase without rebuilding.
- Effort: High setup effort to script and test the CI/CD pipeline steps.
- Pros: Builds are consistent across all environments; reduces build time.
- Cons: Complexity in pipeline setup and maintenance.
- Source: Commonly referenced in discussions about DevOps practices for SPAs.
5. Server-Side Rendering for Dynamic Injects:
- Approach: Use server-side rendering to dynamically inject environment variables into the application at runtime.
- Effort: High due to the need for server-side capabilities and additional coding.
- Pros: Offers greater control over environment-specific configurations.
- Cons: Increases complexity and resource usage of the application.
- Source: Discussed in the context of React and Redux documentation.
6. Webpack Environment Plugin:
- Approach: Utilize Webpack's EnvironmentPlugin to manage environment variables during the build process.
- Effort: Moderate effort to configure Webpack appropriately.
- Pros: Integrates smoothly into existing Webpack builds, easy to use once set up.
- Cons: Still requires a build per environment if variables are used in the code directly.
- Source: Detailed in Webpack's official documentation.
7. Containerization:
- Approach: Package the application in a container that can be deployed across any environment.
- Effort: High initial effort to set up Docker or other container technologies.
- Pros: Highly portable and consistent across all deployment environments.
- Cons: Requires container management and orchestration knowledge.
- Source: General knowledge and best practices in cloud-native development.
8. Injecting Global Variables:
- Approach: Consider all static assets as immutable and utilise the public scope to host an index.html that is unique to each environment. By including versioned references to the web application static assets and setting the environment variables in the index.html, it effectively becomes a deployment manifest.
- Effort: Low to medium effort to update index.html in your deployment process for each environment and update code to use window.env rather than process.env.
- Pros: Highly portable and consistent across all deployment environments.
- Cons: Configuration files are publicly accessible, which might be a security concern.
- Source: Injecting Data from the Server into the Page
While the strategies list above each have there own benefits, I feel like the last one is the easiest to understand and requires minimal effort to modify an existing React JS based application to take advantage of it. Simply add the "environment variables" to the index.html in your public folder as it won't be bundled:
Use some kind of placeholder like in the example above so that when you deploy the build, it will be easy to quickly update this file. Next, build the app using npm build or yarn build, etc.
Next, simply update any usage of the process.env to retrieve environment variable values to instead use window.env:
That's it! With these changes in place, you can now deploy the production version but all of the environment variables will be defined in the index.html which is not bundled or minified. Use any search and replace tool to find the placeholders you put in the file and substitute them for your true configuration values. After the server hosting the static web app is restarted, the values will be in effect.
How to "Build Once and Deploy Many" for React App in CI/CD - Cevo https://cevo.com.au/post/how-to-build-once-and-deploy-many-for-react-app-in-ci-cd/
A Build Once, Deploy Many Implementation Guide for React Apps with GitHub Actions and React-Inject-Env | by Liam Patty | Medium https://medium.com/@liamhp/build-once-deploy-many-for-react-apps-with-github-actions-and-react-inject-env-f56aa78ffa44
Build Once and Deploy Everywhere for SPAs – mikesir87's blog https://blog.mikesir87.io/2021/07/build-once-deploy-everywhere-for-spas/
Build once, deploy many in React | Profinit blog https://profinit.eu/en/blog/build-once-deploy-many-in-react-dynamic-configuration-properties/
React Application: Build Once, Deploy Anywhere Solution - DEV Community https://dev.to/eamonnprwalsh/react-application-build-once-deploy-anywhere-solution-507m
Environment Variables in JavaScript: process.env https://dmitripavlutin.com/environment-variables-javascript/
Managing Front-end JavaScript Environment Variables https://robertcooper.me/post/front-end-javascript-environment-variables
Title and Meta Tags | Create React App https://create-react-app.dev/docs/title-and-meta-tags/#generating-dynamic-meta-tags-on-the-server
reactjs - How to inject pod environment variables values into React app on runtime? - Stack Overflow https://stackoverflow.com/questions/70085518/how-to-inject-pod-environment-variables-values-into-react-app-on-runtime
EnvironmentPlugin | webpack https://webpack.js.org/plugins/environment-plugin/
Server Rendering | Redux https://redux.js.org/usage/server-rendering#inject-initial-component-html-and-state
The Most Common XSS Vulnerability in React.js Applications | by Emelia Smith | Node Security | Medium https://medium.com/node-security/the-most-common-xss-vulnerability-in-react-js-applications-2bdffbcc1fa0
Injecting Data from the Server into the Page | Create React App https://create-react-app.dev/docs/title-and-meta-tags/#injecting-data-from-the-server-into-the-page