Understand Docker containers by building a simple webserver

Containers are a form of OS virtualization. Whether you work on containers or not you must have heard about Docker and Kubernetes. Docker is a container runtime. Kubernetes is a container orchestration tool, it is used to build, deploy and manage containers.

According to Docker, “A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings. Container images become containers at runtime and in the case of Docker containers – images become containers when they run on Docker Engine.” That means Containers are running instances of images.

Containers can be confusing to people who are new to this technology. There is lot of theory and examples available, in this blog post we are going to build a very basic web server using docker container to build some fundamental understanding of exactly how containers work.

We will do the following-

  1. Install Docker on Ubuntu Server.
  2. Create a Docker file.
  3. Create a basic HTML webpage.
  4. Build docker image.
  5. Build containers from the image.

For this setup I am using VMware Fusion to run an Ubuntu server, and run docker container on Ubuntu. I am trying to build a webserver with nginx and I should be able to access the the webserver from my computer.

Hopefully after this it will be a bit easier to understand containers. So, let’s begin..

Install Docker on Ubuntu Server

We are using Ubuntu server 22.04.2.

clitoapi@jarvis:~/docker_nginx$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 22.04.2 LTS
Release: 22.04
Codename: jammy
clitoapi@jarvis:~/docker_nginx$

We will follow the steps from official page, https://docs.docker.com/engine/install/ubuntu/.

