Deploying a Nodejs app with Docker
To achieve the above, first we need to install docker. Please follow the link below to install docker.
https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
Clone the app
[edit]1) Clone the sample app from the below link.
git clone https://github.com/PheonixDevs/helloapp.git
2) After cloning you will see a directory with name “helloapp”
3) Go inside the directory with the below command.
cd helloapp
4) You will see a Docker file, open it with the below command. As you have cloned you will get the Docker file as well. You can create your own Docker file as well.
Dockerfile contains instructions for building a Node application using the Docker node:alpine image and the contents of your current project directory.
5) Docker file will look like below. These instructions build a Node image by copying the project code from the current directory to the container and installing dependencies with “npm install”. They also take advantage of Docker’s caching and image layering by separating the copy of package.json and package-lock.json, containing the project’s listed dependencies, from the copy of the rest of the application code.
#base image
FROM node:alpine
#install dependencies
WORKDIR /usr/helloapp
COPY ./package.json ./
RUN npm install
COPY ./ ./
EXPOSE 8080
#start-up command
CMD ["npm", "start"]
}
6) Create a directory in the current project directory for the configuration file
mkdir nginx-conf
nano nginx-conf/nginx.conf
Copy and paste the below content.
server {
listen 80;
listen [::]:80;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name dockertestings.pheonixsolutions.com www.dockertestings.pheonixsolutions.com;
location / {
proxy_pass http://dockertestings.pheonixsolutions.com:8080;
}
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
}
7) Next we will create a docker-compose file. The docker-compose.yml file will define our services, including the Node application and web server. It will specify details like named volumes, which will be critical to sharing SSL credentials between containers, as well as network and port information. It will also allow us to specify specific commands to run when our containers are created. This file is the central resource that will define how our services will work together.
nano docker-compose.yml
Copy and paste the below content.
version: '3'
services:
nodejs:
build:
context: .
dockerfile: Dockerfile
image: helloapp
container_name: helloapp
ports:
- "8080:8080"
restart: always
networks:
- app-network
webserver:
image: nginx:mainline-alpine
container_name: webserver
restart: always
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
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 abc@gmail.com --agree-tos --no-eff-email --staging -d dockertestings.pheonixsolutions.com
volumes:
certbot-etc:
certbot-var:
web-root:
driver: local
driver_opts:e
type: none
device: /root/helloapp
o: bind
networks:
app-network:
driver: bridge
We can start our containers with docker-compose up, which will create and run our containers and services in the order we have specified. If our domain requests are successful, we will see the correct exit status in our output and the right certificates mounted in the /etc/letsencrypt/live folder on the webserver container.
First build the image with the below command.
docker build -t helloapp .
Now run the below command to create the services and run the containers.
docker-compose up -d
8) Using docker-compose ps, check the status of your services.
9) If you want to check the service logs, execute the below command.
docker-compose logs service_name
10) You can now check that your credentials have been mounted to the webserver container with docker-compose exec
docker-compose exec webserver ls -la /etc/letsencrypt/live
11) Now that you know your request will be successful, you can edit the certbot service definition to remove the --staging flag. Open docker-compose.yml
nano docker.compose.yml
12) Find the section of the file with the certbot service definition, and replace the --staging flag in the command option with the --force-renewal flag, which will tell Certbot that you want to request a new certificate with the same domains as an existing certificate. The certbot service definition should now look like below.
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 abc@gmail.com --agree-tos --no-eff-email --force-renewal -d dockertestings.pheonixsolutions.com
13) You can now run docker-compose up to recreate the certbot container and its relevant volumes. We will also include the --no-deps option to tell Compose that it can skip starting the webserver service, since it is already running.
docker-compose up --force-recreate --no-deps certbot
You will see output indicating that your certificate request was successful.
14) Now we will modify the webserver Configuration and Service Definition. First stop the webserver with the below command. Enabling SSL in our Nginx configuration will involve adding an HTTP redirect to HTTPS and specifying our SSL certificate and key locations. It will also involve specifying our Diffie-Hellman group, which we will use for perfect forward secrecy.
docker-compose stop webserver
15) Next, create a directory in your current project directory for your Diffie-Hellman key
mkdir dhparam
16) Generate your key with the openssl command
sudo openssl dhparam -out /home/helloapp/dhparam/dhparam-2048.pem 2048
It will take a few moments to generate the key.
17) To add the relevant Diffie-Hellman and SSL information to your Nginx configuration, first remove the Nginx configuration file you created earlier.
rm nginx-conf/nginx.conf
18) Again create config file.
nano nginx-conf/nginx.conf
19) Add the following code to the file to redirect HTTP to HTTPS and to add SSL credentials, protocols, and security headers.
server {
listen 80;
listen [::]:80;
server_name dockertestings.pheonixsolutions.com;
location ~ /.well-known/acme-challenge {
allow all;
root /var/www/html;
}
location / {
rewrite ^ https://$host$request_uri? permanent;
}
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name dockertestings.pheonixsolutions.com;
server_tokens off;
ssl_certificate /etc/letsencrypt/live/dockertestings.pheonixsolutions.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/dockertestings.pheonixsolutions.com/privkey.pem;
ssl_buffer_size 8k;
ssl_dhparam /etc/ssl/certs/dhparam-2048.pem;
ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
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;
20) Before recreating the webserver service, you will need to add a few things to the service definition in your docker-compose.yml file, including relevant port information for HTTPS and a Diffie-Hellman volume definition.
nano docker-compose.yml
21) In the webserver service definition, add the following port mapping and the dhparam named volume. It will be like below.
webserver:
image: nginx:mainline-alpine
container_name: webserver
restart: always
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
- dhparam:/etc/ssl/certs
depends_on:
- nodejs
networks:
- app-network
}
Next, add the dhparam volume to your volumes definitions
volumes:
certbot-etc:
certbot-var:
web-root:
dhparam:
driver: local
driver_opts:
type: none
device: /root/helloapp/dhparam/
o: bind
Now the complete docker-compose.yml file look like below.
version: '3'
services:
nodejs:
build:
context: .
dockerfile: Dockerfile
image: helloapp
container_name: helloapp
ports:
- "8080:8080"
restart: always
networks:
- app-network
webserver:
image: nginx:mainline-alpine
container_name: webserver
restart: always
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
- dhparam:/etc/ssl/certs
depends_on:
- nodejs
networks:
- app-network
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 abc@gmail.com --agree-tos --no-eff-email --force-renewal -d dockertestings.pheonixsolutions.com
volumes:
certbot-etc:
certbot-var:
web-root:
dhparam:
driver: local
driver_opts:
type: none
device: /root/helloapp/dhparam/
o: bind
networks:
app-network:
driver: bridge
22) Recreate the webserver service with the below command
docker-compose up -d --force-recreate --no-deps webserver
23) Check your services with docker-compose ps. It should look like below.
24) Finally, you can visit your domain to ensure that everything is working as expected. Navigate your browser to https://dockertesting.pheonixsolutions.com. Rplace with your own domain and check.
It should look like below.