Developing Docker Multiservices Application Using Git Submodule: A Study Case of Laravel Lumen API and Vue for Covid-19 Worldometers Data

Hanief
Remote Worker Indonesia
10 min readSep 24, 2021

Good day friends!😊

Today we will explore again our very useful friend, Docker, but we will step further! We will use Docker to make multi containers or services for 2 different github projects using GIT submodule.

What is GIT submodule?

Git submodule is one of GIT feature that allow us to keep a GIT repository as a subdirectory of another GIT repository. This lets us clone another repository into our project and keep your commits separate. If you want to know more about GIT submodule, you can read it in GIT Submodule Official Documentation HERE.

Anyway, we will create docker based on our article about Covid-19 application project before:

  1. API using Laravel Lumen https://medium.com/remote-worker-indonesia/create-api-covid-19-data-using-laravel-lumen-and-swagger-as-documentation-24edd56ea0d0
  2. Frontend apps using Vuehttps://medium.com/remote-worker-indonesia/create-covid-19-website-using-vue-axios-to-consume-laravel-lumen-api-b91c82434d31

As always let’s start our journey with planning.

The Plan

  1. For API we will use this project: https://github.com/haniefhan/covid_19_api
  2. For frontend apps we will use this project: https://github.com/haniefhan/covid_19_vue
  3. Create 2 containers for each project using docker compose
  4. Both of the containers will use Alpine Linux as its Operating System. Why use this OS? simple, because it’s lightweight and frequently used for production server.
  5. Use one of GIT feature called git submodule, this feature allow us to keep a Git repository as a subdirectory of another Git repository.

Requirement

  1. Stable Internet Connection, of course, because Docker will find and download all the files and packages we need from an online repository. — It’s quite a torture to work with docker when there’s a problem with your internet connection, believe me.
  2. Docker has been installed on your machine. You can download and see how to do it HERE.

Implementation

First what we must do is creating new GIT project (I use github) and name it covid_docker. Go to project folder in your machine then clone the project using this command:

git clone {your_git_project_url}

If you want to use my github project, you can run this command:

git clone https://github.com/haniefhan/covid_docker.git

After the cloning process is complete, our project folder will look like this:

covid_docker
|- .git
|- README.md // if you create git project with README.md

Our next step is clone our API and frontend projects using git submodule. This is the command:

git submodule add https://github.com/haniefhan/covid_19_api
git submodule add https://github.com/haniefhan/covid_19_vue

It will clone API project into folder covid_19_api and clone frontend project into folder covid_19_vue.

This is our project folder looks like now:

covid_docker
|- .git
|- covid_19_api
|- ...
|- covid_19_vue
|- ...
|- .gitmodules
|- README.md // if you create git project with README.md

We know about folder covid_19_api and covid_19_vue come from. But, what is .gitmodules file? It was file that generated by GIT that contain our project submodules info. This is .gitmodules file content:

Alright, all set. Let’s move to dockerize our project.

Dockerization

To bundle 2 dockerization process into 1 file, we need docker compose. Of course we still need to create each Dockerfile per container. This is what we will do.

We will create Dockerfile for covid_19_api project, then Dockerfile for covid_19_vue project, and the last create docker-compose.yml to bundle all of Docker instruction before we build it.

Ok, first let’s create new file in folder covid_19_api with name Dockerfile.

Covid_19_api Dockerfile

It’s already there? no problem, it’s because I already create it in this article: https://haniefhan.medium.com/create-a-lightweight-but-powerful-docker-for-laravel-lumen-api-58cd0cfc8953

But let’s assume we don’t have Dockerfile file yet. 😉

Put this content into Dockerfile file we created:

Explanation:

  1. FROM alpine:3.13, we use Alpine Linux version 3.13 because it’s really lightweight for our simple server (the image is just around 105.34 Mb include our project) and this version provides us with the PHP version we need (PHP 7.4).
  2. RUN apk — no-cache add php7 php7-fpm php7-pdo php7-mbstring php7-openssl, this command is to install packages we need so that Laravel Lumen can run smoothly.
  3. RUN apk — no-chace add php7-json php7-dom curl php7-curl, because our code using curl, dom-manipulation, and JSON for the response, we need to run the command.
  4. RUN apk — no-cache add php7-tokenizer, we use Swagger to provide API documentation, so we need to add this package.
  5. RUN apk — no-cache add php7-phar php7-xml php7-xmlwriter, we need this command so that composer and its composer update process run smoothly.
  6. RUN php7 -r “copy(‘http://getcomposer.org/installer', ‘composer-setup.php’);” && php7 composer-setup.php — install-dir=/usr/bin — filename=composer && php7 -r “unlink(‘composer-setup.php’);” , composer installation process.
  7. COPY . /src WORKDIR /src, we want to copy the project that we pull before to /src folder in our server and then make it as our workdir.
  8. RUN composer update, run composer update command in container we created.

Covid_19_vue Dockerfile

Next step is create new file in folder covid_19_vue with name Dockerfile. Put this content into Dockerfile file we created:

Explanation:

  1. FROM node:lts-alpine as build-stage, we use Node.JS bundle that using Alpine Linux LTS as OS. You can see the detail HERE. We labeled it as build-stage.
  2. WORKDIR /app, change workdir into /app folder.
  3. COPY package*.json ./, copy all file with name package*.json to workdir (/app).
  4. RUN npm install, execute command npm install to download our project dependencies that written in package.json file.
  5. COPY . ., copy all of our project files to workdir(/app).
  6. RUN npm run build, execute command npm run build to build our project.
  7. FROM nginx:stable-alpine as production-stage, second we use nginx bundle that using Alpine Linux Stable as OS. You can see the detail HERE.
  8. And last, COPY — from=build-stage /app/dist /usr/share/nginx/html, copy all files in /app/dist folder in build-stage to /usr/share/nginx/html folder in production-stage.

Now, our project folder will be like this:

covid_docker
|- .git
|- covid_19_api
|- ...
|- Dockerfile
|- ...
|- covid_19_vue
|- ...
|- Dockerfile
|- ...
|- .gitmodules
|- README.md

Docker-compose.yml

Next step is create docker-compose.yml file in folder covid_docker that will execute instruction in each Dockerfile.

The content of docker-compose.yml file is like this:

Explanation:

  1. version: ‘3.8’, we use docker-compose version 3.8, because it’s the newest one.
  2. services: > api :, it will create a service with name api.
  3. > api > image: covid_19_api , we will name the image with covid_19_api, if this not defined, it will be named with folder name as default.
  4. > api > build: > context: ./covid_19_api/, this code is to point a folder of Dockerfile we will use to build the service.
  5. > api > build: > dockerfile: Dockerfile, this code is to point to the folder of Dockerfile file we will use to build the service. We put its name Dockerfile, if you change the name of Dockerfile with Dockerfile.dev for example, put its name in docker-compose.yml: Dockerfile.dev.
  6. > api > ports: - 8080:8080, this code is to point to the port we will use. The left side is the port for our machine to point the container, the right side is for the port inside of the container. We put it like this because we want to access our container using port 8080 (from our browser) and the service that running in the container is using port 8080.
  7. > api > command: php -S 0.0.0.0:8080 public/index.php, this is to activate our service. Important! we need to write its url with 0.0.0.0 not localhost, because 0.0.0.0 is url for our machine (so that we can open the url in our browser) and localhost is for url for the container.
  8. services: > vue :, it will create a service with name vue.
  9. > vue > image: covid_19_vue , we will name the image with covid_19_vue, if this not defined, it will be named with folder name as default.
  10. > vue > build: > context: ./covid_19_vue/, this code is to point a folder of Dockerfile we will use to build the service.
  11. > vue > build: > dockerfile: Dockerfile, this code is to point to the folder of Dockerfile file we will use to build the service. We put its name Dockerfile, if you change the name of Dockerfile with Dockerfile.dev for example, put its name in docker-compose.yml: Dockerfile.dev.
  12. > vue > ports: - 3000:80, this code is to point to the port we will use. The left side is the port for our machine to point the container, the right side is for the port inside of the container. We put it like this because we want to access our container using port 3000 (from our browser) and the service that running in the container is using port 80.
  13. > vue > nginx -g “daemon off;”, this is to activate our nginx service in vue container.

Now, our project folder will be like this:

covid_docker
|- .git
|- covid_19_api
|- ...
|- Dockerfile
|- ...
|- covid_19_vue
|- ...
|- Dockerfile
|- ...
|- .gitmodules
|- docker-compose.yml
|- README.md

Build the container

All is done, let’s try to build it using command:

docker-compose up --build

The process will started…

=> [covid_19_api internal] load build definition from Dockerfile 0.1s 
=> => transferring dockerfile: 1.19kB 0.0s
=> [covid_19_vue internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 379B 0.0s
=> [covid_19_api internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [covid_19_vue internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [covid_19_api internal] load metadata for docker.io/library/alpine:3.13 3.4s
=> [covid_19_vue internal] load metadata for docker.io/library/nginx:stable-alpine 3.3s
=> [covid_19_vue internal] load metadata for docker.io/library/node:lts-alpine 5.4s
=> [covid_19_api 1/9] FROM docker.io/library/alpine:3.13@sha256:2582893dec6f12fd499d3a709477f2c0c0c1dfcd28024c93f1f0626b9e3540c8 0.0s
=> [covid_19_api internal] load build context 0.1s
=> => transferring context: 333.56kB 0.1s
=> CACHED [covid_19_api 2/9] RUN apk — no-cache add php7 php7-fpm php7-pdo php7-mbstring php7-openssl 0.0s
=> CACHED [covid_19_api 3/9] RUN apk — no-cache add php7-json php7-dom curl php7-curl 0.0s
=> CACHED [covid_19_api 4/9] RUN apk — no-cache add php7-tokenizer 0.0s
=> CACHED [covid_19_api 5/9] RUN apk — no-cache add php7-phar php7-xml php7-xmlwriter 0.0s
=> CACHED [covid_19_api 6/9] RUN php7 -r “copy(‘
http://getcomposer.org/installer’, ‘composer-setup.php’);” && php7 composer-setup.php — install-dir=/usr/bin — filename=composer && php 0.0s
=> CACHED [covid_19_api 7/9] COPY . /src 0.0s
=> CACHED [covid_19_api 8/9] WORKDIR /src 0.0s
=> CACHED [covid_19_api 9/9] RUN composer update 0.0s
=> [covid_19_vue] exporting to image 0.0s
=> => exporting layers 0.0s
=> => writing image sha256:51de3c6dda614626f0527dc868b18fad683a3b03462cc3515a1531faa5832a05 0.0s
=> => naming to docker.io/library/covid_19_api 0.0s
=> => writing image sha256:748e43d343ee29dedc3b3d9c15c4f995f50d3c9f845b5640677cad2fb3ed88ae 0.0s
=> => naming to docker.io/library/covid_19_vue 0.0s
=> [covid_19_vue production-stage 1/2] FROM docker.io/library/nginx:stable-alpine@sha256:2012644549052fa07c43b0d19f320c871a25e105d0b23e33645e4f1bcf8fcd97 0.0s
=> [covid_19_vue internal] load build context 0.1s
=> => transferring context: 1.09MB 0.1s
=> [covid_19_vue build-stage 1/6] FROM docker.io/library/node:lts-alpine@sha256:8c94a0291133e16b92be5c667e0bc35930940dfa7be544fb142e25f8e4510a45 0.0s
=> CACHED [covid_19_vue build-stage 2/6] WORKDIR /app 0.0s
=> CACHED [covid_19_vue build-stage 3/6] COPY package*.json ./ 0.0s
=> CACHED [covid_19_vue build-stage 4/6] RUN npm install 0.0s
=> CACHED [covid_19_vue build-stage 5/6] COPY . . 0.0s
=> CACHED [covid_19_vue build-stage 6/6] RUN npm run build 0.0s
=> CACHED [covid_19_vue production-stage 2/2] COPY — from=build-stage /app/dist /usr/share/nginx/html 0.0s
Use ‘docker scan’ to run Snyk tests against images to find vulnerabilities and learn how to fix them
[+] Running 3/3
— Network covid_docker_default Created 0.8s
— Container covid_docker_api_1 Created 0.3s
— Container covid_docker_vue_1 Created 0.3s
Attaching to api_1, vue_1
api_1 | [Fri Sep 24 01:12:07 2021] PHP 7.4.21 Development Server (
http://0.0.0.0:8080) started

ps: It’s finished so fast in my machine because my machine had a cache (see the CHACED line) for the OS and all the packages our project needs. 😁

Alright, the building process has been complete!

Result

Let’s check it first in our Docker Desktop.

As you can see in images above, our docker compose will create 2 Images with name covid_19_api and covid_19_vue. And in menu Containers / Apps, it will create docker group with name covid_docker with covid_docker_vue_1 container and covid_docker_api_1 container inside.

Next, let’s check frontend apps on our browser

http://localhost:3000/

Next, let’s check API on our browser

http://localhost:8080/latest_covid_data

Last, let’s check is API documentation accessible and run.

http://localhost:8080/api/documentation

Excellent! All works like a charm. 😍

Conclusion

We now know how to use GIT submodule and create multiservice container in Docker using our existed projects. It’s really simple, we just need to create Dockerfile for each project, then create docker-compose file to wrap all of Dockerfile files, and last build it using command docker-compose up--build .

You can see and clone code in this article in my github:

Thank you for your time to read. Let’s join us again next time as we explore another interesting case! 😉

--

--