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;
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.
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 .
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'
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';
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.
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})`
);
}
}
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
Use webpack.ProvidePlugin({ process: "process/browser" }) to enable process in web app
Use webpack.DefinePlugin to define env variables available in web app
Add process library to dev dependency by npm i -S process
plugins: [
new webpack.ProvidePlugin({
process: "process/browser"
}),
new webpack.DefinePlugin({ "process.env": JSON.stringify(process.env) })
]
Technically, we can't use environment variables in browser context, that's why we usually use DefinePlugin or EnvironmentPlugin in webpack based projects like CRA and Vue-CLI to statically replace process.env.* with environment variables.
But this way forces us to rebuild the whole application multiple times (e.g., development. staging and production).
To fix this, I want to share you a set of plugins: import-meta-env, with these plugins, you only need to define and pass the env you want to use and the plugins do the rest for you.
During production, you can use this plugin to statically replace import.meta.env.* with some expression (we use import.meta because process.env is a Node specific object), and on the startup of container, you can run a special script to inject your environment variables which may passed from docker run, stored in your Google Cloud Run, etc.
I have also created an example for Docker.
Hope this helps you and people who needs it.
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.
Related
I'm trying to get React to change content of the site when it's file is being saved.
I'm using VS code which doesn't have safe write. I'm using docker-compose on Windows via Docker Desktop.
Dockerfile:
FROM node:17-alpine
WORKDIR /front
ARG FRONT_CMD
ARG API_HOSTNAME
ENV REACT_APP_API_HOSTNAME=$API_HOSTNAME
COPY . .
RUN npm i #emotion/react #emotion/styled
CMD $FRONT_CMD
relevant part of docker-compose.yml:
frontend:
volumes:
- ./frontend/src:/front/src
- /front/node_modules
build:
context: ./frontend
dockerfile: Dockerfile
args:
- FRONT_CMD=${FRONT_CMD}
- API_HOSTNAME=${API_HOSTNAME}
env_file:
- .env.dev
networks:
- internal
environment:
- CHOKIDAR_USEPOLLING=true
- FAST_REFRESH=false
- NODE_ENV=development
Everything is running behind traefik. CHOKIDAR_USEPOLLING and FAST_REFRESH seem to make no difference, I start with ' docker-compose --env-file ..env.dev up' - within the file FRONT_CMD="npm start" which behaves just fine. Env.dev should be clear indication of dev build (and is, works the same without the addition) to React, but I added NODE_ENV just be safe. I tried adding all of them into build envs just be super sure, but nothing changes. React files lay in 'frontend' folder, which is in the same location as docker-compose.yml.
Every time React says it compiled successfully and warns me that it's a development build.
Only suspicion I have left is that there's some issue with updating files with Windows locally while docker uses Linux, but I have no idea where to go from there even if that's the case.
Shortest way I found was to start from the other side, that is attach editor to container instead of updating container based on changes in system files. I followed the guide here: https://code.visualstudio.com/docs/remote/attach-container
I'm using Bitbucket pipelines to deploy my react app to the s3 bucket, deployments work fine but unfortunately, my process.env variables are undefined. I already add my all env variables in deployment variables.
bitbucket-pipeline.yml:
image: node:14
pipelines:
branches:
master:
- parallel:
- step:
name: Build and Test
caches:
- node
script:
- rm -rf package-lock.json
- npm install
- npm rebuild node-sass
- CI=false npm run build:prod
artifacts:
- build/**
- step:
name: Security Scan
script:
# See more security tools at https://bitbucket.org/product/features/pipelines/integrations?&category=security
- pipe: atlassian/git-secrets-scan:0.5.1
- step:
name: Deploy to Production
deployment: Production
trigger: manual
clone:
enabled: false
script:
# sync your files to S3
- pipe: atlassian/aws-s3-deploy:1.1.0
variables:
AWS_ACCESS_KEY_ID: $AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY: $AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION: $AWS_DEFAULT_REGION
S3_BUCKET: $S3_BUCKET
LOCAL_PATH: 'build'
build script in package.json
"build": "env-cmd -f .env.prod react-scripts build",
.env.prod
REACT_APP_BASE_URL=http://myurl/api
App.js
import { useEffect } from "react";
import axios from 'axios'
const App = () => {
const getData = async () => {
console.log("base url: ", process.env.REACT_APP_BASE_URL); // it is undefined
const response = await axios.get(`${process.env.REACT_APP_BASE_URL}/api/users`);
console.log("response: ", response)
}
useEffect(() => {
getData();
}, [])
return <h1>My Component</h1>
};
}
The problem is when I run npm run build locally so the build holds all the env variables from .env.prod because the file that file exists in my local machine but in the pipe line I put the all .env.prod env variables inside deployment variables but unfortunately all the env variables are undefined
React runs in the user's web browser and therefore does not have access to environment variables running on the server. They are different environments.
You can embed environment variables in your app at build time:
Note: You must create custom environment variables beginning with REACT_APP_. Any other variables except NODE_ENV will be ignored to avoid accidentally exposing a private key on the machine that could have the same name. Changing any environment variables will require you to restart the development server if it is running.
The "at build time" part of this is important. To include and use your REACT_APP_BASE_URL variable in your app, make sure to define it in a way your "Build and Test" step can see.
Assuming you have defined that variable for the Production deployment environment, make sure to use that environment for the build step:
- step:
name: Build and Test
# Add this
deployment: Production
caches:
- node
script:
- rm -rf package-lock.json
- npm install
- npm rebuild node-sass
- CI=false npm run build:prod
artifacts:
- build/**
In my case, with the help of Chris I solved it by adding all environment variables inside repository variables
I am facing an issue while building my React project using GitHub as a repository, Travis as CI with AWS ElasticBeanStalk as a service to run my app using Docker. I am able to run my test suite but after that, it is not deploying my app on AWS and also not getting any error in Travis console except below:
Below is my Travis .yml file configuration:
language: generic
services:
- docker
before_install:
- docker build -t heet1996/my-profile -f Dockerfile.dev .
script:
- docker run heet1996/my-profile npm run test -- --coverage
deploy:
provider: elasticbeanstalk
region: "us-east-1"
app: "My-profile"
env: "MyProfile-env"
bucket_name: "elasticbeanstalk-us-east-1-413920612934"
bucket_path: "My-profile"
on:
branch: master
access_key_id: $AWS_ACCESS_KEY
secret_access_key: "$AWS_SECRET_KEY"
Let me know if you need more information
A couple things you could try:
Your script command needs to set the environment var CI=true
So
script:
- docker run heet1996/my-profile npm run test -- --coverage
Becomes
script:
- docker run -e CI=true heet1996/my-profile npm run test -- --coverage
Also AWS needs the access variables to be named differently.
Change
access_key_id: $AWS_ACCESS_KEY
secret_access_key: "$AWS_SECRET_KEY"
To
access_key_id: "$AWS_ACCESS_KEY_ID"
secret_access_key: "$AWS_SECRET_ACCESS_KEY"
Using the option --coverage, your test will hang, waiting for input. Hence the message: "...no output has been received in the last 10m0s...".
At a certain point, --coverage was probably able to stop tests (as some used for that purpose), but I guess it was not meant for that and subsequent versions of docker removed that behavior.
Your test must conclude and the conclusion be a success for the deployment by Travis to begin.
Use instead the option --watchAll=false. So you should have:
...
script:
- docker run heet1996/my-profile npm run test -- --watchAll=false
...
That would take care of the obvious issue of your test never concluding (that could be the only issue). Afterward, make sure that your tests are successful. Then, you can worry about other issues such as authentication on AWS, etc...
I have a ReactJS application and I want to deploy to 3 production environments: production1, production2 and production3.
I have env files as: .env (for development), .env.production1 (production 1), .env.production2 (production2) and .env.production3 (production3).
I run the systax 'npm start' but it reads variables from file .env.
Is there any way to run 'npm start' with specific .env file?
Something like:
npm start (read from .env file)
npm start .env.production1 (read from .env.prod1 file)
npm start .env.production2 (read from .env.prod2 file)
npm start .env.production3 (read from .env.prod3 file)
I read a reference here: https://create-react-app.dev/docs/adding-custom-environment-variables but this is for 'development', 'test' and 'production'. My case is production but for 3 environments.
This is the problem which I have during deployment the production to the client.
If anyone down-votes this, please tell me why?
Thank you.
you need four .env file
.env
.env.production1
.env.production2
.env.production3
Then read form .env using
let mode = process.env.NODE_ENV
which could be production1 or production2 or production3
then use mode to get other configuration data like
require('dotenv').config({ path: `/path/to/.env.${mode}` })
when you need to switch between configuration then just modify .env like below
NODE_ENV = production1
or
NODE_ENV = production2
or
NODE_ENV = production3
I'm setting up ReactJS Project to change environment variables at Run-time.
I have 2 ENV variables:
1) NODE_ENV = Production, Development
2) FLAVOUR = Alpha, Beta
I want to load different configurations, theme and images based on FLAVOUR variable. Which is already done and working.
Build commands:
1) cross-env NODE_ENV=production --env.FLAVOUR=Alpha webpack
2) cross-env NODE_ENV=production --env.FLAVOUR=Beta webpack
--
But above commands are changing env variables at Build-Time. But I want to change FLAVOUR variable only for Production env at Run-Time after the build.
Ex. ./build cross-env --env.FLAVOUR=Alpha node server
Ex. ./build cross-env --env.FLAVOUR=Beta node server
./build is build directory, created by npm run build command.
--
Which means when I change the FLAVOUR variable to Beta and restart the web app
My expectation is that it will be the Beta app and Theme.
This helps us with scaling these webapps. They can be re-purposed on a dime.
Webpack Production:
new webpack.DefinePlugin({
'process.env.FLAVOUR': JSON.stringify(process.env.FLAVOUR),
})
I don't know if this answers your question, but I have set up my project using webpack as the following
new webpack.EnvironmentPlugin({
ENV: process.env.ENV || 'development',
API_BASE: process.env.API_BASE,
}),
I have API_BASE defined in an .env file which webpack picks up when I run yarn start for development
Once I am done developing, I build a production docker image and I can overwrite the API_BASE in the environment file while running the image as
docker build -t yourProjectName .
docker run -e API_BASE='http:\/\/yourIPAddress:port/' -p 9000:80 yourProjectName
This is possible because in my Dockerfile I run bash script whenever the image runs
RUN chmod +x setup.sh
ENTRYPOINT ["sh","/setup.sh"]
CMD ["nginx", "-g", "daemon off;"]
and the setup.sh file
/bin/sed -i "s||${API_BASE}|g" /usr/share/nginx/html/app.*.js
exec "$#"
The script looks for the string http://localhost:5000/ and replaces it with whatever you pass during runtime
Hope this helps!
I have come across this problem today and have solved it by making process.env available through webpack externals.
const config = {
externals: [{ processEnv: 'process.env' }, nodeExternals({})],
}
The line above tells webpack that there is a dependency called processEnv and should be resolved by referencing process.env. It outputs a small commonjs module.
// WEBPACK'S BUILD
/***/ "processEnv":
/*!******************************!*\
!*** external "process.env" ***!
\******************************/
/***/ ((module) => {
"use strict";
module.exports = process.env;
/***/ }),
In your code, you can require this shim by doing.
const processEnv = require('processEnv')
I have documented the process in more detail here https://blog.freemiumpn.com/docs/developer/how-to/web/runtime-configs-with-webpack-and-docker-v2