How I Built and Deployed Micro Frontends Using Webpack, Module Federation, Docker, and NGINX
A step-by-step guide on how I implemented micro frontends with Webpack Module Federation, Docker, and NGINX for scalable React applications.

Micro frontends allow large teams to build and deploy independently scalable frontend applications. In this blog, I’ll walk you through how I built a micro frontend architecture using Webpack Module Federation, Docker, and NGINX, based on a real-world project.
What Is Micro Frontend Architecture?
Micro frontends break a monolithic frontend into smaller, independently deployable pieces. Each team can own and deploy their part of the frontend.
Example structure:
mfe-host
(container app + shared components/utilities)mfe1
(remote)mfe2
(remote)
Structuring the Repositories
- mfe-host/
- mfe1/
- mfe2/
Each MFE is an isolated React app with its own Webpack config and deployment pipeline.
🔧 Basic Webpack Setup
To better manage environment-specific configurations, we use webpack-merge
to separate common, development, and production settings for both the Host and MFE1 apps.
First, install the dependency:
npm i webpack-merge
We’ll define three config files per app:
webpack.common.js
webpack.dev.js
webpack.prod.js
Host MFE Configuration
webpack.common.js
const { ModuleFederationPlugin } = require("webpack").container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
resolve: { extensions: [".js", ".jsx"] },
plugins: [
new ModuleFederationPlugin({
name: "mfe_host",
remotes: {
mfe1: "mfe1@http://localhost:3001/remoteEntry.js",
mfe2: "mfe2@http://localhost:3002/remoteEntry.js",
},
shared: ["react", "react-dom"],
}),
new HtmlWebpackPlugin({ template: "./public/index.html" }),
],
};
webpack.prod.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = merge(common, {
mode: "production",
plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
splitChunks: { chunks: "all" },
},
});
webpack.dev.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
module.exports = merge(common, {
mode: "development",
devServer: {
port: 3000,
historyApiFallback: true,
hot: true,
},
devtool: "eval-source-map",
});
Remote MFE1 Configuration
webpack.common.js
const { ModuleFederationPlugin } = require("webpack").container;
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = {
entry: "./src/index.js",
resolve: { extensions: [".js", ".jsx"] },
plugins: [
new ModuleFederationPlugin({
name: "mfe1",
filename: "remoteEntry.js",
exposes: {
"./Page": "./src/pages/Page",
},
shared: ["react", "react-dom"],
}),
new HtmlWebpackPlugin({ template: "./public/index.html" }),
],
};
webpack.prod.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
const TerserPlugin = require("terser-webpack-plugin");
module.exports = merge(common, {
mode: "production",
plugins: [new MiniCssExtractPlugin({ filename: "[name].[contenthash].css" })],
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
splitChunks: { chunks: "all" },
},
});
webpack.dev.js
const { merge } = require("webpack-merge");
const common = require("./webpack.common");
module.exports = merge(common, {
mode: "development",
devServer: {
port: 3001,
historyApiFallback: true,
hot: true,
},
devtool: "eval-source-map",
});
🌍 Using Environment Variables Across Environments
You’ll often want different values for API endpoints, feature flags, or keys in local, dev, and production environments.
Step 1: Create .env
files in each project root
.env.development
.env.production
.env.local
Example content for .env.development
:
REACT_APP_API_URL=http://localhost:4000
Step 2: Install dotenv plugin (for custom Webpack)
npm install dotenv-webpack --save-dev
Step 3: Configure in Webpack
const Dotenv = require("dotenv-webpack");
plugins: [new Dotenv({ path: "./.env.development" })];
Alternatively, in Create React App (CRA), variables prefixed with REACT_APP_
are auto-injected.
⚠️ Never include sensitive data like secrets or tokens in these files.
Environment-Specific Execution
To specify environment files dynamically in your commands, you can set the DOTENV_CONFIG_PATH
:
npm install dotenv-cli --save-dev
Then update scripts like this:
"scripts": {
"start:dev": "dotenv -e .env.development -- webpack serve --config webpack.dev.js",
"start:prod": "dotenv -e .env.production -- webpack --config webpack.prod.js",
"build:dev": "dotenv -e .env.development -- webpack --config webpack.dev.js",
"build:prod": "dotenv -e .env.production -- webpack --config webpack.prod.js"
}
This ensures the right environment variables are injected based on the context of the command.
🐳 Basic Docker Setup
Dockerfile
FROM node:18 AS build
WORKDIR /app
COPY . .
RUN npm install && npm run build
FROM nginx:alpine
COPY --from=build /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/nginx.conf
Build & Run
docker build -t mfe1 .
docker run -d -p 3001:80 mfe1
🌐 Basic NGINX Configuration
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
location /remoteEntry.js {
add_header Cache-Control "no-cache";
}
}
🔌 Essential Webpack Plugins
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
plugins: [
new HtmlWebpackPlugin({ template: "./public/index.html" }),
new CleanWebpackPlugin(),
];
🚀 Production Optimization Techniques
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const webpack = require('webpack');
plugins: [
new MiniCssExtractPlugin({ filename: '[name].[contenthash].css' }),
new CompressionPlugin({ algorithm: 'gzip' }),
new BundleAnalyzerPlugin({ analyzerMode: 'static', openAnalyzer: false }),
new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production') }),
new webpack.IgnorePlugin({ resourceRegExp: /^\.\/locale$/, contextRegExp: /moment$/ }),
new webpack.optimize.ModuleConcatenationPlugin(),
];
optimization: {
minimize: true,
minimizer: [new TerserPlugin(), new CssMinimizerPlugin()],
splitChunks: { chunks: 'all' },
};
✅ Conclusion
Start with a working micro frontend setup, then incrementally layer in optimizations to make it production-grade. From module federation to caching strategies and bundling best practices—each part matters.