clitoapi@jarvis:~$ for pkg in [docker.io](http://docker.io/) docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done

clitoapi@jarvis:~$ sudo apt-get update

sudo apt-get install ca-certificates curl gnupg

clitoapi@jarvis:~$ sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg

clitoapi@jarvis:~$ echo \ "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

clitoapi@jarvis:~$ sudo apt-get update

clitoapi@jarvis:~$ sudo apt-get install docker-ce docker-ce-cli [containerd.io](http://containerd.io/) docker-buildx-plugin docker-compose-plugin

Docker is installed successfully.

clitoapi@jarvis:~$ docker -v 

Docker version 24.0.4, build 3713ee1

clitoapi@jarvis:~$ sudo docker run hello-world Unable to find image 'hello-world:latest' locally latest: Pulling from library/hello-world 70f5ac315c5a: Pull complete Digest: sha256:926fac19d22aa2d60f1a276b66a20eb765fbeea2db5dbdaafeb456ad8ce81598 Status: Downloaded newer image for hello-world:latest

Hello from Docker! This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:

1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (arm64v8)
3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.

To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID: https://hub.docker.com/

For more examples and ideas, visit: https://docs.docker.com/get-started/

clitoapi@jarvis:~$

Create a Dockerfile

According to Docker “Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image.”

Let’ create a working directory and within the directory create Dockerfile.

clitoapi@jarvis:~/docker_nginx$ mkdir docker_nginx

clitoapi@jarvis:~/docker_nginx$ cd docker_nginx

clitoapi@jarvis:~/docker_nginx$ vi Dockerfile

We are building a webserver so let’s use nginx as base image. I am going to use arm64 version of nginx https://hub.docker.com/r/arm64v8/nginx/. You can choose base image depending upon your OS. Remember containers share OS kernel so base image should be of same architecture.

Enter the following in Dockerfile and save it.

FROM arm64v8/nginx:1.24.0-alpine COPY webpage/html /usr/share/nginx/html

Create a basic HTML webpage

We will create new directory for our website. Add a new directory called html and create index.html page.

clitoapi@jarvis:~/docker_nginx**$ mkdir webpage

clitoapi@jarvis:~/docker_nginx**$ cd webpage

clitoapi@jarvis:~/docker_nginx$ mkdir html

clitoapi@jarvis:~/docker_nginx$ vi index.html

Here is how my index.html page looks like. This isn’t much but will be enough for this demo.

<html> 
<body> 
<h1> This is an example of custom Docker container. 
</h1> 
</body> 
</html>

Build docker image

Now the exciting part. According to Docker, “An image is a read-only template with instructions for creating a Docker container.” Let’s build docker image. We are calling our image ‘webpage’.

clitoapi@jarvis:~/docker_nginx$ sudo docker build -t webpage .

[+] Building 3.1s (7/7) FINISHED                                             docker:default

=> [internal] load build definition from Dockerfile                                   0.0s

=> => transferring dockerfile: 110B                                                   0.0s

=> [internal] load .dockerignore                                                      0.0s

=> => transferring context: 2B                                                        0.0s

=> [internal] load metadata for [docker.io/arm64v8/nginx:1.24.0-alpine](http://docker.io/arm64v8/nginx:1.24.0-alpine)                 3.0s

=> [internal] load build context                                                      0.0s

=> => transferring context: 202B                                                      0.0s

=> CACHED [1/2] FROM [docker.io/arm64v8/nginx:1.24.0-alpine@sha256:80534844ad33faec10](http://docker.io/arm64v8/nginx:1.24.0-alpine@sha256:80534844ad33faec10)  0.0s

=> [2/2] COPY webpage/html /usr/share/nginx/html                                      0.0s

=> exporting to image                                                                 0.0s

=> => exporting layers                                                                0.0s

=> => writing image sha256:3c3acc0c7e50eb447a1be7bed551d6e838c79595e5fb681befbfddc0e  0.0s

=> => naming to [docker.io/library/webpage](http://docker.io/library/webpage)                                             0.0s

We can see two images present here. Our image ‘webpage’ has ID ‘3c3acc0c7e50’.

clitoapi@jarvis:~/docker_nginx$ sudo docker images

REPOSITORY    TAG       IMAGE ID       CREATED          SIZE

webpage       latest    3c3acc0c7e50   18 seconds ago   40.7MB

hello-world   latest    b038788ddb22   2 months ago     9.14kB

This is just an image. There is no container yet.

Build containers from the image

To build container from image select image id and use docker run command. Here I am mapping port 8080 to port 80.

clitoapi@jarvis:~/docker_nginx**$ sudo docker run -d -p 8080:80 3c3acc0c7e50

d089a96553b80d56d377867770a8b518c28451210c8df44eafce022330551ade

We can see that we have built a container.

clitoapi@jarvis:~/docker_nginx**$ sudo docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                   NAMES

4a820dc4dc78   3c3acc0c7e50   "/docker-entrypoint.…"   18 seconds ago   Up 17 seconds   0.0.0.0:8081->80/tcp, :::8081->80/tcp   intelligent_cartwright

Let’s build one more container.

clitoapi@jarvis:~/docker_nginx$ sudo docker run -d -p 8081:80 3c3acc0c7e50

4a820dc4dc7886e61334adee5715c60cb0602d962c00902a05a8a6610fedae16

Now we have two containers. Notice intelligent_cartwright is using port 8081.

clitoapi@jarvis:~/docker_nginx$ sudo docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                   NAMES

4a820dc4dc78   3c3acc0c7e50   "/docker-entrypoint.…"   18 seconds ago   Up 17 seconds   0.0.0.0:8081->80/tcp, :::8081->80/tcp   intelligent_cartwright

d089a96553b8   3c3acc0c7e50   "/docker-entrypoint.…"   2 minutes ago    Up 2 minutes    0.0.0.0:8080->80/tcp, :::8080->80/tcp   adoring_liskov

So what just happened? We built a webserver that is serving us our webpage using nginx.

Let’s test it. We will access both these pages from my computer.

Both webpages are accessible from my computer confirming that the containers are running.

Additional commands to stop and remove containers and images

Get list of containers.

clitoapi@jarvis:~/docker_nginx$ sudo docker ps

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS                                   NAMES

4a820dc4dc78   3c3acc0c7e50   "/docker-entrypoint.…"   18 seconds ago   Up 17 seconds   0.0.0.0:8081->80/tcp, :::8081->80/tcp   intelligent_cartwright

Stop Docker Container.

clitoapi@jarvis:~/docker_nginx$ sudo docker stop intelligent_cartwright

Stopping container does not remove it.

clitoapi@jarvis:~/docker_nginx/webpage/html$ sudo docker ps -a

CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS                     PORTS                                   NAMES

4a820dc4dc78   3c3acc0c7e50   "/docker-entrypoint.…"   52 minutes ago   Exited (0) 9 seconds ago                                           intelligent_cartwright

We can start Docker Container again.

clitoapi@jarvis:~/docker_nginx$ sudo docker start intelligent_cartwright

Remove Docker Container

clitoapi@jarvis:~/docker_nginx$ sudo docker rm intelligent_cartwright

Delete Docker image

clitoapi@jarvis:~/docker_nginx/webpage/html$ sudo docker images

REPOSITORY    TAG       IMAGE ID       CREATED             SIZE

webpage1      latest    3c3acc0c7e50   About an hour ago   40.7MB

webpage       latest    3c3acc0c7e50   About an hour ago   40.7MB

clitoapi@jarvis:~/docker_nginx$ sudo docker rmi 3c3acc0c7e50

Get image ID and delete it.