DYNAMIC ENV CONFIG USING CUSTOM ENTRYPOINT
What if a base image does not support docker-entrypoint.d
An nginx docker image supports docker-entrypoint.d folder. If you add a bash script in this folder then it will be executed every-time during container startup. But not all the docker images support this feature. In the blog React Project Dynamic Environment Variables we were using nginx docker image for running the react app. We will now see how to externalise the environment variables when the image does not support this.
Assumptions
The app in run inside docker on a docker image that does not support docker-entrypoint.d
We are using serve for running the app (You can use any other static folder serving option)
Folder structure
Create an app named my-app using create-react-app tool. Below is a sample folder structure for the same :
my-app/
├── public
├── Dockerfile
├── docker-compose.yml
├── .env
├── package.json
├── generate-env-config.sh
├── start.sh
└── ...
Create a Dockerfile for the project
FROM node:18-alpine as builder
WORKDIR /app
COPY package*.json ./
RUN npm install
RUN npm install -g pm2
COPY . .
RUN npm run build
FROM node:18-alpine as server
COPY --from=builder /app/build /app/build
COPY --from=builder /app/start.sh /app/start.sh
COPY --from=builder /app/generate-env-config.sh /app/generate-env-config.sh
RUN apk add bash
WORKDIR /app
RUN npm install -g serve
RUN chmod +x start.sh
RUN chmod +x generate-env-config.sh
EXPOSE 3001
CMD ["sh", "start.sh"]
In the panel above you can see a Dockerfile code for dockerizing this react app. It is a two stage docker build. In the first stage we build the project by copying all the source code inside the docker. It generates a build folder as per the output of the npm run build command from react scripts. In the second stage we copy the build folder from the first stage and run that on nginx image as server. We do copy the required scripts i.e generate-env-config.sh and start.sh from builder stage to server stage. In the last step as entrypoint script we are using the CMD command for executing the start.sh. CMD and ENTRYPOINT are two different approaches to run these kinds of entrypoint scripts. These scripts are run whenever you run this docker image or restart the container.
Create a script to generate env.js
#!/bin/bash
ENVJS=/app/build/env.js
touch $ENVJS
echo "const app_env = { " > $ENVJS
ENV_KEYS=( \
"APP_VERSION" \
)
for key in "${ENV_KEYS[@]}"
do
varname=$key
value=$(printf '%s\n' "${!varname}")
echo " $varname: \"$value\"," >> $ENVJS
done
echo "};" >> $ENVJS
The script (generate-env-config.sh) will generate the env.js file by reading the list of ENV_KEYS from the environment variables passed on by Docker container. We create this file at the location /app/build inside the docker container. The script is executed by the start.sh hence this script is run every-time we start the container.
Docker environment variables can be accessed directly in a bash script which is executed as entrypoint script of the docker container.
Contents of env.js
const app_env = {
APP_VERSION: 1.2.3
};
You can name a global constant as app_env or give any other desired name.
Note: The file will be generated using the values passed from docker-compose.yml using environment variables tag. You don't need to create this file manually. The script generate-env-config.sh will do it for you.
Add reference of env.js in index.html
<!DOCTYPE html>
<html lang="en">
<head>
<script src="env.js"></script>
</head>
<body>
The body content is added here....
</body>
</html>
In the create react app project inside the public folder we have an index.html file. Add a script tag for loading the env.js file as shown in the code snippet in the left panel.
Create a script to run the app during container startup
#!/bin/bash
startup_log_file=startup.log
> $startup_log_file
echo "Creating env config file..." >> $startup_log_file
bash generate-env-config.sh >> $startup_log_file
echo "Starting My App..." >> $startup_log_file
serve build >> $startup_log_file
The start.sh script should contain the code as shown in the left panel. The script executes the generate-env-config.sh and then runs the build folder using npm serve library.
Default port number for serve is 3000.
Build and run using docker-compose.yml
version: '3'
services:
my-app:
container_name: my-app
image: my-app:local
ports:
- 3001:3000
build: .
environment:
APP_VERSION: 1.2.3
In the left panel you can see the docker-compose.yml file which builds the image and runs the same inside the docker. The port binding shows that the app is running on your machine port number 3001 with URL http://localhost:3001.
Accessing the environment variable in a React Component
import React from "react";
import logo from "./logo.svg";
import "./App.css";
declare const app_env: any;
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React {app_env.APP_VERSION}
</a>
</header>
</div>
);
}
export default App;
You can access this environment variable in a react component as shown in the left panel code. If you are using typescript template for generating the app then you will need to declare a placeholder variable in this component like declare const app_env: any; as shown in the code.
Summary
In this setup you can now build a docker image for the project and run the same image on different environments by changing the .env file or the environment variables passed to the service declaration inside the docker-compose.yml file. The solution helps for those docker container base images which do not support executing the entrypoint scripts as standard process.