Reputation: 1655
I am using docker to build my react application and deploy it in nginx.
I have set an environment variable in docker-compose.yml
version: '2'
services:
nginx:
container_name: ui
environment:
- HOST_IP_ADDRESS= xxx.xxx.xx.xx
build:
context: nginx/
ports:
- "80:80"
After the docker container is created I can see hi
when I echo
the variable inside the container.
However, when I am trying to read it in react using process.env.HOST_IP_ADDRESS
it is logging undefined
.
I read in a blogpost somewhere that the env variables can be only accessed in production environment. Since, I am building the app and deploying it in nginx, I should be able to access it, but for some reason I am not able to read it.
Am I doing something fundamentally wrong here. If so, please let me know a solution. I am not a react expert, I am just managing someone else's code.
UPDATE:
The Dockerfile looks as follows:
FROM node:8 as ui-builder
WORKDIR /home/ui
COPY helloworld .
RUN npm install
RUN npm run build
FROM nginx
COPY --from=ui-builder /home/ui/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
The React Component snippet is as follows:
import React, { Component } from 'react';
class HelloWorld extends Component {
render() {
console.log(process.env.HOST_IP_ADDRESS);
return (
<div className="helloContainer">
<h1>Hello, world!</h1>
</div>
);
}
}
export default HelloWorld;
Upvotes: 61
Views: 99421
Reputation: 1
Environment variables used in React applications must begin with REACT_APP_
for security reasons and convention.
For example: REACT_APP_KEYCLOAK_CLIENT_ID
.
Upvotes: 0
Reputation: 9578
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
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.
Step by step instruction. Git repo here
npx create-react-app read-env-example --template typescript
cd read-env-example
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 [email protected] -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"]
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;"
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
docker/run/myapp.env
MYAPP_API_ENDPOINT='http://elasticsearch:9200'
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
.
public/index.html
<head>
...
<script type="text/javascript" src="%PUBLIC_URL%/config.js" ></script>
...
</head>
<body>
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();
});
npm install
npm test
docker build -f docker/build/Dockerfile -t read-env-example:0.1.0 .
docker-compose -f ./docker/run/docker-compose.yml up
Open http://localhost in your browser.
You will see the content of MYAPP_API_ENDPOINT
like you provided in your docker/run/myapp.env
.
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.
Upvotes: 5
Reputation: 386
Step 1: Add args for env to docker-compose file
We use args instead of environment field because environment fields are not available on build stage
services:
...
web_app:
build:
context: .
dockerfile: Dockerfile
args:
- MY_ENV=HELLO_WORLD
Step 1_alternative: If image is built by cloudbuild instead of docker-compose, we should add args to cloudbuild.yml file
steps:
- name: ...
args:
...
- --build-arg
- MY_ENV=HELLO_WORLD
Step: 2: Add ARGS and ENVS to dockerfile
We use ARG command to get variables from docker-compose args
We use ENV to set env for the build
ARG MY_ENV
ENV MY_ENV=$MY_ENV
RUN echo "$MY_ENV"
Step 3: Update webpack config
plugins: [
new webpack.ProvidePlugin({
process: "process/browser"
}),
new webpack.DefinePlugin({ "process.env": JSON.stringify(process.env) })
]
Upvotes: 0
Reputation: 221
I use Github CI to set secrets per env. First in GH action file I use run: docker build ... --build-arg REACT_APP_APIURL=${{ secrets.REACT_APP_APIURL }} .
Then I use them in Dockerfile
in my repo to build there final image with the React app like:
ARG REACT_APP_APIURL
RUN test -n "$REACT_APP_APIURL" || (echo "REACT_APP_APIURL not set in GH/your environment" && false)
...
RUN npm run build
This value is used in the npm run build
automatically (used in my react typescript codebase as process.env.REACT_APP_APIURL
). I chose to check for this value and let the app fail immediately on load if something is wrong with my Docker image or configuration somewhere.
export const config = {
apiUrl: setApiUrlFromEnv(),
};
function setApiUrlFromEnv() {
if (process.env.REACT_APP_APIURL) {
return process.env.REACT_APP_APIURL;
} else {
// if something goes wrong in setup, do not start app and show err directly in web console
throw new Error(
`ENV not configured properly (REACT_APP_APIURL) to use desired ENV variables (${process.env.REACT_APP_APIURL})`
);
}
}
Upvotes: 0
Reputation: 1655
I would like to thank everyone who posted answers and comments.The problem that I was facing was solved using a combination of these answers and some help from other resources.
As suggested by @DavidMaze (in the comments), I started looking into webpack config present in my code. I found out that the webpack was reading all the environment variables declared inside the container.
So I started experimenting with my Dockerfile and docker-compose.yml as I realized that REACT_APP_HOST_IP_ADDRESS
was not being passed as an environment variable when the react was building the code.
The first thing I changed was the Dockerfile. I statically declared the IP inside dockerfile for testing
ENV REACT_APP_HOST_IP_ADDRESS localhost
.
By doing this I was able to see the value localhost inside the env variables which were read by webpack.
Now I tried passing the ENV Variable from docker-compose to dockerfile as suggested by @Alex in his answer, but it didn't work.
So I referred to https://github.com/docker/compose/issues/5600 and changed the docker-compose.yml and Dockerfile as follows
docker-compose.yml
version: '2'
services:
nginx:
container_name: ui
build:
context: nginx/
args:
REACT_APP_HOST_IP_ADDRESS: ${IP_ADDRESS}
ports:
- "80:80"
where IP_ADDRESS
is exported as an env variable.
Dockerfile
FROM node:8 as ui-builder
WORKDIR /home/ui
COPY helloworld .
RUN npm install
ARG REACT_APP_HOST_IP_ADDRESS
ENV REACT_APP_HOST_IP_ADDRESS $REACT_APP_HOST_IP_ADDRESS
RUN npm run build
FROM nginx
COPY --from=ui-builder /home/ui/build /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]
React Component
import React, { Component } from 'react';
class HelloWorld extends Component {
render() {
console.log(process.env.REACT_APP_HOST_IP_ADDRESS);
return (
<div className="helloContainer">
<h1>Hello, world!</h1>
</div>
);
}
}
export default HelloWorld;
This configuration makes available the variables passed via ARG in docker-compose to Dockerfile during the image build process and hence the variables can be in turn declared as env variables which React can use during build process provided the webpack reads the env variables.
The webpack will be able to read the env variables using DefinePlugin https://webpack.js.org/plugins/define-plugin/.
Make sure you prefix your variables with REACT_APP_
(as seen here), otherwise it won't be picked up by React.
Upvotes: 86
Reputation: 12420
You should check next moments
I. You env variables have prefix REACT_APP_
II. In docker file you have ARG and ENV commands like
ARG REACT_APP_DEBUG
ENV REACT_APP_DEBUG=$REACT_APP_DEBUG
III. you pass your arg as build arg in docker-compose.yml it looks like
services:
my-app:
build:
args:
REACT_APP_DEBUG=True
or in docker build it looks like
docker build -t my_app:dev --build-arg REACT_APP_DEBUG=True .
Upvotes: 10
Reputation: 15603
Here is my solution using ENV
in my Dockerfile
, DefinePlugin
in the webpack.config.js
and process.env
in my javascript codes:
First set you environment variable and its value in your Dockerfile
:
...
RUN npm install
ENV MY_ENV_VAR my_env_value
...
Then using DefinePlugin
plugin, add it to process.env
in webpack.config.js
:
const webpack = require('webpack');
...
plugins: [
new webpack.DefinePlugin({
'process.env.MY_ENV_VAR': JSON.stringify(env.MY_ENV_VAR),
}),
],
...
And finally use the env variable in your code:
const host = process.env.MY_ENV_VAR || 'a_default_value_in_case_no_env_is_found';
Upvotes: 2
Reputation: 2174
Env variables should start with REACT_APP_ otherwise NODE_ENV variables are a bit confused and your environment variable will not work:
environment:
- REACT_APP_DEBUG=TRUE
Otherwise, docker-compose.yml
is not valid and you will see an error message:
services.client.environment contains an invalid type, it should be an object, or an array
Here is a working sample:
docker-compose.yml
version: "3.3"
services:
client:
container_name: client
environment:
- REACT_APP_DEBUG=TRUE
build:
dockerfile: Dockerfile
context: ./web/client
Dockerfile
FROM node:6.0.0
# Set env variable
ARG REACT_APP_DEBUG
ENV REACT_APP_DEBUG=$REACT_APP_DEBUG
# that will be empty
RUN echo "DEBUG": $REACT_APP_DEBUG
Run:
->docker-compose run client node
->process.env.REACT_APP_DEBUG
'TRUE'
Upvotes: 4
Reputation: 8418
I checked how it's done in API Platform, config just defines consts based on env ('.env' file):
export const API_HOST = process.env.REACT_APP_API_ENTRYPOINT;
export const API_PATH = '/';
Importing this you have one value (API_HOST) while process.env.HOST_IP_ADDRESS
refers to deep object structure unavailable at runtime.
Upvotes: 1