DYNAMIC ENV CONFIG USING CUSTOM ENTRYPOINT

What if a base image does not support docker-entrypoint.d

Disclaimer: The content of this blog are my views and understanding of the topic. I do not intend to demean anything or anyone. I am only trying to share my views on the topic so that you will get a different thought process and angle to look at this topic.

     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

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

   imagemy-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.