Deploim isomorphic web application on the example of Nuxt.js


In medium and large projects, the site is not limited to one service - for example, only a site, as a rule, there is a database, API, server that routes requests to all these services. Rolling out and updating all this without any standardization is not easy, and it is even more difficult to scale to multiple servers.

Docker will help us solve this problem - it has become the de facto standard in the world of packaging, delivery and publication of applications.

Docker allows us to wrap an application or service with all dependencies and settings in an isolated container, ensuring the consistency of the content on any platform.

As an isomorphic application, we will use the Nuxt.js framework, which consists of Vue.js and Node.js, allowing us to write universal web applications with server-side rendering (SSR).

This choice is due to personal preference, but in the same way you can take any other framework, for example, Next.js.

We collect and publish the first image.


First of all, you need to configure the port and host within the application. There are several ways to do this, we will use the settings in package.json, adding a new section:

"config": {
 "nuxt": {
   "host": "0.0.0.0",
   "port": "3000"
 }
}

For further action, we need docker, docker-compose installed on the system and an editor with an open project.

Create a Dockerfile that we put in the root and describe the instructions for building the image.

We need to build an image based on the Node.js version 10 image, in this case the light version of alpine is used:

FROM node:10-alpine

Then set the environment variable with the directory name:

ENV APP_ROOT /web

Install as a working directory and add the source:

WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}

Install the dependencies and build the application:

RUN npm ci
RUN npm run build

And we write the command to launch the application inside the image:

CMD ["npm", "run", "start"]

Dockerfile
FROM node:10-alpine
ENV APP_ROOT /web
ENV NODE_ENV production
WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}
RUN npm ci
RUN npm run build
CMD ["npm", "run", "start"]


After that, open the current folder in the terminal and collect the image:

docker build -t registry.gitlab.com/vik_kod/nuxtjs_docker_example .

Run the image locally to check that everything works correctly:

docker run -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example

Going to localhost: 3000 we should see the following:



Excellent! We have successfully launched production assembly of the application on the local machine.

Now we need to publish the image in the docker repository in order to use the ready-made image on the target server. You can use either the self-hosted repository or any other repository, for example, the official hub.docker.com .

I will use the repository in gitlab, the tab with the docker repositories there is called registry. Previously, I have already created a repository for the project so now I run the command:

docker push registry.gitlab.com/vik_kod/nuxtjs_docker_example

After the image has successfully booted, you can proceed to the configuration of the VPS server,
my mine has the following:

  • 1 GB of RAM
  • 4 cores
  • 30 GB disk

I also took the opportunity to put docker right away when creating the server, so if it is not installed on your VPS, you can read the instructions on the official website.

After creating the server, go to it and log in to the docker repository, in my case it is gitlab:

docker login registry.gitlab.com

After authorization, we can start applications with the command previously seen:

docker run -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example



The image downloaded and started, let's check:



We see a familiar picture, we started the container with the application, but on a remote server.

The final touch remains, now when the terminal is closed, the image will be stopped, so we will add the -d attribute to start the container in the background.
Stop and restart:

docker run -d -p 3000:3000 registry.gitlab.com/vik_kod/nuxtjs_docker_example

Now we can close the terminal and make sure that our application is functioning successfully.

We have achieved the necessary - we launched the application in docker and now it is suitable for deployment, both as a standalone image and as part of a larger infrastructure.

Add reverse proxy


At the current stage, we can publish simple projects, but what if we need to put the application and API on the same domain, and in addition to this, give the static not through Node.js?

Thus, there is a need for the so-called reverse proxy server, which will receive all requests and be redirected, depending on the request to the related services.

We will use nginx as such a server.

Managing containers if there are more than one individually is not very convenient. Therefore, we will use docker-compose as a way to organize and manage containers.

Let's create a new empty project, in the root of which we will add the file docker-compose.yml and the nginx folder.

In docker-compose.yml we write the following:

version: "3.3"
# Указываем раздел со связанными сервисами
services:
 # Первый сервис, nginx
 nginx:
   image: nginx:latest
   # Пробрасываем порты 80 для http и 443 для https
   ports:
     - "80:80"
     - "443:443"
   # Опциональный параметр с именем контейнера
   container_name: proxy_nginx
   volumes:
     # Используем свой nginx конфиг, он заменит дефолтный в контейнере
     - ./nginx:/etc/nginx/conf.d
     # Монтируем папку с логами на хост машину для более удобного доступа
     - ./logs:/var/log/nginx/
 # Второй сервис Nuxt.js приложение
 nuxt:
   # Используем ранее собранный образ
   image: registry.gitlab.com/vik_kod/nuxtjs_docker_example
   container_name: nuxt_app
   # Также пробрасываем порт на котором висит приложение
   ports:
     - "3000:3000"

