As I wrote in my previous article, it came time to describe building and running process of my whole Weather Station application. My solution is based on Docker and Docker Compose which is running on Raspberry Pi 4. So let’s start from beginning and clone my repository.
Building frontend application
After cloning repository you have to install all necessary dependencies. Next you have to build common libraries and finally you can build main application. You can read about it in this article.
cd app
npm ci
npm run build:core
npm run build:weather-stations
npm build
After that in directory /dist/app you should see compiled application to JavaScript files.
Building backend application
As you know from my previous articles my backend application contains two services:
- MQTT – listen on different events from my Weather Station devices
- WEB – is responsible for serving frontend application and necessary API
So let’s go to building above applications:
cd server
npm ci
npm run build:weather-stations-module
npm run build:weather-stations-subscriber
npm run build:weather-stations-microservice
These above commands will build common wheater-station module and than it will build MQTT and WEB service. All produced files will be write in below directories:
- /server/dist/apps/weather-stations-subscriber – server MQTT
- /server/dist/apps/weather-stations-microservice – server WEB
In both directories you can find files:
- main.js – application file
- package.json – all necessary dependiences
Docker Compose
We build all kind of applications, so now we are ready to compose our environment. To do that I used Docker and Docker Compose, which allows me to create config YAML file containing three containers:
- database
- MQTT service
- WEB service
Depends on what kind of architecture you use, you have to find matching version of each container image. In my case I use configuration for Raspberry Pi 4 with Ubuntu Server on which I have installed Docker and Docker Composer.
Database
version: '2.4'
services:
database:
container_name: ws-database
image: webhippie/mariadb
environment:
- TZ=Europe/Warsaw
- MARIADB_ROOT_PASSWORD=password
- MARIADB_DATABASE=database_name
- MARIADB_USERNAME=username
- MARIADB_PASSWORT=password
restart: always
volumes:
- ./mysql/conf.d:/etc/mysql/conf.d
- ./mysql/mysql:/var/lib/mysql
- ./mysql/backup:/var/lib/backup
expose:
- 3306
ports:
- 3308:3306
mem_limit: 512m
mem_reservation: 256m
Database container is based on webhippie/mariadb and you need to set some configuration:
- container_name – it is very important, because it allows other container to communicate with it directly
- environment (time zone, database name, database user name, database user password)
- volumes – here you need add links between container directories and host directories to store database files on host machine (to not lose them during restart container)
- expose and forward ports – you have to expose default MySQL ort 3306 and redirect it to some port on host machine, in that case it is 3308
- memory limits – because of Raspberry Pi have only 4GB RAM, you have to limit memory for each container to protect your host machine before memory leak), for this container I set that memory usage could be between 256MB and 512MB.
Additional docker image for Node.js applications
As we know from previous part of this article my WEB server needs to be run as Node.js application also it needs to install some dependencies which can change in time. So I decided to create my own image which prepare environments and allows to run some script inside the container.
To do that I use balenalib/raspberrypi4-64-debian-node:latest image and modify it a bit. I create a Dockerfile as below.
FROM balenalib/raspberrypi4-64-debian-node:latest
WORKDIR /usr/src/app
COPY ./start-app.sh /usr/local/bin
RUN npm -v
RUN /bin/bash -c "node -v"
EXPOSE 8080
The first line of that file is reponsible for downloading an image which will be the base of my image. After that we create inside container working directory /usr/src/app (in the future we will link here our application). Next step is to copy start-app.sh script.
#!/usr/bin/env bash
echo "install npm ..."
npm i
node main.js
This script as you can see is very simple. Its main responsibility is to install npm dependencies and run application using main.js file.
So let’s back to our Dockerfile. Next two steps displays current version of npm and node installed inside the container, but the last line is very important. It exposes container port 8080 and we are able to call some service from the container which is listen on that port. As you probably know on that port is running MQTT and WEB service.
Generally that is all, now we are able to create our image and register it under node-web-app.
docker build -t node-web-app ./
From that moment we can use this image in all ours projects.
Server WEB
If we have our node-web-app image created we are able to prepare second container configuration – that’s time will be WEB server.
api:
container_name: ws-api
image: node-web-app:latest
environment:
- TZ=Europe/Warsaw
volumes:
- ./www/api/:/usr/src/app
ports:
- 8080:8080
links:
- "database:database"
command:
- start-app.sh
mem_limit: 256m
mem_reservation: 128m
As it was previously that this time we need to set up some configuration
- environment – this time only time zone is necessary
- volumes – I think it is most important thing, because we link our lokal directory with our build application with working directory inside container where will be run script start-app.sh.
- ports – we map container 8080 port to our host machine port 8080
- links – we set link to our database container as “database” so our ws-api container will be able to access to database container using hostname “database”
- commands – we set command wchich should be run after the container will be up
- memory – as previously in database container we set memory limits, but this time it is twice time less
MQTT Server
MQTT server configuration is almost the same as WEB server.
mqtt:
container_name: ws-mqtt
image: node-web-app:latest
environment:
- TZ=Europe/Warsaw
volumes:
- ./www/mqtt/:/usr/src/app
ports:
- 8081:8080
links:
- "database:database"
command:
- start-app.sh
mem_limit: 256m
mem_reservation: 128m
The difference is only in volumes map and ports forwarding.
So our whole docker-compose.yaml file is presents as below:
version: '2.4'
services:
database:
container_name: ws-database
image: webhippie/mariadb
environment:
- TZ=Europe/Warsaw
- MARIADB_ROOT_PASSWORD=password
- MARIADB_DATABASE=database_name
- MARIADB_USERNAME=username
- MARIADB_PASSWORT=password
restart: always
volumes:
- ./mysql/conf.d:/etc/mysql/conf.d
- ./mysql/mysql:/var/lib/mysql
- ./mysql/backup:/var/lib/backup
expose:
- 3306
ports:
- 3308:3306
mem_limit: 512m
mem_reservation: 256m
api:
container_name: ws-api
image: node-web-app:latest
environment:
- TZ=Europe/Warsaw
volumes:
- ./www/api/:/usr/src/app
ports:
- 8080:8080
links:
- "database:database"
command:
- start-app.sh
mem_limit: 256m
mem_reservation: 128m
mqtt:
container_name: ws-mqtt
image: node-web-app:latest
environment:
- TZ=Europe/Warsaw
volumes:
- ./www/mqtt/:/usr/src/app
ports:
- 8081:8080
links:
- "database:database"
command:
- start-app.sh
mem_limit: 256m
mem_reservation: 128m
The structure of our Docker directory should be:
- docker-composer.yaml – containers configuration
- www/mqtt – directory with MQTT compiled applications
- www/api – directory with WEB compiled application
- www/api/app – directory with frontend compiled application
- mysql/mysql – directory for database files
That is all, now you need to run only bellow command
docker-compose up -d
and our application should be available on ports 8080 and 8081.
Summary
This kind of solutions has few benefits:
- it is easy to set up
- it is safe for any problems with image container
- it is easy to upgrade (both containers and applications)
- it is easy to move to other location (host machine)
If you have any questions, doubts do not hesitate to contact me via email or comments below.