How to Dockerize Your Application

How to Dockerize Your Application

Docker is trending in both software development and IT for a long time. In this blog, I am going to explain what is docker? why should you use docker? and how to dockerize your Laravel or any other application.

What is Docker


Docker is an open-source tool designed to create, run, and deploy your application in an easy way. In a way, it is the same as a Virtual Machine, but unlike Virtual Machine, rather than creating a whole virtual operating system, Docker allows applications to use the same kernel

Why Docker?


Let say, I have added a new feature to the project, and it works fine in my local machine, but it does not work in production. For instance, with that new feature, I have installed dependencies, and I forgot to install those dependencies in production. Docker's purpose is to solve this problem.

What is Dockerfile?

Dockerfile is a configuration file that contains a collection of commands and instructions which that will be automatically executed in sequence in the docker environment for building a new docker image. The file is written in YAML markup language.

What is Docker Image?

Docker image is a file that contains source code, libraries, dependencies, tools, and other files needed to run an application. A docker image is described in a text file called a Dockerfile, which has a simple, well-defined syntax.

What is Docker Container?

Docker container is a running instance of a docker image.

You can understand better the container, image, and Dockerfile with the help of the following image.

docker.jpeg

How to dockerize an application for development


Let's assume that you are planning to dockerize your Laravel and Vuejs application. If that is the case then you need to have two docker file. One docker file to run our Vuejs application, and the other docker file to run our Laravel application. Here is my Dockerfile to run the Laravel application.

Dockerfile.dev

#choose the OS as a basae image
FROM php:7.3-fpm
#add metadata
LABEL version="1.0.0"
#install required dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    libzip-dev \
    zip \
    jpegoptim optipng pngquant gifsicle \
    unzip \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl
RUN docker-php-ext-configure gd --with-gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ --with-png-dir=/usr/include/

RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

#specify the directory where the source code must be copied 
WORKDIR /var/www
#Copy from the host machine to image
COPY composer.json composer.lock ./

COPY . .
#copy environment file
COPY ./.env.example ./.env

RUN composer install

RUN php artisan key:generate --ansi

RUN php artisan storage:link

COPY ./script/php_script.sh /tmp

RUN chmod +x /tmp/php_script.sh

ENTRYPOINT ["sh","/tmp/php_script.sh"]
#choose the port to communicate with a container
EXPOSE 8000

Following is the docker file to run Vuejs

Dockerfile.node

FROM node:14.15.0

WORKDIR /var/www

COPY package.json ./

RUN npm install

COPY . .

CMD npm run watch -- --watch-poll

and of course, you need to have another container for your database, and for the database, you do not need to have a Dockerfile. You will use an image of MySQL from docker hub.

To run the above docker files with a single command we will use docker-compose.

What is Docker Compose?

You have two custom Dockerfiles to create an image from it and MySQL image from the docker hub. So you need to create, run, network, and volume all the images one by one, and as a developer, it is a headache to up and down all three images one by one.

Docker-compose solves this problem. Simply, by running only one command.

Docker-compose is a simple yet powerful tool that is used to run multiple containers as a single service. For example, suppose you have an application which requires nodejs to run Vuejs, PHP to run Laravel, and mysql as a database service. In this case by docker-compose, you can create one single file (docker-compose.yml ) which will create all the containers as a single service without starting each separately.

docker-compose.yml

version: "3.8"
services:
  server:
    build: .
    container_name: server
    ports:
      - "${HTTP_PORT}:8000"
    volumes:
      - ./:/var/www/
      - /var/www/vendor
    depends_on:
      - mysql
    links:
      - mysql
  mysql:
    image: mysql
    container_name: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    ports:
      - ${MYSQL_PORT}:3306
    volumes:
      - ./mysql/init.sql:/data/application/init.sql
      - mysql_data:/var/lib/mysql
  client:
    build:
      context: .
      dockerfile: ./Dockerfile.node
    container_name: client
    volumes:
      - ./:/var/www/
      - /var/www/node_modules
    depends_on:
      - "server"
