Building an architecture to implement a SaaS model

MaelVB
5 min readJul 8, 2021

--

Preamble: NextSourcia is a web hosting and development company whose flagship solution is AquilaCMS, an open source e-commerce CMS developed on a ME*N stack.

One of the projects we had to implement in the last few months is the provision of a hosting offer on our shop, allowing the installation of our AquilaCMS solution in one click.

A French version of this article is available on the AquilaCMS website and on Medium.

Why use the SaaS model

To carry out this project, we used the SaaS model, which stands for Software as a Service. This means that the software is not installed on the user’s machine but on a remote machine. In our case, this translates into the installation of an AquilaCMS on our servers, at the user’s request, which corresponds perfectly to the hosting offer we wanted to set up.

If you want to adapt this project, you just have to replace AquilaCMS and the different technologies used such as MongoDB with the ones you are interested in.

This project was designed for low traffic and minimal latency, for a project with higher traffic it is recommended to use Traefik instead of Nginx and Kubernetes with Docker (especially for scalability needs). This same project with Traefik and Kubernetes may be the subject of an article.

SaaS model illustration

The implementation with Docker

We opted for an architecture based on Docker images and an installation on our own servers, without using an external service. This has two advantages: to reduce network latency as much as possible by having control over all the servers used and to have control over the entire architecture to optimize it as much as possible according to our needs. In this project we use Docker in a “vanilla” way, without extensions and without orchestration like with Kubernetes. This way of doing things can also be practical in cases where the use of an orchestrator (or container-orchestration system) is not possible.

Dynamically deploy an application and its MongoDB

The first need of this architecture is to be able to deploy our solution and a MongoDB database at each hosting request. For this we have developed Bash scripts that launch Docker containers from a docker-compose.yml file and environment variables.

version: '3.4'
services:
mongo:
container_name: mongo-${ID}
image: mongo
volumes:
- "db-name-volume:/data/db"
networks:
- aquila
ports:
- "${PORT_DB}:27017"

aquila:
depends_on:
- mongo
container_name: aquila-${ID}
build: .
environment:
- NODE_ENV=production
- AQUILA_ENV=aquila-${ID}
ports:
- "${PORT_AQL}:3010"
networks:
- aquila

volumes:
db-name-volume:
name: "mongo-${ID}"

networks:
aquila:
external:
name: aquila-${ID}

The environment variables are placed in a .env file in the same folder as the docker-compose.yml. Here we have the ID variable which corresponds to the name of the shop given by the user, PORT_AQL which is the port of the AquilaCMS container and PORT_DB which is the port of the MongoDB container. We can note the declaration of a volume outside the mongo service because it is the only way to use a variable in the name of a volume. Finally, we create an external network that is specific to each pair of AquilaCMS and MongoDB containers.

Managing the ports of the hosting server

To know the ports on which the containers will be launched, we have set up the management of a CSV file in our scripts : at each hosting request, the file is browsed and the first available port is retrieved, then a line is added in the CSV for the AquilaCMS container and another for the MongoDB container.
For example, we have in our file :

8001,test1.aquila-cms.cloud,aquila
8002,test1.aquila-cms.cloud,mongo
8003,test2.aquila-cms.cloud,aquila
8004,test2.aquila-cms.cloud,mongo
8007,test4.aquila-cms.cloud,aquila
8008,test4.aquila-cms.cloud,mongo

Port 8005 will be retrieved to launch the AquilaCMS container and port 8006 for the MongoDB container.

Redirection management

A major problem of the SaaS model is the redirection of a URL accessible to the user to the address of his site on our servers. For example, we want test1.aquila-cms.cloud to redirect to internalserver.com:8005. First, we set up a Nginx container with a shared volume with the host machine in which we will create a configuration file for each new AquilaCMS.

Secondly, we have taken a domain name whose entire subdomain points to this Nginx container, which means that *.aquila-cms.cloud points to the address of the Nginx container (e.g. internalserver.com:8000).

For example, if the hosting test1.aquila-cms.cloud is requested, we create a configuration file test1.aquila-cms.cloud.conf in the volume of the Nginx container and we send a command to this container to restart the Nginx process (/usr/sbin/nginx -s reload).

Communication between the shop and the scripts

To communicate between the shop and the scripts on the hosting server, we use a REST API, which allows us to call the scripts even if the shop and the hosting are not on the same server, all in a secure way. For this API, we use another of our projects that was released as open source: Aquila Probe, but any API that interfaces between an HTTP request and the execution of a script (via the Node.js child_process module for example) is sufficient.

The final architecture

Here is our architecture of this project, soberly entitled Aquila Saas. The different stages in the creation of a hosting :

  1. A button on our shop shop.aquila-cms.com calls a route from our API passing as argument the name of the client’s site
  2. Call the main script, check the arguments that have been passed
  3. Some processing (like reading the available port) and call to a first script to deploy an AquilaCMS and a MongoDB
  4. The deployment script has completed its execution, it returns to the main script
  5. Call the script that will create the Nginx configuration
  6. The deployment script has completed its execution, it returns to the main script
  7. The main script returns the hand to the API
  8. The API returns everything that has been written to the standard output of the different scripts
  9. The shop can make various other calls to this same API to retrieve information
  10. The API returns this information
Final architecture for our SaaS model implementation
Final architecture for our implementation of a SaaS model

Delete a hosting

To delete a hosting, you just have to go back to each of the points mentioned above: the creation of the AquilaCMS and MongoDB containers, the creation of the Nginx configuration file and the insertion of the port information in the CSV file. In a deletion script, you then have to stop and delete the containers that correspond to the right user (docker stop and docker rm) and delete the network on which the two containers were (if no other project is on the same server, a docker system prune --all may be appropriate to be sure to delete all traces of this hosting). Then just delete the Nginx configuration file and delete the user lines from the CSV file (can be done with the sed command for example).

AquilaCMS is an open source and "all in one" e-commerce solution, self hosted, built using Node.js. The project presented here takes place in the proposal of a hosting offer of this solution to all those wishing to test it. If you want to adapt this project, you just have to replace AquilaCMS and the different technologies used such as MongoDB with the ones you are interested in.

--

--

MaelVB
MaelVB

Written by MaelVB

Tech Lead DevOps at NextSourcia, work mainly on AquilaCMS.

Responses (1)