Add the config to the nginx folder, which is recommended by the official site Nuxt.js , with minor changes.

nginx.conf
map $sent_http_content_type $expires {
    "text/html" epoch;
    "text/html; charset=utf-8"  epoch;
    default off;
}
server {
    root /var/www;
    listen 80; # Порт который слушает nginx
    server_name localhost; # домен или ip сервера
    gzip on;
    gzip_types  text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;
    location / {
        expires $expires;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        # Адрес нашего приложения, так как контейнеры связаны при помощи
        # docker-compose мы можем обращаться к ним по имени контейнера, в данном случае nuxt_app
        proxy_pass http://nuxt_app:3000;
    }
}


Run the command to run:

docker-compose up



Everything started correctly, now if we go to the address that listens to nginx, localhost, then we will see our application, there will be no visual differences, but now all requests first go to nginx where they are redirected depending on the specified rules.

Now we have no additional services or statics, let's add a static folder in which we place some image.

Mount it in the nginx container by adding a line to docker-compose:

...
container_name: proxy_nginx
volumes:
 #  Монтируем папку со статикой
 - ./static:/var/www/static
...

Updated docker-compose.yml
version: "3.3"
# Указываем раздел со связанными сервисами
services:
  # Первый сервис, nginx
  nginx:
    image: nginx:latest
    # Пробрасываем порты 80 для http и 443 для https
    ports:
      - "80:80"
      - "443:443"
    # Опциональный параметр с именем контейнера
    container_name: proxy_nginx
    volumes:
      # Используем свой nginx конфиг, он заменит дефолтный в контейнере
      - ./nginx:/etc/nginx/conf.d
      # Монтируем папку с логами на хост машину для более удобного доступа
      - ./logs:/var/log/nginx/
      #  Монтируем папку со статикой
      - ./static:/var/www/static
  # Второй сервис Nuxt.js приложение
  nuxt:
    # Используем ранее собранный образ
    image: registry.gitlab.com/vik_kod/nuxtjs_docker_example
    container_name: nuxt_app
    # Так же пробрасываем порт на котором висит приложение
    ports:
      - "3000:3000"


Then add a new location to nginx.conf:

location /static/ {
   try_files $uri /var/www/static;
}

Updated nginx.conf
map $sent_http_content_type $expires {
    "text/html" epoch;
    "text/html; charset=utf-8"  epoch;
    default off;
}
server {
    root /var/www;
    listen 80; # Порт который слушает nginx
    server_name localhost; # домен или ip сервера
    gzip on;
    gzip_types  text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;
    location / {
        expires $expires;
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout 1m;
        proxy_connect_timeout 1m;
        # Адрес нашего приложения, так как контейнеры связаны при помощи
        # docker-compose мы можем обращаться к ним по имени контейнера, в данном случае  nuxt_app
        proxy_pass http://nuxt_app:3000;
    }
    location /static/ {
      try_files $uri /var/www/static;
    }
}


Restart docker-compose:

docker-compose up --build

Go to localhost / static / demo.jpg




Now static is given through Nginx, removing the load from Node.js in the main application.

Making sure that everything works, you can publish our assembly on the server. To do this, I will create a repository in the current directory. Pre-adding the logs and static folder to .gitignore.

After that we go to the server, stop the previously launched docker image and clone the repository.



Before you start the build, you need to move the folder with statics to the server, go to the terminal on the local machine and through the scp command utility move the folder to the server:

scp -r /Users/vik_kod/PhpstormProjects/nuxtjs_docker_proxy_example/static root@5.101.48.172:/root/example_app/

If the amount of static is large, it is better to first compress the folder and send it to the archive, then unpack it on the server. Otherwise, the download may take a long time.

We return to the terminal on the server and go to the cloned folder and run the command:

docker-compose up -d

We close the terminal and go to the site:




Excellent! Using reverse proxy, we separated the static from the application.

Further steps


All that we have done above is a fairly simple option, in large projects you need to consider more things, below is a short list of what you can do next.

  • Data only containers for static admins, SPA applications and databases
  • Additional services for image processing and optimization, example
  • Integration of CI / CD, image assembly when pushing to the selected branch as well as automatic update and restart of services
  • Creating a Kubernetes or Swarm cluster if there are more than 1 servers, for load balancing and easy horizontal scaling

Total


  • We successfully published the application to the server and prepared it for further scaling.
  • We got acquainted with the docker and got an idea of ​​how to wrap your application in a container.
  • We learned what steps can be made further to improve the infrastructure.

Sources


Appendix
Configs

Thank you for your attention and I hope this material has helped you!

Also popular now: