[ELI5] Docker: Basics

Nyein Chan Aung
6 min readDec 14, 2020

--

Docker Explain Like I’m 5
Docker Explain Like I’m 5

If you’re like me, then you’re having a hard time wrapping your head around Docker. Like “What dark magic allows you to instantly run an application with only one command?” Well, we’ll get right into it!

Docker is a containerization platform that packages all the code and its dependencies in one image, essentially all your code in one file! You can think of it as a pre-built computer and all you have to do is turn it on and run it!

So what’s all this jazz about containers, when we have tools like make that automatically recompile large programs for us?

The answer is that Docker is so much more flexible and simpler than anything else. Unlike a make command determining the compatibility of your OS, Docker containers have their OWN OS and file system separate from the computer, so it will always run no matter what! I like to remember it like this; Docker’s command line is like a virtual machine, less intensive and perfectly lightweight.

So the best way to understand something is by doing! So let’s open up our code editor and get started!

Installing Docker

Depending on your machine. you will need to install the appropriate Docker Engine. Otherwise, I am working on Mac with Catalina.

If you have brew, go ahead and install it with:

brew cask install docker

This will install Docker Hub and its command line tools, we can verify this with:

docker --version

Using Docker

From here let’s create a simple flask app in a folder! We can imagine the contents of the folder hosting the flask app as its own file system. Now we can go ahead and make a Dockerfile, a Dockerfile is basically the instructions needed to create your Image.

touch Dockerfile

Each line in a Dockerfile is a layer and basically, they're the instructions to construct your image.

Let’s start with FROM, if you are familiar with programming, you can think of this as importing a library to use or build off of. In this case, we are not working with libraries, but Base Images. Base Images are Images that are pre-configured operating systems such as python, ubuntu, alpine, and many more on docker hub. These Base Images let you do a lot less work to set up.

So because I am using a python based application, it’s a good idea for me to use python:3. Wait why did I follow a Base Image Python name with a “:3”? This is a tag — a specific build of the Base Image Python. We usually denote our base images with a specific build or latest while having a colon separating them. It’s good practice to use a specific build so the image will always work, otherwise, a new latest build may break your application.

The rest is now smooth sailing. Now we need to figure out what our app needs and ‘explain’ those instructions in the Dockerfile.

Does it need?:

  • Libraries / Extra Modules
  • Environment variables
  • Ports
  • Commands it should run

There are more, but these are the most common features an application needs and don’t worry, there are plenty of layer options to tailor to your application needs.

So after we specify a base image, we need to create a directory [or folder] to work in and copy the contents over. We can do this with WORKDIR and COPY.

# Base Image
FROM python:3
# create the work directory app
WORKDIR /app
# Copy the current content of where the
# Dockerfile is to WORKDIR /app
COPY . /app

Now that we have all the contents in the workdir, we still need to install our packages! So hopefully you have a requirements.txt , if not do:

pip3 freeze > requirements.txt

If you are working with a language or framework that has its libraries localized like Node.js, it is preferred to either add the folder with the libraries in .dockerignoreor copy only the package*.json over first and install the libraries. Then, copy the rest of the application over, otherwise, it will take a while to build.

The code should look like this:

FROM node:alpineWORKDIR /serverCOPY ./package*.json /serverRUN npm install

Now we need to tell Docker to run a command to install all the requirements, so we use RUN:

RUN pip3 install -r requirements.txt

Now we need environmental variables for the flask app, for this specific case we need FLASK_APP which is app.py, and the FLASK_RUN_HOST

ENV FLASK_APP=app.pyENV FLASK_RUN_HOST=0.0.0.0

So far simple right?

Next is for documentation purposes: What port does the application run on? Flask on default runs on 5000 so:

EXPOSE 5000

You can specify a protocol with [port]/[protocol] as well, if needed. Now, all we need to do is run the command to start the server, but wait! I know that you are thinking, “but we don’t use RUN here, instead we use CMD!

CMD [“flask”, “run”]

What’s the difference? Well the RUN instruction is a layer built on top of the base image, and CMD is used as a default command that can be changed in the command line. So instead of the RUN instruction that can’t be changed, we prefer CMD in case we want to do python3 app.pyor any other different command.

Notice the syntax as well, we are passing the CMD instruction JSON data, instead of CMD flask run. The JSON one is called exec form instead of the other shell form. The exec form is usually preferred over the shell form for CMD.

Always refer to Docker documentation to check which is preferred!

Now we are done! Your Dockerfile should look like this:

FROM python:latestLABEL GithubArthur NinjaAungWORKDIR /appADD . /appENV FLASK_APP=app.pyRUN pip3 install -r requirements.txtEXPOSE 5000CMD [“flask”, “run”]

The label is not important. It’s for documentation purposes

Now, let’s run it! Go ahead and open Terminal and type:

docker build --tag docker-flask .
  • --tagis naming the image that docker is building
  • . is the location of Dockerfile which is in the current directory

Next, we need to run it.

docker run -p 80:5000 -d --rm --name flask_app docker-flask
  • -p or --publish , however I prefer to remember it as port, because we are connecting our [local Port} to inside our docker container [container port] e.g [local Port]:[Container Port]
  • -dor —-detach, run the container in the background, if you want to see what the application is happening remove the -d
  • --rm, deletes the container after you stop the process
  • --name, is the name of the container you are running otherwise docker will give it a randomly generated name e.g “busy-box’
  • docekr-flask, is the name of the image we are using in the container

And voila, connect to locahost and you will see your app! Now after we are done appreciating your hard work, let’s close it down.

docker stop flask_app

and if you did not include —-rm, we can follow the command with:

docker rm flask_app

Docker-Compose

Wait, you’re not satisfied with building, running, and specifying the port every time? Well be not afraid, there is docker-compose.yml, it has a lot of power and is used to handle multiple containers, but we will go over that next time. All you need to know about this application is this:

version: “3.3”
services:
web:
build: .
ports:
- “5000:5000”
  • version is specifying what version the docker-compose file is in
  • services is a collection of named services you have / collection
  • web is a type of service
  • build is the path to the Dockerfile
  • ports is the same as the command — publish [local]:[Container]

Then to run it:

docker-compose up

and that’s the [ELI5] basics of Docker!

--

--