Docker: containers explained simply
Tags: docker, containerization, beginners
Published: 2020-03-08 17:19:55

---
title: Docker: containers explained simply
published: true
description: Explaining containerisation in a simple way, plus Docker basics, and three examples of why Docker is used to deliver software.
tags: docker, containerization, beginners
---

# Docker

Docker has become synonymous with containerisation.  Although there are similar services out there, whenever I've heard someone talking about containers, they're talking about Docker.

This blog post will explain containerisation in a simple way, describe some Docker basics, and give three examples of why Docker is used to deliver software.

## Containerisation

**Containers** are a way to build, run and share an application, along with everything that app relies on so that it can be shipped all together. The app and it's environment are isolated and packaged together - and these packages are _standardised_.

> Containers are _isolated environments_, they don't depend on the setup of the outside world (at least, not much). This means when you run the container locally you can have very high confidence of it working in production.
>
><sub>Chris James, _Docker Crash Course_</sub>

Much like shipping containers, the container itself is standardised, making it simpler to use wherever in the world those standards are followed.  This in turn makes it easier to build tools to support containerised code.

Having your code exist in the controlled environment of a container means you can expect your machine will run software the same as in production.  You can be confident your tests will pass having seen them pass locally in the containerised environment.

## Docker customisation

Docker is a set of tools that make creating, running and sharing applications easier, by using containerisation. 

A **container** is a running instance of an **image**; an image is like the blueprint you will create your containers from.  

A configuration file called a `Dockerfile` details how the **image** should build.  The `Dockerfile` is built in _layers_: you could use an existing image from the open-source registry _Docker Hub_ as a base layer, and customise the configuration on top of this with the uppermost layers taking precedent.  There are plenty of commands to build your custom configuration, including, 

- `FROM` (which specifies the existing image to configure on top of) 
- `RUN` (which indicates a executable to be run) 
- `CMD` (which specifies a default command for running a container)

>:bulb: Imagine a game of four-in-a-row.  If each column in the grid is a configurable setting, and each layer has its own coloured coins, the docker image will build from whichever colour is at the top of each column, ignoring any coloured coins below.

