This is my take on learning Docker if you are a complete beginner.
Traditional Approach, and the Problem
- So you have your separate EC2 Instances, 1 for UAT env, and 1 for Prod.
- I like to think of the EC2 Instance as like a laptop you bought remotely from Amazon, and you can't physically touch it.
- You can only remotely control it using
ssh
.
- Everything works fine for your Hotel Reservation system.
- There was a new functionality introduced at your Hotel Reservation system, say,
- Java 8 in UAT was upgraded to Java 10, for some reason.
- in Production, it's still Java 8.
- So you used Java 10 new features, say, a new Streams API, in your local and UAT.
- So, everything works in UAT, QA is also happy with it.
- But guess what. When it was deployed in Prod, it broke.
- And you probably can't pinpoint it directly.
That's why, the idea of containerization was introduced!
Containerization Approach
Docker
In 2013, Docker introduced what would become the industry standard for containers.
Containers
Containers are a standardized unit of software that allows developers to isolate their app from its environment, solving the “it works on my machine” headache.
The idea is that, having a container is like having a "super mini-laptop" that has NO OS (it shares with its Host's OS), just a bare minimum drivers / kernels that is essential for running, say, a MySQL server, or a Java server with Maven.
The "super mini-laptop" term is an oversimplification
So, YES, it is possible to put your Java server, Maven, and DB in one whole container.
But, it's very dangerous. If your container goes down, you will lose all of your data.
It is still important to isolate your DB to a separate container,
Or much better, resort to something like third-party platforms that can guarantee the persistence of your data.
e.g. for MongoDB, -> MongoDB Atlas, mLab, etc.
Images
With that said, you need an image which is stored inside containers where it will run.
And you need a Dockerfile in order to build an image. It sits alongside your source code.
This Dockerfile will be the same regardless when you're in develop
branch or in master
branch of your project's Git repo.
Dockerfile
Consider this Dockerfile example:
FROM maven:3.6.3-ibmjava-8-alpine
ENV TZ=Asia/Manila
# Apache POI uses JRE 8 Font on writing Excel cells, so this package must be installed in the Docker container
# to be able to use it.
# source: https://stackoverflow.com/a/56664613/7209628
RUN apk --update add fontconfig ttf-dejavu
# Copy whole source code to the docker image
# Note of .dockerignore, this ensures that folders such as `target` is not copied
COPY . /usr/src/xrecon/
RUN cd /usr/src/xrecon/ \
&& mvn clean package \
&& mkdir -p /usr/src/xrecon-app \
&& cp /usr/src/xrecon/target/*.jar /usr/src/xrecon-app/xrecon-app.jar
WORKDIR /usr/src/xrecon-app
EXPOSE 8080
ENTRYPOINT ["java", "-Djava.security.egd=file:/dev/./urandom", "-Xms500M", "-Xmx900M", "-jar", "xrecon-app.jar"]
Let's digest these commands one step at a time..
FROM
command means I want to use maven:3.6.3-ibmjava-8-alpine
as my base image.
**
Meaning, I won't have any difficulty creating a blank image from scratch and installing Maven with JDK 8 in there.
The maven:3.6.3-ibmjava-8-alpine
image took care of it.
This base image will be STANDARD and FIXED regardless on what environment I'm at.
The -alpine
at the image name indicates that I want to use this base image that also has Alpine Linux inside it.
This way, I have a very very small distro like Ubuntu, helpful for installing packages like curl
, vim
, ssh
, or any Linux packages that you need.
RUN
means I want to execute shell commands inside the image; equivalent to using your Terminal.
In this Dockerfile case above ☝️☝️, I want to execute a shell command that installs a package that will be helpful for the Apache POI Java library. Without this, my Apache POI Excel generation will crash.
I'm using apk add
as this is how you would install a package in Alpine Linux,
as you would do apt-get
in Ubuntu, or brew install
in Mac's Homebrew.
COPY
is like magic, because you can literally copy your source code files inside your Git repo, to the Docker image.
**
So, I went and copied my whole source code inside the Docker image like so.
NEXT: Running Maven inside the image
As I was using the official Maven image available from Dockerhub, I didn't have any difficulty running mvn clean package
inside the image.
And we want to preserve "layers" as using FROM, COPY and RUN etc commands consumes "layers".
In this case, we are able to mvn clean package
, mkdir
, and cp
in one single Docker RUN
command.
WORKDIR
is where the default directory will go if you enter this Docker container using docker exec
.
In this case, if you enter inside this Docker container, it will land you to the /usr/src/xrecon-app
directory.
EXPOSE
is what port you would expose your image to.
Because a container can have more than one image, it is essential how the client would access your app through a port inside the Docker image.
For example, suppose you have a Java server image and FTP server image together inside a container.
You would expose
the Java server usually in port 8080, and the FTP server usually in port 21.
**
Think of it like the container is a hotel.
You can reach the Java server inside the hotel room number 8080.
And you can reach the FTP server inside the hotel room number 21.
But it is greatly advised that you only have strictly one image inside a container especially if you want a Microservices nature of architecture.
We're done with our short Docker basics!
Hope this helped!
Top comments (2)
I find it to be a terrible idea to have the neccesarity to copy your src folder. You can achieve this, with also bringing the possibility of not having to be inside the container to edit project files, through creating volumes inside a service.
Like so
Thanks so much for this info.. I am completely new to Docker, even not touching the
volumes
part of Docker.So this is docker-compose, right? Thanks!
What if you're currently restricted to only using Dockerfile? Can you achieve this as the same?