- Prerequisites
- Installing Docker and Docker Compose
- Installing Node.js
- Creating the Node.js application
- Building the Node.js application container
- Setting up NGINX as a reverse proxy
- Creating the Docker Compose file for the Node.js and NGINX containers
- Deploying the application using Docker Compose
- Modifying NGINX configuration and service definition
- Verifying Node.js application
- Conclusion
- Install Nginx Proxy Manager with Docker on Ubuntu - Thu, Sep 21 2023
- Dockerizing a Node.js application with NGINX, Let’s Encrypt, and Docker Compose - Mon, Sep 11 2023
- Ansible yum module: Install RHEL/CentOS packages - Wed, Aug 30 2023
Docker Compose allows you to define, configure, and manage multi-container applications in a single YAML configuration file. Developers can define the set of services and dependencies needed for an application and run the entire application on different operating systems.
NGINX is a multifunctional HTTP and reverse proxy server, mail proxy server, and generic TCP/UDP proxy server that is widely used in the DevOps world.
Let's Encrypt is a free, automated, and open certificate authority that provides TLS certificates to over 300 million websites worldwide. It is operated by the nonprofit Internet Security Research Group.
Prerequisites
To follow this guide, the following prerequisites are required:
- An Ubuntu server must be installed on your system.
- A root user or a user with sudo privileges must be configured.
Installing Docker and Docker Compose
Before starting, the Docker and Docker Compose packages must be installed on your server. If they are not installed, follow the steps below to install both packages.
First, install all the packages required to allow apt to install the packages over HTTPS:
apt install apt-transport-https ca-certificates curl software-properties-common -y
Next, download and add Docker's official GPG key:
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
Then, add the Docker repository to the apt source file:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
After that, update the apt package index:
apt update -y
Once the package index is updated, install both the Docker and Docker Compose packages on your server.
apt install docker-ce docker-compose -y
Verify the status of the Docker service after successful installation:
systemctl status docker
You will see the active status of the Docker service on the following screen:
Installing Node.js
You can use the NodeSource repository to install the latest version of Node.js. Here are the steps:
First, add the NodeSource repository to apt:
curl -sL https://deb.nodesource.com/setup_18.x | bash -
Next, update the package index:
apt update -y
Now, install the Node.js from the NodeSource repository:
apt install nodejs -y
Verify that Node.js has been installed by checking its version:
node --version
Creating the Node.js application
In this section, we will create a simple Node.js application and dockerize it in the next section.
First, create a directory for your Node.js application.
mkdir ~/node-app
Then, navigate inside your created directory and run the following command to initialize a new project:
cd ~/node-app npm init -y
This command will create a package.json file in your current working directory. The package.json holds the information about your application.
Then, install the Express.js framework using the npm command. Express is a lightweight framework that provides a set of features for web applications.
npm install express --save
Next, create a server.js file for your application.
nano server.js
Add the following code:
// server.js const express = require('express'); //Create an app const app = express(); app.get('/', (req, res) => { res.send('Node.js application is working!\n'); }); //Listen port const PORT = 8080; app.listen(PORT); console.log(`Running on port ${PORT}`);
Save and close the file. Then, run your Node.js application:
node server.js
The above command will start an Express server on port 8080, as shown below.
http://your-server-ip:8080
Now, open your web browser and verify your application using URL http://your-server-ip:8080. You should receive a message that the Node:js application is working.
Press CTRL+C to stop the Express server.
Building the Node.js application container
To build a container for the Node.js application, we will use Docker to package the application with its dependencies and configuration into a single image. You can deploy and run this image on different platforms easily, without any additional dependencies.
First, create a Dockerfile inside your Node.js application directory.
nano Dockerfile
Add the following code:
# Define Node.js version FROM node:18 # Create app directory WORKDIR /usr/src/app # Install app dependencies # A wildcard is used to ensure both package.json AND package-lock.json are copied COPY package*.json ./ RUN npm install # Bundle app source COPY . . # Define port to expose application for the outside world. EXPOSE 8080 CMD [ "node", "server.js" ]
Next, change the directory to your Node.js application directory and build the Docker image:
cd /root/node-app docker build -t node-app .
This will build the Docker image from your Node.js application, as shown below:
Here is an explanation of the above command:
- -t: Define the name of your image.
- .: Specify the current working directory.
You can now verify your built Docker image using the following command:
docker images
This will show you your Node.js application image, as shown below:
REPOSITORY TAG IMAGE ID CREATED SIZE node-app latest 628798797f8b 19 seconds ago 1.1GB
Finally, run your Node.js application container using the Node.js image:
docker run --name node-app-container -p 80:8080 -d node-app
Here is an explanation of the above command:
- --name: Specify the name of your Node.js application container.
- -p: Map Port 80 from your server machine to Port 8080 in the container.
- -d: Run the container in the background.
You can check the status of your Node.js application container using the following command:
docker ps
You will see the Node.js container status in the following output:
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e8639110ed17 node-app "docker-entrypoint.s…" 10 seconds ago Up 8 seconds 0.0.0.0:80->8080/tcp, :::80->8080/tcp node-app-container
You can now access your Node.js application running inside the Docker container using the URL http://your-server-ip:80.
After successful verification, stop and remove your Node.js application container using the following commands:
docker stop node-app-container docker container rm node-app-container
Next, remove all unused images using the following command:
docker system prune -a
Setting up NGINX as a reverse proxy
At this point, the Dockerfile for your Node.js application is tested and working properly. Now you will need to create an NGINX configuration file to build and run the NGINX container. Using NGINX as a reverse proxy allows you to bind your application with the domain name and secure it with SSL.
First, create a directory in the node-app directory.
mkdir nginx-conf
Then, create an NGINX configuration file:
nano nginx-conf/nginx.conf
Add the following configuration:
server { listen 80; listen [::]:80; root /var/www/html; index index.html index.htm index.nginx-debian.html; server_name node.exampledomain.com www.node.exampledomain.com; location / { proxy_pass http://nodejs:8080; } location ~ /.well-known/acme-challenge { allow all; root /var/www/html; } }
Save and close the file when you're done.
Here is a brief explanation of the NGINX configuration file:
- server_name: Specify your domain name.
- proxy_pass: Specify the Node.js application port running inside the container.
- listen: Specify the NGINX listening port.
Creating the Docker Compose file for the Node.js and NGINX containers
Here, we will use docker-compose.yml to define and run multiple containers, such as NGINX, Node.js, and Certbot, using a single resource and working them together.
First, create a docker-compose.yml file in your node-app directory:
nano docker-compose.yml
Add the following configuration to define the Node.js service:
version: '3' services: nodejs: build: context: . dockerfile: Dockerfile image: nodejs container_name: nodejs restart: unless-stopped networks: - app-network
A brief explanation of the Node.js service component is shown below:
- build: Specify the Dockerfile to build the Node.js application image.
- image: Specify the name of your application image.
- container_name: Specify the name of your application container.
- restart: Define the container start behavior.
- networks: Define the network name to interconnect all containers.
Next, add the following configuration for the webserver service:
webserver: image: nginx:latest container_name: webserver restart: unless-stopped ports: - "80:80" volumes: - web-root:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs networks: - app-network
A brief explanation of the webserver service component is shown below:
- image: Specify the version of your NGINX web server image.
- ports: Specify the NGINX listening port.
- web-root:/var/www/html: Specify the Docker volume name to be mounted in the /var/www/html directory in the NGINX container.
- ./nginx-conf:/etc/nginx/conf.d: Mount the NGINX configuration file from the Docker host to the NGINX container.
- certbot-etc:/etc/letsencrypt: Mount the Let's Encrypt certificates to the NGINX container.
Next, add the following configuration for the Certbot service:
certbot: image: certbot/certbot container_name: certbot volumes: - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt - web-root:/var/www/html depends_on: - webserver command: certonly --webroot --webroot-path=/var/www/html --email user@email-domain.com --agree-tos --no-eff-email --staging -d node.exampledomain.com
A brief explanation of the Certbot service component is shown below:
- image: Specify the Certbot image name.
- volumes: Specify the volumes used to share resources with the NGINX container, including application code, certificates, and the Let's Encrypt working directory.
- depends_on: Used to start the Certbot container only after the webserver container.
- command: Run the certbot command to download Let's Encrypt certificates for your domain.
Note: Replace node.exampledomain.com with your domain name and user@email-domain.com with your real email address.
Next, add the following configuration for volume and network definitions:
volumes: certbot-etc: certbot-var: web-root: driver: local driver_opts: type: none device: /root/node-app o: bind networks: app-network: driver: bridge
Note: Replace the /root/node-app with the path of your Node.js application.
Save and close the file when you're finished.
Deploying the application using Docker Compose
At this point, your Docker Compose file is ready to start all containers and test Let's Encrypt certificate requests.
Now, run the docker-compose command to create and run all containers in the background, which you specified in the docker-compose.yml file.
docker-compose up -d
If everything is OK, you will see the following screen:
The above command will start all containers, download the Let's Encrypt certificates, and mount them to the /etc/letsencrypt/live folder on the webserver container.
You can now check the status of all containers using the following command:
docker-compose ps
You will see the service status in the following output:
Name Command State Ports -------------------------------------------------------------------------------------- certbot certbot certonly --webroot ... Exit 0 nodejs docker-entrypoint.sh node ... Up 8080/tcp webserver /docker-entrypoint.sh ngin ... Up 0.0.0.0:80->80/tcp,:::80->80/tcp
As you can see, the nodejs and webserver containers are in the Up state, while the certbot container is in the Exit 0 state. To identify the issue of the certbot container, check the Certbot log using the following command:
docker-compose logs certbot
You will see the Certbot log on the following screen:
Now, verify that your Let's Encrypt certificates are mounted to the webserver container:
docker-compose exec webserver ls -la /etc/letsencrypt/live
If everything is OK, you will see the following screen:
As you can see, the Let's Encrypt certificate request is successful. The problem is with the Verifying the Let's Encrypt certificate mount flag in the command: section of your docker-compose.yml file.
Let's edit the Docker Compose file and replace the --staging flag with the --force-renewal flag:
nano docker-compose.yml
Change the following line:
command: certonly --webroot --webroot-path=/var/www/html --email user@email-domain.com --agree-tos --no-eff-email --force-renewal -d node.exampledomain.com
Save and close the file, and then run the docker-compose command again to recreate the Certbot container.
docker-compose up --force-recreate --no-deps certbot
If the certificate request is successful, you will see the following screen:
Modifying NGINX configuration and service definition
At this point, you have Let's Encrypt certificates in your hand. Now, you will need to edit your NGINX configuration file and Docker Compose file to define the SSL ports and certificate path.
First, stop the webserver container using the following command:
docker-compose stop webserver
Next, edit the NGINX configuration file:
nano nginx-conf/nginx.conf
Remove the default configuration, and add the following configuration:
server { listen 80; listen [::]:80; server_name node.exampledomain.com www.node.exampledomain.com; location ~ /.well-known/acme-challenge { allow all; root /var/www/html; } location / { rewrite ^ https://$host$request_uri? permanent; } } server { listen 443 ssl; listen [::]:443 ssl; server_name node.exampledomain.com www.node.exampledomain.com; server_tokens off; ssl_certificate /etc/letsencrypt/live/node.exampledomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/node.exampledomain.com/privkey.pem; ssl_buffer_size 8k; ssl_protocols TLSv1.2; ssl_prefer_server_ciphers on; ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5; ssl_ecdh_curve secp384r1; ssl_session_tickets off; ssl_stapling on; ssl_stapling_verify on; resolver 8.8.8.8; location / { try_files $uri @nodejs; } location @nodejs { proxy_pass http://nodejs:8080; add_header X-Frame-Options "SAMEORIGIN" always; add_header X-XSS-Protection "1; mode=block" always; add_header X-Content-Type-Options "nosniff" always; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Content-Security-Policy "default-src * data: 'unsafe-eval' 'unsafe-inline'" always; #add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always; # enable strict transport security only if you understand the implications } root /var/www/html; index index.html index.htm index.nginx-debian.html; }
Save and close the file when you're done.
Next, edit the Docker Compose file:
nano docker-compose.yml
Find the webserver service section and modify it with the following configuration:
webserver: image: nginx:latest container_name: webserver restart: unless-stopped ports: - "80:80" - "443:443" volumes: - web-root:/var/www/html - ./nginx-conf:/etc/nginx/conf.d - certbot-etc:/etc/letsencrypt - certbot-var:/var/lib/letsencrypt depends_on: - nodejs networks: - app-network
Save and close the file, and then recreate the webserver container using the following command.
docker-compose up -d --force-recreate --no-deps webserver
Verifying Node.js application
At this point, your Node.js application is hosted inside the Docker container with NGINX as a reverse proxy. It's time to test the Node.js application by using a web browser.
First, check the NGINX and Node.js containers to identify the listening ports.
docker-compose ps
You will see that the NGINX container is started and listening on Ports 80 and 443.
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 02779b7297e1 nginx:latest "/docker-entrypoint.…" 6 seconds ago Up 5 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp, 0.0.0.0:443->443/tcp, :::443->443/tcp webserver 3a33075c6381 nodejs "docker-entrypoint.s…" 8 minutes ago Up 8 minutes 8080/tcp nodejs
Now, open your web browser, and access the Node.js application securely using the URL https://node.exampledomain.com.
Subscribe to 4sysops newsletter!
Conclusion
Congratulations! You have successfully deployed a Node.js application with NGINX as a reverse proxy in a containerized environment. This way, you can deploy your application in a scalable, secure, and easily maintainable environment. Using Docker Compose makes it easier to manage multiple containers and services.
Would it be possible to do a video of it….??
Remember, docker-compose is now deprecated so you can install the Docker Compose plugin to start using the newer version of docker compose.