I am building a series of "Hello, World!" tutorials to edify myself and others on taking a simple (M)ERN app where a React frontend fetch’s a static string from an Express backend and then prints it to the screen (React: ‘Hello,’ Express: ‘ World!’). When run locally the App.js and the server.js work as expected but once I Dockerize them the and run Compose the frontend container cannot resolve the service name for the backend container. NOTE I am trying to do this locally and the nature of the error looks like CORS but I thought I handeled that configuration. Additionally I am using CRA and react-inject-env which based on the console.log is injecting correctly
App.js
import React, { useState, useEffect } from 'react';
import './App.css';
import { env } from './env'
/**
* App component: Fetches a greeting message from a backend API and displays it.
*
* Environment Variables:
* - REACT_APP_BACKEND_URL: URL of the backend service.
* Defaults to "http://localhost:3000" for local development.
*
* @returns {JSX.Element} The rendered App component.
*/
function App() {
// State to hold the greeting fetched from the backend
const [greeting, setGreeting] = useState('');
console.log(env.REACT_APP_BACKEND_URL);
useEffect(() => {
/**
* fetchGreeting function: Asynchronously fetches a greeting message from the backend.
*
* @async
* @function
*/
const fetchGreeting = async () => {
try {
const backendURL = env.REACT_APP_BACKEND_URL || "http://localhost:3000";
//const backendURL = "http://localhost:3000";
// Fetch the greeting from the backend
const response = await fetch(`${backendURL}/`);
const data = await response.text();
// Update state with the fetched greeting
setGreeting(data);
} catch (error) {
console.error("Failed to fetch the greeting: ", error);
}
};
// Invoke the fetch function
fetchGreeting();
}, []); // Empty dependency array ensures this useEffect runs once on component mount.
return (
<div className="App">
<header className="App-header">
<p>Hello, {greeting}</p>
</header>
</div>
);
}
export default App;
Frontend Dockerfile
type here
# Use an official node runtime as a parent image
FROM node:14
# Set the working directory in the container to /app
WORKDIR /app
# Copy package.json and package-lock.json to the container
COPY package*.json ./
# Install dependencies in the container
RUN npm install
# Install serve globally in the container
RUN npm install -g serve
# Bundle app source inside the docker image
COPY . .
# Build the app for production
RUN npm run build
# Indicate the port the app listens on
EXPOSE 3000
# Define the command to run the app
ENTRYPOINT npx react-inject-env set && serve -s build -l 3000
Server.js
/**
* External Dependencies
*/
const express = require("express");
const cors = require("cors");
/**
* Create a new Express application instance
*/
const app = express();
/**
* Middleware
*/
// Use CORS middleware to allow requests from any origin for better cross-origin request support
console.log(process.env.CORS_ORIGIN);
const corsOptions = {
origin: process.env.CORS_ORIGIN || "http://localhost:3001", // replace with your frontend application's URL
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
/**
* Routes
*/
// Endpoint to serve a simple greeting string
app.get("/", (req, res) => {
res.send("World!");
});
/**
* Server Activation
*/
// Start the Express server on port 3000
app.listen(3000, () => {
console.log("Backend server listening on port 3000");
});
Backend Dockerfile
# Use an official node runtime as a parent image
FROM node:14
# Set the working directory in the container to /app
WORKDIR /app
# Copy package.json and package-lock.json to the container
COPY package*.json ./
# Install dependencies in the container
RUN npm install
# Bundle app source inside the docker image
COPY . .
# Define the network port that this container will listen on
EXPOSE 3000
# Define the command to run the app
CMD [ "node", "server.js" ]
docker-compose.yaml
version: "3" # We are using Docker v3 as v2 uses different syntax than that provided
services:
frontend:
build: ./frontend
image: hello-world-frontend:latest
ports:
- "3001:3000" # Exposing frontend on port 3001
environment:
- REACT_APP_BACKEND_URL=http://backend:3000
depends_on:
- backend # Enforces the order with which the images will spin up
networks:
- internal
backend:
build: ./backend
image: hello-world-backend:v1.0.0
ports:
- "3000:3000"
environment:
- CORS_ORIGIN=http://frontend:3001
networks:
- internal
networks:
internal:
driver: bridge
I have tried to lookup the network, Verify that all the names and forwarded ports line up, I am new to DevOps so I’m really not even sure what questions I need to be asking.
2
Answers
here is the Error Stack and the Network Error
Your frontend app runs in your browser which isn’t on the bridge network. So when it needs to talk to the backend, it needs to go through the host.
You only use the docker network service names as hostnames when the containers talk directly to each other. In your scenario, they don’t talk to each other. All the requests are coming from the browser.
That means than you can’t use the
backend
hostname from the browser. You need to use the address of the host – and the mapped host port of the backend service.If you change
to
it’ll work on your local machine.
Likewise, your CORS origin will be
http://localhost:3001
.