How to Set Up NGINX as a Reverse Proxy for Node.js with Docker: A Complete Step-by-Step Guide
Introduction: What is NGINX and Why Should You Care?
Let’s talk about NGINX – it’s more than just a buzzword. NGINX is a web server, reverse proxy, load balancer, and HTTP cache. Think of it as the traffic cop of the web, making sure data flows smoothly between servers.
Picture a concert with a huge crowd. There’s one person in a neon vest guiding people into different lines based on priority – that’s NGINX, ensuring requests get to the right server without chaos.
In short, NGINX keeps your website fast and efficient, handling thousands of requests at once. If you want your app to stay cool under pressure, NGINX is your best friend. In this tutorial, I’ll show you how to proxy a Node.js app, containerized with Docker, using NGINX.
Prerequisites
Before you dive into this tutorial, make sure you have these tools installed:
Node.js: For creating the Express server
Docker: To containerize your app (and make sure it’s portable and runs smoothly anywhere)
NGINX: The traffic cop for your web requests
SSL Certificates: For securing your app (because who doesn’t love a bit of encryption?)
Project Overview
In this project, we’ll:
Set up a basic Express web server to serve HTML files.
Dockerize the application.
Use Docker Compose to run multiple app instances.
Use NGINX as a reverse proxy to balance traffic across all instances of the app.
Secure everything using SSL certificates.
Setting Up the Express Web Server
Step 1: Create a Basic HTML File
Start simple – create a basic index.html
file that will be served by the Express server. You know, something like “Welcome to My Cool App!” to make it all official.
Step 2: Create the Express Server
Now, let’s set up the real magic. Create a server.js
file with this code:
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
console.log("Request served by node app");
});
app.listen(port, () => {
console.log(`Node app is listening on port ${port}`);
});
This code sets up an Express server that serves static files from the "images" directory and sends the "index.html" file when the root URL is accessed. It listens on port 3000 and logs when a request is served.
Step 3: Install Dependencies
Run the following to install all the needed packages:
npm install express
Step 4: Run the Server
Start the server with:
node server.js
At this point, you should be able to visit http://localhost:3000
and see your “Welcome to My Cool App!” message. Nice!
Containerizing the Application
Step 1: Create a Dockerfile
Time to make your app containerized (aka “let’s put this bad boy in a box so it can run everywhere”). Create a Dockerfile
with the following content:
FROM node:14
WORKDIR /app
COPY server.js .
COPY index.html .
COPY images ./images
COPY package.json .
RUN npm install
EXPOSE 3000
CMD ["node", "server.js"]
This Dockerfile tells Docker how to build your app, from the base image to the working directory and dependencies.
Step 2: Build and Run the Docker Image
Let’s build your Docker image and run it:
docker build -t myapp:1.0 .
docker run -p 3000:3000 myapp
Now you’ve got your app running in a container – neat, right?
Creating Multiple Instances with Docker Compose
Step 1: Create docker-compose.yaml
Let’s make things scalable! Define multiple instances of your app in docker-compose.yaml
:
services:
app1:
build: .
environment:
- APP_NAME=App1
ports:
- "30001:3000"
app2:
build: .
environment:
- APP_NAME=App2
ports:
- "30002:3000"
app3:
build: .
environment:
- APP_NAME=App3
ports:
- "30003:3000"
Now you’ve got multiple instances of the app running, all in different ports. It's like having three lanes at your concert, and everyone can get in faster.
Step 2: Update the Server Code
Let’s update server.js
to show which instance is serving each request:
const express = require('express');
const path = require('path');
const app = express();
const port = 3000;
const replicaApp = process.env.APP_NAME;
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/', (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
console.log(`Request served by ${replicaApp}`);
});
app.listen(port, () => {
console.log(`${replicaApp} is listening on port ${port}`);
});
Step 3: Build and Run Multiple Instances
Run multiple instances with:
docker-compose up --build -d
Step 4: Check Logs
Make sure everything’s running smoothly by checking the logs:
docker-compose logs
Setting Up NGINX as a Reverse Proxy
Step 1: Install NGINX
Install NGINX to start acting as your traffic cop:
sudo apt install nginx
Step 2: Configure NGINX
Create an nginx.conf
file for load balancing:
worker_processes 1;
events {
worker_connections 1024;
}
http {
include mime.types;
upstream nodejs_cluster {
least_conn;
server 127.0.0.1:30001;
server 127.0.0.1:30002;
server 127.0.0.1:30003;
}
server {
listen 8080;
server_name localhost;
location / {
proxy_pass http://nodejs_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real_IP $remote_addr;
}
}
}
This NGINX configuration defines a load balancer for a Node.js app, forwarding requests to a cluster of servers running on ports 30001, 30002, and 30003. It listens on port 8080 and uses the least_conn
method to distribute traffic to the least busy server in the cluster. Additionally, it sets up headers to pass the original host and client IP to the backend servers.
Step 3: Test the NGINX Proxy
Check that NGINX is properly forwarding requests on port 8080:
nginx -t
sudo systemctl restart nginx
Creating a Secure Encrypted Connection
Step 1: Generate SSL Certificates
To secure your app, generate a self-signed SSL certificate:
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout nginx-selfsigned.key -out nginx-selfsigned.crt
Step 2: Update NGINX for SSL
Update the nginx.conf
to enable SSL:
server {
listen 443 ssl;
server_name localhost;
ssl_certificate /path/to/nginx-selfsigned.crt;
ssl_certificate_key /path/to/nginx-selfsigned.key;
location / {
proxy_pass http://nodejs_cluster;
}
}
Step 3: Restart NGINX
Restart NGINX to apply the SSL changes:
sudo systemctl restart nginx
Conclusion
And there you have it! You've set up:
A basic Express web server.
Dockerized it and run multiple instances with Docker Compose.
Configured NGINX as a reverse proxy to handle incoming requests.
Secured your app with SSL certificates to keep the traffic safe and sound.