Accessing container environment at startup time with typescript / react / docker
Here a solution that works with .env
files that can be included via
env_file: myapp.env
in docker-compose or directly as .env
.
Basic idea is following this approach https://blog.codecentric.de/react-application-container-environment-aware-kubernetes-deployment
Basic idea
Provide a config.js
file as static hosted resource under public
at container startup. Use the entrypoint
of the docker container to generate the config.js
. Link to the config.js
within index.html
to make it available in the app.
Full working example
Step by step instruction. Git repo here
- Create example app
npx create-react-app read-env-example --template typescript
- Navigate to fresh app
cd read-env-example
- Create Dockerfile
mkdir -p docker/build
docker/build/Dockerfile
# build environment
FROM node:19-alpine3.15 as builder
WORKDIR /app
ENV PATH /app/node_modules/.bin:$PATH
COPY package.json ./
COPY package-lock.json ./
RUN npm ci
RUN npm install react-scripts@5.0.1 -g
COPY . ./
RUN PUBLIC_URL="." npm run build
# production environment
FROM nginx:stable-alpine
COPY --from=builder /app/build /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
COPY docker/build/docker-entrypoint.sh /
RUN chmod +x docker-entrypoint.sh
ENTRYPOINT ["/docker-entrypoint.sh"]
- Create docker-entrypoint.sh
This script will be executed at container start.
It generates the config.js
file containing all environment variables starting with 'MYAPP' under window.extended
.
docker/build/docker-entrypoint.sh
#!/bin/sh -eu
function generateConfigJs(){
echo "/*<![CDATA[*/";
echo "window.extended = window.extended || {};";
for i in `env | grep '^MYAPP'`
do
key=$(echo "$i" | cut -d"=" -f1);
val=$(echo "$i" | cut -d"=" -f2);
echo "window.extended.${key}='${val}' ;";
done
echo "/*]]>*/";
}
generateConfigJs > /usr/share/nginx/html/config.js
nginx -g "daemon off;"
- Create docker-compose.yml
mkdir docker/run
docker/run/docker-compose.yml
version: "3.2"
services:
read-env-example:
image: read-env-example:0.1.0
ports:
- 80:80
env_file:
- myapp.env
- Create runtime config for your app
docker/run/myapp.env
MYAPP_API_ENDPOINT='http://elasticsearch:9200'
- Create config.js <-- this is where .env will be injected.
public/config.js
/*<![CDATA[*/
window.extended = window.extended || {};
window.extended.MYAPP_API_ENDPOINT='http://localhost:9200';
/*]]>*/
Note: This file will be completely overwritten by the docker-entrypoint.sh
. For development purposes you can set it to any value that is appropriate, e.g. when used together with npm start
.
- Include config.js in index.html
public/index.html
<head>
...
<script type="text/javascript" src="%PUBLIC_URL%/config.js" ></script>
...
</head>
<body>
- Make use of your environment variable
src/index.tsx
...
declare global {
interface Window { extended: any; }
}
root.render(
<React.StrictMode>
<App {...{MYAPP_API_ENDPOINT:window.extended.MYAPP_API_ENDPOINT}}/>
</React.StrictMode>
);
...
src/App.tsx
...
type Config={
MYAPP_API_ENDPOINT:string
}
function App(props : Config) {
return (
<div className="App">
<header className="App-header">
<div>
You have configured {props.MYAPP_API_ENDPOINT}
</div>
</header>
</div>
);
}
...
src/App.test.tsx
test('renders learn react link', () => {
render(<App {...{MYAPP_API_ENDPOINT:"teststring"}}/>);
const linkElement = screen.getByText(/You have configured teststring/i);
expect(linkElement).toBeInTheDocument();
});
- Build and test
npm install
npm test
- Create docker image
docker build -f docker/build/Dockerfile -t read-env-example:0.1.0 .
- Run container
docker-compose -f ./docker/run/docker-compose.yml up
- Navigate to your app
Open http://localhost in your browser.
You will see the content of MYAPP_API_ENDPOINT
like you provided in your docker/run/myapp.env
.
- Further usage
You can provide additional variables starting with MYAPP
. The docker-entrypoint.sh
script will search for all variables starting with MYAPP
and make them available through the windows
object.