Part I. Dockerize SPA based on Nginx +VueJs+Python-Sanic. Intoduction

Let's create an SPA (Single Page Application) based on micro-service architecture and make it all works inside the docker containers.

Introducing

Motivation: In this article, we will build a bunch of Docker containers and go step by step through the process of building the chat SPA (single page application) with the basic authorization. We will use:

  • VueJs  — JavaScript front-end framework for SPA;
  • Sanic — asynchronous python framework for back-end (authorization API and WebSocket server);
  • PostgreSql as a database (in our case store for users);
  • Redis  — for session storage;
  • Nginx — as the proxy server for routing of incoming requests between our services.
  • Adminer —  a tool for working with database (personally I prefer console, but who knows);
  • Docker— to push all those things to work together.

TL;DR

If you have no time to read, just exec next bash commands:
cd ~
git clone git@github.com:v-kolesov/vue-sanic-spa.git
cd vue-sanic-spa
docker-compose up  -d

and run a browser with the http://localhost.

Briefly about my PC (host machine)

cat /etc/os-release | grep PRETTY_NAME  && \
docker -v && docker-compose -v   && \
git --version  &&  \
make -v
return:
PRETTY_NAME=”Ubuntu 18.04.2 LTS”
Docker version 18.09.2, build 6247962
docker-compose version 1.23.2, build 1110ad01
git version 2.17.1
GNU Make 4.1


Stage 0: How does SPA would be work?

Our application should work on http://localhost. There are 4 main routing rules:

  1. “/ “  — here should be load our Chat SPA. For this route, we will create VueJs application in “app” directory. If no “token” key in localStorage, SPA should redirect to /#/login page for authorization. Login form send email/password to some API URL (for instance /api/v1.0/user/auth);
  2. “/api”  — all requests that started with this, should be redirected to api microservice that build with a Sanic;
  3. “/db”  — all requests that started with this, relate to “adminer” tool;
  4. “/ws” — route for WebSocket server. It is a dedicated instance through that chat will send and receive messages.
To realize 1–4 we will use Ngix web-server.

Stage 1: Project Initialization.

#!/bin/bash
git init vue-sanic-spa;
cd vue-sanic-spa;
# Create .gitignore
touch .gitignore;
# and add some instructions to it
echo '.*' >> .gitignore
echo '!/.gitignore' >> .gitignore
echo '!/.env' >> .gitignore
# Create directory ".data". We will store PostgreSql and Redis database files there
mkdir .data;
# Create directory api. Here will be code of  API
mkdir api;
# in directory app we'll place front-end part (VueJs)
mkdir app;
# Создаем файл переменных окружения
mkdir ws;
# also we need "nginx" directory for server.conf
mkdir nginx;
# also we need file .env,
touch .env;
# and  docker-compose.yml
touch docker-compose.yml;

Stage 2: Containerization

In stage 1 we have just created the empty .env. So fill it with default environment variables. When we will build container we pass all those variables into all of them.

ADMINER_DEFAULT_SERVER=pgsql

POSTGRES_DB=dev
POSTGRES_USER=dev
POSTGRES_PASSWORD=test

PYTHONASYNCIODEBUG=1
PYTHONDONTWRITEBYTECODE=1

API_MODE=dev
API_ADMIN_EMAIL=admin@example.com
API_ADMIN_PASSWORD=password

API_WEBSOCKET_TIMEOUT=86400
It is a good idea to have different .env file for development, production and testing mode and of course, it shouldn't be inside the git repository for security reason. I think the defined variables no need to be explained. Next step is creating the docker-compose.yml file:

version: '3.7'
networks:
  web:
    driver: bridge
  internal:
    driver: bridge

services:  
  redis:
    container_name: test_redis
    image: "redis:latest"
    volumes:
      - ${PWD}/.data/redis:/data        
    networks:
      - internal

  db:
    restart: always
    container_name: test_db
    image: postgres:alpine
    
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    networks:
      - internal
    volumes:
      - ${PWD}/.data/postgresql:/var/lib/postgresql/data
      - ${PWD}/db_entrypoint:/docker-entrypoint-initdb.d

  adminer:
    container_name: test_adminer
    image: adminer    
    restart: always
    env_file:
      - .env
    networks:
      - internal  

  nginx:
    container_name: test_nginx
    image: "nginx:stable"      
    ports:
      - "80:80"
    volumes:
      - ./nginx:/etc/nginx/conf.d
    networks:
      - web
      - internal

    env_file:
      - .env
    links:       
      - adminer

In 2–6 lines we defined 2 different networks: “web” and “internal”. The “web” will be used for exchange data between a client’s browser and web server (Nginx). The “internal” network will link “back-end services” between each other. More theory about networks can read here.

redis

In 9–15 lines we have created “redis” container (based on official docker image) and shared “./data/redis” directory between host machine and container. In line #13 we have used variable {PWD} that is containing the value of the same environment variable of the host machine.

db

This service was built on a top of official PostgreSQL docker image. In lines 23–25 we have defined several environment variables for the container with values from .env file. For this purpose, there are 2 ways. First one is through the “environment” section of current service where it is necessary to set the key-value pair. 2nd way is through the env_file variable when we forward all the variables of the .env file directly to the container (see how it has done in file for nginx service).

adminer

In 33–39 lines we have created a container based on the official adminer image. As you can see adminer has the same “network” as “db” has. In adminer’s docker documentation is described that throw environment variable named ADMINER_DEFAULT_SERVER it is possible to set up default driver, so we have placed it to .env file and attach it in line #37.

nginx

In lines 41–55 we are creating Nginx container. Most important instruction is in line #47 (volumes section). On a host machine, we have created nginx/server.conf with:
server {
    listen 80;
    server_name localhost;   
    
    location /db {            
        rewrite /db$     /db    break;  
        rewrite /db/(.*) /db/$1  break;  
        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_set_header Host $http_host;
        proxy_pass http://adminer:8080;
    }    
}

Stage 3: run it all

If you have done all properly just run “docker-compose up -d”. This command should run all containers in a background mode. After that in a browser go to http://localhost/db you should see the auth page of adminer . Use data from .env file to connect to the database cause this data was also used by building the db container.

Read: "Part II. Dockerize SPA based on Nginx +VueJs+Python-Sanic. Python API."