Nov 1, 2017

Fullstack development environment with Docker

I have been using Docker for building (compile/packaging) and running web applications for some time. Through this blog, I would like to share how I used docker for building and running a complete (Angular/SpringBoot) webapp in a local and production environments.


Docker for development environment

In large enterprise projects with distributed development teams, it becomes very difficult to manage and maintain a consistent development environment across developers. The option of VirtualBox/Vagrant/Chef/Puppet is too complex, bulky and resource intensive to be viable for this use-case.

Docker with its simplicity and light-weight containers is an excellent choice for developer environment. As Docker is lightweight, one can run multiple containers simultaneously for fine-grained components and tasks. Not to mention the benefit of having a production-like development environment.

Docker for building artifacts 

In typical production usage, Docker is plugged into the CI/CD pipeline, where the artifacts are already generated and docker machines are spun to run them. In a development environment, Docker can itself act as a build machine with the right build configuration to generate the artifacts. Those artifacts can then be deployed to other docker containers to run them.

The process of building and running the application in a Docker container is a big time saver for daily local builds, Frontend developers can use a Docker container to build and run backend services with the right version of code without having to install backend build and runtime tools locally on their machines.

Below you will find a sample Docker configuration for a complete stack (frontend/backend build and runtime) using existing published docker images. It took me some time to setup Angular CLI build container.

Project Structure

The sample project is Angular frontend running on Ngnix with SpringBoot(Tomcat) as backend services. Angular is built with Yarn/NPM/Angular CLI and backend with Gradle/Java8

As shown in the figure below, both backend and frontend have two docker files, one to build the component (Dockerfile_Build) and the second one to run it (Dockerfile). Docker-compose is used to tie the application together with the proper order of components and dependencies.

The local docker-compose will include the build steps to build the artifact and run them, whereas the production docker-compose will get QA certified artifacts from CI/CD process and would just run them in the containers.


sampleProject
      backend
            Dockerfile_build
            Dockerfile
            src
            build
                 libs
      frontend
            Dockerfile_build
            Dockerfile
            src
            nginx-config
            dist
        ops
            local
                 docker-compose.yml
            production
                 docker-compose.yml
                   

The Frontend Angular code (frontend/src) is built in frontend/dist folder using AngularCLI (ng build) by build container(../frontend/Dockerfile_build). The dist folder and Ngnix configurations are used by Ngnix container to run the Angular app.

Backend code (backend/src) is built using the Gradle container(../backend/Dockerfile_build) with output jar in backend/build/libs folder. The jar is then used by the Java container(../backend/Dockerfile) to run the SpringBoot app.

Backend Gradle Build Container

Java code is compiled and packaged using Gradle/Java8. I took the image file from Offical Docker image library: https://hub.docker.com/_/gradle/


FROM openjdk:8-jdk

CMD ["gradle"]

ENV GRADLE_HOME /opt/gradle
ENV GRADLE_VERSION 4.2.1

ARG GRADLE_DOWNLOAD_SHA256=b551cc04f2ca51c78dd14edb060621f0e5439bdfafa6fd167032a09ac708fbc0
RUN set -o errexit -o nounset \
 && echo "Downloading Gradle" \
 && wget --no-verbose --output-document=gradle.zip "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
 \
 && echo "Checking download hash" \
 && echo "${GRADLE_DOWNLOAD_SHA256} *gradle.zip" | sha256sum --check - \
 \
 && echo "Installing Gradle" \
 && unzip gradle.zip \
 && rm gradle.zip \
 && mv "gradle-${GRADLE_VERSION}" "${GRADLE_HOME}/" \
 && ln --symbolic "${GRADLE_HOME}/bin/gradle" /usr/bin/gradle \
 \
 && echo "Adding gradle user and group" \
 && groupadd --system --gid 1000 gradle \
 && useradd --system --gid gradle --uid 1000 --shell /bin/bash --create-home gradle \
 && mkdir /home/gradle/.gradle \
 && chown --recursive gradle:gradle /home/gradle \
 \
 && echo "Symlinking root Gradle cache to gradle Gradle cache" \
 && ln -s /home/gradle/.gradle /root/.gradle

# Create Gradle volume
USER gradle
VOLUME "/home/gradle/.gradle"
WORKDIR /home/gradle

RUN set -o errexit -o nounset \
 && echo "Testing Gradle installation" \
 && gradle --version

Build this image from backend folder using Dockerfile_build, run using

docker run --rm -v "$PWD":/home/gradle/project -w /home/gradle/project im-build-backend gradle build

It generates the SpringBoot app as an executable jar in the building/libs folder.



Backend SpringBoot Container

This is a regular JDK container with SpringBoot app jar.

FROM frolvlad/alpine-oraclejdk8:slim

VOLUME /tmp

COPY build/libs/mySample.jar app.jar
RUN sh -c 'touch app.jar'

ENV JAVA_OPTS=""

ENTRYPOINT [ "sh", "-c", "java $JAVA_OPTS -jar /app.jar" ]


docker run -d -p 8080:8080 im-backend

Frontend Yarn/AngularCLI Build Container

This container has Yarn, Node, Angular CLI and NPM to build Angular app.

FROM alexsuch/angular-cli:1.4.8

Build/run this container from frontend project folder which has package.json and src folders.

docker run -it --rm -w /app -v $(pwd):/app im-frontend-build ng build


FrondEnd Ngnix Container

Copy the Ngnix confs and dist folder to the  Ngnix container to run the Angular app

FROM nginx
COPY nginx-config/nginx.conf /etc/nginx
COPY dist /var/www/im


docker run -it -p 80:80 im-frontend


DockerCompose local

Pull it all together in docker compose.


Run docker-compose up -d to build and run the frontend and backend services.

Output:


local romiawasthy$ docker-compose up -d
Creating network "local_default" with the default driver
Creating local_build_BackEnd_1 ... 
Creating local_build_BackEnd_1 ... done
Creating local_backend_1 ... 
Creating local_backend_1 ... done
Creating local_build_FrontEnd_1 ... 
Creating local_build_FrontEnd_1 ... done
Creating local_frontend_1 ... 
Creating local_frontend_1 ... done


DockerCompose production

In the production configuration, build images are excluded.


Run docker-compose up -d to run the frontend and backend services.

Output:

production romiawasthy$ docker-compose up -d
Starting production_backend_1 ... 
Starting production_backend_1 ... done
Creating production_frontend_1 ... 
Creating production_frontend_1 ... done


Conclusion

Docker can be a great option for consistent and shareable development environment across large teams.The pattern allows for using the same docker containers for running the services in both local and production. The pattern can be extended for different build and deployment tools.