# Reading Config Vars in Docker Often you will need to read in config values in a react app, for example when connecting to a backend API, or when using config values to change various UI elements or control enabled features. When using a full stack framework such as Next.JS or Gatsby, you can use the `process.env` object to read in environment variables as these applications are both server and client side rendered. If we are using a client side only framework we will not have the luxury of using `process.env`. In this case we need to be able to load in our own configuration values into the app running in a Docker container. To this end we can create a `config.json` file, and serve this file in the Docker container mounted as a volume. For this demo we'll be using a simple Vite react frontend, with an ExpressJS backend. We'll be using Docker to containerize our application. ## Creating a Backend ### Setup First get started by creating a `backend` directory, and inside it a `package.json` with the following content: ::: code-group ```json [backend/package.json] { "name": "backend" } ``` ::: ### Installing Dependencies ```bash cd backend npm install typescript express cors npm install -D @types/express @types/cors @types/node ts-node ``` ### Creating the backend In our case the backend will be a simple ExpressJS server that returns a list of users, so we can make this all one file: ::: code-group ```typescript [backend/src/index.ts] import express from "express"; import cors from "cors"; const USERS = [ { id: 1, name: 'John Doe', email: 'john.doe@gmail.com' }, { id: 2, name: 'Jane Doe', email: 'jane.doe@gmail.com' }, { id: 3, name: 'John Smith', email: 'john.smith@gmail.com' } ] const app = express(); app.use(cors()); app.get('/', (_, res) => { res.send('Hello World!'); }); app.get('/users', (_, res) => { res.json(USERS); }); app.listen(3000, () => { console.log('Server is running on port 3000'); }); ``` ::: ### Setting Up Typescript Create a `tsconfig.json` file in the `backend` directory with the following content: ::: code-group ```json [backend/tsconfig.json] { "compilerOptions": { "target": "es2016", "module": "commonjs", "esModuleInterop": true, "strict": true, "skipLibCheck": true, "rootDir": "./src", "outDir": "./dist", } } ``` ::: ### Add Scripts Add the following scripts to the `package.json` file: ::: code-group ```json [backend/package.json] { "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "start": "node dist/index.js" }, } ``` ::: ## Creating a Frontend ### Setup Create a new Vite React TS app using the command `npm create vite@latest frontend -- --template react-ts`, ensuring you are in the parent directory of the `backend` directory. ### Ignore the Local Config File Update the `.gitignore` file to ignore the `config.json` file we will be creating later: ::: code-group ``` [frontend/.gitignore] # ... public/config.json ``` ::: ### Create Config File Create a `config.js` file in the `public` directory with the following content: ::: code-group ```js [frontend/public/config.js] const config = { apiUrl: 'http://localhost:3000' } window.config = config; ``` ::: Next update the `index.html` file to include the `config.json` file: ::: code-group ```html [frontend/public/index.html] ``` ::: ### Create a Config Util Create a `config.ts` file in the `src` directory with the following content: ::: code-group ```typescript [frontend/src/config.ts] export interface Config { apiUrl: string; } export const config = (window as any).config as Config; ``` ::: ### Update App Update the app to call the backend API: ::: code-group ```tsx [frontend/src/App.tsx] import { useEffect, useState } from "react" import { config } from "./config" type User = { id: number, name: string email: string } function App() { const [users, setUsers] = useState([] as User[]) useEffect(() => { const fetchUsers = async () => { const response = await fetch(`${config.apiUrl}/users`) const data = await response.json() setUsers(data) } fetchUsers(); }, []) return ( <>

Users


) } export default App ``` ::: # Running the Application At this stage you will be able to run the application locally by running the backend and frontend separately. The backend will expose the user list, and the frontend will display the list of users. # Dockerise Finally the last step is dockerising the application. Create the following Dockerfiles, docker-compose and config files in the root of the project: ::: code-group ``` [Dockerfile.backend] FROM node:alpine WORKDIR /app COPY package.json . COPY package-lock.json . RUN npm install COPY . . RUN npm run build CMD ["npm", "start"] ``` ``` [Dockerfile.frontend] FROM node:alpine as build WORKDIR /app COPY package.json . COPY package-lock.json . RUN npm install COPY . . RUN npm run build FROM nginx:alpine COPY --from=build /app/dist /usr/share/nginx/html ENTRYPOINT ["nginx","-g","daemon off;"] ``` ```yml [compose.yml] services: api: build: context: './backend' dockerfile: '../Dockerfile.backend' ports: - "8081:3000" web: build: context: './frontend' dockerfile: '../Dockerfile.frontend' ports: - "8080:80" volumes: - './config-production.js:/usr/share/nginx/html/config.js' ``` ```js [config-production.js] const config = { apiUrl: 'http://localhost:8081' } window.config = config; ``` ::: If you then run `docker-compose up` you should see the application running in a Docker container and when navigating to `http://localhost:8080`. # Conclusion In this guide we have seen how to read config values in a React app running in a Docker container. We have created a `config.js` file that we use to store our config values, and then use a volume to mount this file into the Docker container. This allows us to read the config values in our React app. As a follow up this `config.js` file would be created per environment, or even created as part of the CI/CD pipeline, so that the correct values are used for each environment. Please note however as this is a client side app all values will be visible to the end user, so do not store any sensitive information in the `config.js` file.