Configuring and Using .env files
by John Vincent
Posted on May 24, 2021
The multitude of requirements create the requirement there be many .env
files. This requirement creates some challenges.
This is part of a series of discussions regarding Deploying TaskMuncher, a React and Node application, to a Multi-Container Docker Environment at AWS using Dockerhub and Travis CI
For more details, please see Overview of Create Multi-Container Docker TaskMuncher Application at AWS
The following sections include
Overview
The traditional solution is to configure the application to get a .env
file from the root of the application.
The .env
file would be replaced in a production environment with a .env
from private storage.
This simple approach is usually sufficient.
However, the requirements of this project far exceed the usual. Let's review.
Docker Requirements
Notice that client
, a React application, requires 3 .env
files and server
, a Node/Express application requires 2 .env
files.
Further, configuring a Docker image for development use often requires the source code be mounted into the container, for example docker-compose.yml
version: '3'
services:
client:
build: .
ports:
- 8080:8080
volumes:
- ./client:/app
...
which mounts my local ./client
into the container at /app
.
This will also copy .env
if it is in the ./client
directory or any sub-directory.
Further, it is not possible to copy another .env
file to /app
. The .env
file has to copied elsewhere in the file system and must be referenced by the application.
Thus the traditional structure of providing a .env
file in the root directory of the application cannot satisfy the requirements.
Non-Docker Requirements
The components client
and server
are also built individually in the customary manner.
Client Application
The client application is built with React. The requirement is to build an application for
- Development
- Production in a Development environment
- AWS Production in a Docker environment
- Non-AWS Production
which can be seen in the following partial package.json
"start": "ENV_FILE=$(pwd)/envs/dev.env webpack serve",
"build:dev:prod": "ENV_FILE=$(pwd)/envs/dev-prod.env npm run build:actual:production",
"build:production": "ENV_FILE=$(pwd)/envs/production.env npm run build:actual:production",
"build:actual:production": "rm -rf dist && webpack --mode production --progress"
Server Application
The server application is built with Node/Express. The requirement is to build an application for
- Development
- Production in a Development environment
- Production
- Unit tests
which can be seen in the following partial package.json
"nodemon": "ENV_FILE=./envs/dev.env nodemon server.js",
"start": "ENV_FILE=./envs/dev.env node server.js",
"start:dev:prod": "ENV_FILE=./envs/dev-prod.env node server.js",
"start:production": "ENV_FILE=./envs/prod.env node server.js",
"test": "ENV_FILE=./envs/unit-test.env jest ./test",
Solution
An example of the general solution is
"start": "ENV_FILE=$(pwd)/envs/dev.env webpack serve",
- set
ENV_FILE
to the.env
file - execute the application
Client - React
To obtain the ENV_FILE
values, partial webpack.config.js
follows
const { ENV_FILE } = process.env;
require('dotenv').config({ path: ENV_FILE });
const { HOME_HOST, HOME_PORT } = process.env;
config.devServer = {
disableHostCheck: true,
contentBase: DIST_FOLDER,
compress: false,
host: HOME_HOST,
port: HOME_PORT,
clientLogLevel: 'info',
historyApiFallback: true,
proxy: {
'/api/**': {
target: 'http://localhost:3001',
changeOrigin: true,
secure: false
}
}
which uses the dotenv
package.
Client .env
For a non-Docker .env
file
HOME_HOST=localhost
HOME_PORT=8040
HOME_URL=http://localhost:8040
SERVER_APIS_URL=http://localhost:3110
whereas a .env
for Docker
HOME_HOST=0.0.0.0
HOME_PORT=8040
HOME_URL=http://localhost:8100
SERVER_APIS_URL=http://localhost:8100
For use in a Docker container
config.devServer = {
host: HOME_HOST,
port: HOME_PORT,
is required.
Server - Node/Express
An example of the solution
"nodemon": "ENV_FILE=./envs/dev.env nodemon server.js",
- set
ENV_FILE
to the.env
file - execute
nodemon server.js
A partial ./server.js
const express = require('express');
const cors = require('cors');
const { CONFIG } = require('./config/config');
const { PORT, DATABASE_URL, WHITE_LIST } = CONFIG;
const whitelist = WHITE_LIST;
const corsOptions = {
origin(origin, callback) {
// console.log('origin ', origin);
if (!origin || whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
}
};
app.use(cors(corsOptions));
and a partial ./config/config.js
const { ENV_FILE } = process.env;
require('dotenv').config({ path: ENV_FILE });
const WHITE_LIST = process.env.WHITE_LIST.split(',');
const CONFIG = {
PORT: 1 * process.env.PORT,
HOME_URL: process.env.HOME_URL,
WHITE_LIST,
DATABASE_URL: process.env.DATABASE_URL
};
exports.CONFIG = CONFIG;
which can be referenced by
const { CONFIG } = require('./config');
Server .env
For a non-Docker .env
file
HOME_URL=http://localhost:8040
PORT=3110
WHITE_LIST=http://localhost:8040
whereas a .env
for Docker AWS Production
HOME_URL=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com
PORT=3110
WHITE_LIST=http://taskmuncherdocker-env.eba-mv2hwnxx.us-east-1.elasticbeanstalk.com