I am completely new to Docker & NGINX so I’m essentially learning as I go. I’m trying to get my Dockerized MERN app setup for production (not a critical app, just for my learning purposes), and after reading countless tutorials I’ve gotten so far as getting my app only for a Dev environment. However as the title suggests, I want to get this working for a prod-type environment (thinking of hosting in AWS), so I need to optimize it a bit more.
I’ll show you my Dockerfile.prod for the Frontend:
FROM node:18-alpine AS build
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
FROM nginx
# Copy build assets from build/ folder & place them in nginx/html
COPY --from=build /app/build /usr/share/nginx/html
EXPOSE 80
My production NGINX configuration file:
upstream client {
server client:3000;
}
upstream api {
server api:3001;
}
server {
listen 80;
location / {
root /usr/share/nginx/html;
include /etc/nginx/mime.types;
try_files $uri $uri/ /index.html;
}
location /sockjs-node {
proxy_pass http://client;
proxy_http_version 1.1;
# setting 'Upgrade' allows a tunnel setup between client & proxied server http://nginx.org/en/docs/http/websocket.html
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
location /api {
proxy_pass http://api;
}
}
My Dockerfile.prod for NGINX
FROM nginx
COPY ./prod.conf /etc/nginx/conf.d/default.conf
Lastly, my docker-compose.prod.yml file:
version: '3'
services:
nginx:
depends_on:
- api
- client
restart: always
build:
dockerfile: Dockerfile.prod
context: ./nginx
ports:
- "3000:80"
api:
build:
dockerfile: Dockerfile
context: ./backend
volumes:
- '/app/node_modules'
- './backend:/app'
command: npm start
client:
build:
dockerfile: Dockerfile.prod
context: ./frontend
volumes:
- '/app/node_modules'
- './frontend:/app'
environment:
- WDS_SOCKET_PORT=0
tty: true
stdin_open: true
From my understanding, the NGINX container is built first using its Dockerfile.prod, thus overriding its default.conf file with the prod.conf I made. Then upon the build of the ‘client’ container (my frontend) & the execution of its respective Dockerfile.prod, the build contents will be copied over to that NGINX’s html folder. So technically, that ‘Welcome to NGINX’ screen should’ve been overridden right?
However when inspecting the location where I copied my build contents, that doesn’t seem to be the case (see NGINX Image /usr/share/nginx/html contents). For what its worth, the client container seems to be built before the NGINX container does, at least from the order I see the logs being printed out (see Docker Compose Output). Regardless, it seems like I’m doing something wrong, and my only hunch is that the NGINX portion of the Frontend’s Dockerfile.prod is overriden by the execution of the NGINX’s Dockerfile.prod. Can anyone please help me? I’m just beyond confused at this point.
Directory Structure:
.
├── frontend
│ ├── Dockerfile
│ ├── Dockerfile.prod
│ ├── babel.config.json
│ ├── nginx
│ │ └── default.conf
├── nginx
│ ├── Dockerfile
│ ├── Dockerfile.prod
│ ├── default.conf
│ └── prod.conf
├── docker-compose.prod.yml
├── docker-compose.yml
2
Answers
I want to preface that @Geilmaker's answer is the correct answer. However I also want to give additional details in case anyone has a similar setup.
The main issue was as Geilmaker pointed out, that I essentially had two nginx images (one containing the build contents for the React portion). The first created during the Dockerfile.prod for the Frontend & the other created during the docker-compose.prod.yml execution:
But I didn't expose the nginx image with build contents. Rather, I exposed the one with just its default configuration. Furthermore, since the Dockerfile.prod for the Frontend only ran 'npm run build', the React app was not being served & prod.conf was configured to just default to its index.html for any default location - which at this point is only the welcome page.
So what I had to do was to rely on the nginx proxy created for Dockerfile.prod for the Frontend & add some of the configuration used for the prod.conf into the default.conf that resides within the Frontend folder (for example the proxying for the Express api).
The result looking like:
frontend/Dockerfile.prod
frontend/nginx/default.conf
And docker-compose.prod.yml looking like:
Notice the removal of the 'nginx' definition & the port-mapping occuring within the 'client' definition.
Upon executing
I can finally see my React application. Thanks @Geilmaker again 🙏
Let’s start with serving the react app for production, I would recommend the following tutorial on this: https://dev.to/thexdev/dockerize-production-reactjs-3ai9
So you might want to have the following
Dockerfile.prod
:The
default.conf
file you need to create should look like the following:I would also recommend creating a
.dockerignore
file in the same directory where your Dockerfile lives with the following content:That way you don’t copy that folder into the image – it’s created itself by the
npm i
command.With that done you should have a production react app.
Then you should edit your
docker-compose.prod.yml
:That way you have an nginx instance, that should work as a proxy and route your requests to the api or client container.
For that to work you need to create the
sites-enabled
directory where yourdocker-compose.prod.yml
file lives (so it gets mounted as volume inside the nginx container). Inside that folder you need to create a<anyname>.conf
file with the following content:That would be a production setup. You have one image for each service and an nginx container, that is receiving the requests and route them to the corresponding service. You shouldn’t use the nginx instance inside your client container to also route requests to the api container, that would be bad practice. The nginx in your client image is only for making this container a standalone, it should not interact externally.