volumes:
  mysql_data:

Simply run docker-compose up -d to up, and docker-compose down to down the services.

Setup Production Environment


In production, you do not need to have Nodejs and composer installed. all you need is to have PHP and Nginx or Apache to serve my application. However, you may need a Nodejs to run and build your Vuejs application, and a composer to install PHP dependencies. to avoid and reduce the image package size. you will have to use Docker multi-stage feature.

What is Docker Multi-Stage?

Normally each docker file contains one FROM statement as a base. Using Multi-Stage, you can have multiple FROM, and each of them begins a new stage of the build. you can selectively copy artifacts from one stage to another, leaving behind everything you dont want in the final image. following is theDockerfile.prod file.

#Client App
FROM node:14.15.0 as vuejs
LABEL authors="Nimat Razmjo
RUN mkdir -p /app/public
COPY package.json webpack.mix.js package-lock.json /app/
COPY resources/ /app/resources/
WORKDIR /app
RUN npm install && npm run prod

#Server Dependencies
FROM composer:2.0.8 as vendor
WORKDIR /app
COPY database/ database/
COPY composer.json composer.json
COPY composer.lock composer.lock
RUN composer install \
    --ignore-platform-reqs \
    --no-interaction \
    --no-plugins \
    --no-scripts \
    --prefer-dist

#Final Image
FROM php:7.4-apache as base
#install php dependencies
RUN apt-get update && apt-get install -y \
    build-essential \
    libpng-dev \
    libonig-dev \
    libjpeg62-turbo-dev \
    libfreetype6-dev \
    locales \
    libzip-dev \
    zip \
    jpegoptim optipng pngquant gifsicle \
    unzip \
    && apt-get clean && rm -rf /var/lib/apt/lists/*

RUN docker-php-ext-install pdo_mysql mbstring zip exif pcntl

# change the document root to /var/www/html/public
RUN sed -i -e "s/html/html\/public/g" \
    /etc/apache2/sites-enabled/000-default.conf

# enable apache mod_rewrite
RUN a2enmod rewrite

WORKDIR /var/www/html

COPY . /var/www/html
COPY --from=vendor /app/vendor/ /var/www/html/vendor/
COPY --from=vuejs /app/public/js/ /var/www/html/public/js/
COPY --from=vuejs /app/public/css/ /var/www/html/public/css/
COPY --from=vuejs /app/mix-manifest.json /var/www/html/mix-manifest.json

RUN pwd && ls -la

RUN php artisan key:generate --ansi && php artisan storage:link && php artisan config:cache && php artisan route:cache


# these directories need to be writable by Apache
RUN chown -R www-data:www-data /var/www/html/storage \
    /var/www/html/bootstrap/cache

# copy env file for our Docker image
# COPY env.docker /var/www/html/.env

# create sqlite db structure
RUN mkdir -p storage/app \
    && touch storage/app/db.sqlite

VOLUME ["/var/www/html/storage", "/var/www/html/bootstrap/cache"]

EXPOSE 80

And following is docker-compose.production.yml file to run all instance as a single service.

docker-compose.production.yml

version: "3.8"
services:
  server:
    build:
      context: .
      dockerfile: Dockerfile.prod
      target: base
    container_name: server
    env_file:
      - ./.env
    depends_on:
      - mysql
    links:
      - mysql
    ports:
      - 80:80
    networks:
      - back-tier
  mysql:
    image: mysql
    container_name: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
    ports:
      - ${MYSQL_PORT}:3306
    volumes:
      - ./mysql/init.sql:/data/application/init.sql
      - mysql_data:/var/lib/mysql
    networks:
      - back-tier

volumes:
  mysql_data:
networks:
  back-tier:

to run the application in production mode simply run docker-compose -f docker-compose.production.yml up -d to up the service, and docker-compose -f docker-compose.production.yml down to down the service

The source code is available on here

Note: I warmly welcome If you would like to contribute to the above project on Github.

Thanks for reading, If you enjoyed this article, share it with your friends and colleagues! Or, if you have any feedback in general, let me know :)