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-
- Install Docker on Ubuntu Server.
- Create a Docker file.
- Create a basic HTML webpage.
- Build docker image.
- 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.