To use your image outside of your local machine, it will need to be saved in Docker Hub for example, or on your own network repo.  
**Docker has a [walkthrough](
https://docs.docker.com/get-started/) explaining how to setup, build, and run your image, and how to share it on Docker Hub.**

### Example: Hello Whale

I made a webpage for this blogpost, and put it inside an image, so it can be shared and run with Docker.  To view my webpage in the container, I'll need to include the HTML and CSS files and any assets (I used one .png file) in my image.  As it's a webpage, it needs a server too, so I used NGINX.

#### Making a Dockerfile

Here's how my simple four line Dockerfile looks:

```dockerfile
FROM nginx
COPY index.html /usr/share/nginx/html
COPY style.css /usr/share/nginx/html
COPY whale.png /usr/share/nginx/html
```

- `FROM nginx` means we use the NGINX image as the base layer.  
- `COPY` takes the file with the specified name, and copies it to a folder to be served, namely `/user/share/nginx/html`.

My file tree looks like this:

```
_ whale
 |_ Dockerfile
 |_ index.html
 |_ style.css
 |_ whale.png
```
Since all my files are in the same folder as the Dockerfile, I don't need to specify the file path in the `COPY` commands; the file name is enough.

#### Creating the image

With docker running, I used the `image build` command in the command line interface (CLI) to build my image:
```
$ docker image build -t whale:1.0 .

> Sending build context to Docker daemon  46.08kB
> Step 1/4 : FROM nginx
>  ---> a1523e859360
> Step 2/4 : COPY index.html /usr/share/nginx/html
>  ---> 272650a1e45b
> Step 3/4 : COPY style.css /usr/share/nginx/html
>  ---> 2d6e75b87f9a
> Step 4/4 : COPY whale.png /usr/share/nginx/html
>  ---> e77091a86817
> Successfully built e77091a86817
> Successfully tagged whale:1.0
```
Then I ran a container to check my app worked:
```
$ docker run --name whale -d -p 8080:80 ruthmoog/whale:1.0

> XXXXX9e868b93e5f916241efac920ae19f884f92a84e0901a57e3d33742d5edf
```
In my web browser I navigated to `localhost:8080/index.html` to check my page was displaying as expected (at `localhost:8080` is the NGINX splash page). Then, I stopped the container, using the first few digits of the container ID:
```
$ `docker container rm --force XXXXX`

> XXXXX
```

:bulb: Use `docker ps` to confirm there are no container processes running, or use `docker ps -a` to see the status of all container processes (including any that `Expired` or stopped).

#### Sharing the image

After creating an account and a repo, I had to rename my image tag so that it included my repo name, and then push my image to Docker Hub:
```
$ docker image tag whale:2.0 ruthmoog/whale:2.0
$ docker image push ruthmoog/whale:2.0

> The push refers to repository [docker.io/ruthmoog/whale]
> 82a9e16279d0: Pushed 
> 53fedf50da89: Pushed 
> 372393671d63: Pushed 
> 318be7aea8fc: Layer already exists 
> fe08d5d042ab: Layer already exists 
> f2cb0ecef392: Layer already exists
> 2.0: digest: sha123:cc7da9e...4e91c2d75 size: 1571
```

If you want to run my image yourself (_it has a spinning whale, 10/10 would recommend_), you'll need Docker installed, https://www.docker.com/products/docker-desktop.  You can find my whale repo at https://hub.docker.com/r/ruthmoog/whale.

#### Running a container

With docker running, use the following command in the CLI to pull the image from the repo to your computer, `docker pull ruthmoog/whale:2.2`.  Run with `docker run -d -p 8080:80 ruthmoog/whale:2.2`. (Use the 'tags' tab to find the latest image.)
- `-d` flag is _daemonised_, so the container will run in the background until you tell it to stop
- `-p` flag is specifying which port to _publish_ to. Before the colon (`8080`) is the Docker host port, and after the colon is the protocol port in the container (`80`) which will be mapped to the host port.
- `ruthmoog/whale` is the built image you want to run from my repo (`ruthmoog`) in the Docker Hub registry
Then in your browser, navigate to `localhost:8080/index.html` and bask in the joy of a spinny whale.

## Delivering software

#### :whale: Why use containers instead of a virtual machine (VM)?
Compared to containers, starting a VM is slow - minutes vs seconds - and requires greater memory and storage. Containers virtualise and share the OS, making them faster and efficient, with the feel of a lightweight VM - although they don't have such strong isolation.

>Imagine that you need a new room for your house. Docker containers would add a new room to your existing house, sharing the house’s electricity, plumbing and heating. Virtual Machines, on the other hand, would build a new house every time a new room is required.
><sub>Alan Johnson, _Building a MarkLogic Docker Container_</sub>

Using VMs is a bit like building a small house inside your existing house.

#### :package: Updating your database.  
Imagine that every time your database software releases a new version, you're going to need to keep the version number up to date all over your projects: in all your tools that connect to the database.  What a bummer! If you're using containers, you just update the version your image points to in the configuration files, and life becomes a little easier.  Containers will also allow you to keep some code on a different database version if you need to.

#### :ship: An image everyone can use.
The standardisation of images is like a contract for deployment.  Whatever electronics you want to run in your home, you expect to be able to plug them into the socket and work. Whatever the device does is contained within the device - and whatever the code does inside the container is up to you.  This means you could rewrite your entire codebase in a new programming language, and without changing the infrastructure, just create another image to be run.

---

### credits & references

- [Alan Johnson](https://www.marklogic.com/blog/author/ajohnson/), _Building a MarkLogic Docker Container_ | [marklogic.com/blog/building-a-marklogic-docker-container/](https://www.marklogic.com/blog/building-a-marklogic-docker-container/)
- [Chris James](https://www.quii.dev), _Docker Crash Course_ | [github.com/quii/docker-crash-course](https://github.com/quii/docker-crash-course)
- [Doug Chamberlain](https://blog.netapp.com/author/dougchamberlain/), _Containers vs. Virtual Machines (VMs): What’s the Difference?_ | [netapp.com/blogs/containers-vs-vms/](https://blog.netapp.com/blogs/containers-vs-vms/)
- [Docker](https://www.docker.com) | [docker.com](https://www.docker.com)
- [Docker Hub - NGINX](https://hub.docker.com/_/nginx) | [hub.docker.com/_/nginx](https://hub.docker.com/_/nginx)
- [webopedia](https://www.webopedia.com), _Containerization_ | [webopedia.com/TERM/C/containerization.html](https://www.webopedia.com/TERM/C/containerization.html)