In this article, you will learn how to create a Docker image from scratch and deploy and run your application as a Docker container with Dockerfile
As you know, Docker is a tool for packaging, deploying, and running applications in lightweight containers. If you want to learn about the basics of Docker, check out the Docker Explained blog.
If you don’t have a Docker installation, see the Docker installation guide.
Dockerfile explained
The very basic building block of a Docker image is a Dockerfile A Dockerfile is a
simple text file
with instructions and arguments. Docker can create images automatically by reading the instructions given in a Dockerfile.
In a Dockerfile Everything on the left is INSTRUCT, and on the right is an ARGUMENT for those instructions. Remember that the file name is “Dockerfile” without any extension.
The
following table contains the important Dockerfile instructions and their explanation. Automatic
Build Docker Image
with Dockerfile
In this section, you will learn how to create a Docker image using a real-world example. We’ll create a Docker Nginx image from scratch with a custom index page.
Follow the steps below to create a Docker image.
Note: The Dockerfile and configurations used for this article are hosted in a Github repository of Docker image samples. You can clone the repository for reference.
Step 1: Create the necessary files and
folders
Create a folder named nginx-image and create a folder called mkdir files nginx-image && cd
nginx-image
mkdir files Create a .dockerignore tap file.
dockerignore
Step 2: Create a sample
HTML file and configuration file When you create a Docker
image for real-time projects
, it contains code or application configuration files.
For demonstration purposes, we will create a simple HTML file and configuration file as our application code and package it using Docker. This is a simple index.html file. You can create your own if you want.
cd in
the files folder cd files
Create an index .html vi index file
.html
Copy the following contents into the index.html and save it.
<html> <head> <title>Dockerfile</title> </head> <body> <div class=”container”> <h1>My App</h1> <h2>This is my first app</h2> <p>Hi all, This is executed through the Docker</p> </div> </body> </html container>
Create a
default vi default file name
Copy the following contents into the default file.
server { listen 80 default_server; listen [::]:80 default_server; root /usr/share/nginx/html; index index.html index.htm; server_name _; location / { try_files $uri $uri/ =404; } }
Step 3: Choose
a base image
We use the FROM command in the Dockerfile that instructs Docker to create an image based on other images that are available in the Docker hub or any container registry configured with Docker. We call it a base image.
The choice of
a base image depends on our application and operating system platform of choice. In our case, we will choose the base image of ubuntu:18.04.
Note: Always use official base images for your applications to avoid potential vulnerabilities. Towards the end, we have added all public records that have verified container base images. Also, when it comes to production use cases, always use a minimal base image such as alpine (only 5 Mib) or undistributed images. Distroless alpine is only 2 MiB
Step 3:
Create the Dockerfile
Create a Dockerfile in the nginx-image folder
. vi Dockerfile
Here is the simple Dockerfile content for our use case. Add the content to the Dockerfile.
FROM ubuntu:18.04 LABEL maintainer=”[email protected]” RUN apt-get -y update && apt-get -y install nginx COPY files/default /etc/nginx/sites-available/default COPY files/index.html /usr/share/nginx/html/index.html EXPOSE 80 CMD [“/usr/sbin/nginx”, “-g”, “daemon off;”]
Here is the explanation of each step
:
Step 4: Create the first Docker
image
The final folder and file structure would look like the following.
nginx-image ├── Dockerfile └── files ├── default index └──.html
Now, we will build our image using the Docker command. The following command will build the image using Dockerfile from the same directory.
Docker Build -T Nginx:1.0.
- -t is for tagging
- is the name of the
- tag. If you do not add a tag, the default is the tag named latest.
- means that we refer to the Dockerfile location as the Docker build context.
the image. nginx is the name of the image. 1.0
.
If the Dockerfile is in another folder, you must specify it explicitly
. docker build -t nginx /path/to/folder
Now, we can list the images using this command
. Pictures of Docker
We can see that the tag is 1.0 here. If we want to put a specific tag we can put it like this image-name:<tag>. If you do not specify a tag, the default value is the most recent tag.
Docker Build -t nginx:2.0.
A single image can have multiple tags. There are two approaches we usually follow to tag the image
:
- Stable tags: we can continue to extract a specific tag, which continues to receive updates. Our labels are always constant, but the content of the image changes.
- Unique tags – We use a different and unique tag for each image. There are different ways to provide unique tags, for example, date and time stamp, build number, commit ID, etc.
Note: When it comes to production, a recommended method of tagging Docker images is semantic versioning (Semver). Docker
caches build steps. So if we build the image again the process will move a little faster. For example, you will not download the ubuntu 18.04 image again.
Using large images slows down the time to build and deploy containers. If you want to learn more about optimizing Docker images, see the Docker Image Reduction Guide.
Step 5: Test
the Docker image Now, after creating the image,
we will run the Docker image. The command will be
docker run -d -p 9090:80 -name webserver nginx:1.0
Here
, -d flag is to run the container in
- separate mode
- format is local-port:container-port –
- name, webserver in our case
-p flag for the port number, the
name for the container
We can verify the container using the following docker
ps
command Now in the browser, if you go to http://<host-ip>:9090, you can see the index page showing the content in the custom HTML page we added to the Docker image.
Embed Docker image in Docker Hub
To submit our Docker image to Docker Hub, we need to create an account in Docker Hub
.
Post that, run the following command to log in from the terminal. It will ask you for a username and password. Provide the credentials for the Docker hub.
Docker login After logging in, we now need to tag our image with the Docker username as shown below. Docker tag nginx:1.0 <username>/<image-name>:tag
For example, here devopscube is the dockerhub username
. docker tag nginx:1.0 devopscube/nginx:1.0 Run the
docker images command again and verify that the tagged image is there
.
We can now send our images to the Docker hub using the following command.
Docker
push devopscube/nginx :1.0
You can now verify that this image will be available in your Docker Hub account
. Using heredoc With Dockerfile
Dockerfile
also supports heredoc syntax. If we have multiple RUN commands, then we can use the heredoc syntax as shown below.
RUN <<EOF apt-get update apt-get upgrade -y apt-get install -y nginx EOF
Also, suppose you want to run a Python script from a Dockerfile, you can use the following syntax.
RUN python3 <<EOF with open(“/hello”, “w”) as f: print(“Hello”, file=f) print(“World”, file=f) EOF
You can also use heredoc syntax to create a file. Here is an example of Nginx.
DE nginx COPY <<EOF /usr/share/nginx/html/index.html <html> <head> <title>Dockerfile</title> </head> <body> <div class=”container”> <h1>My App</h1> <h2>This is my first app</h2> <p>Hi everyone, This is running through Docker container</p> </div> </body> </html> Dockerfile EOF Best practices Some of the
Dockerfile practices
we should follow:
Use
- a .dockerignore file to exclude unnecessary files and directories to increase build performance.
- Use only trusted base images and continue to update images periodically
- Each statement in the Dockerfile adds an additional layer to the Docker image. Minimize the number of layers by consolidating statements to increase performance and build time.
- Run as non-root to avoid security breaches.
- small: Reduce the image size for faster deployment and avoid installing unnecessary tools on your image. Use minimal images to reduce the attack surface.
- Use specific tags over the last tag in the image to avoid major changes over time.
- Avoid using multiple RUN commands, as it creates multiple cacheable layers that will affect the efficiency of the build process.
- Never share or copy application credentials or any sensitive information to the Dockerfile. If you use it, add it to .dockerignore
- EXPOSE and ENV commands as late as possible in
- linter like hadolint to check for common issues and best practices in your Dockerfile
- per container: each container must run only one process. This makes it easier to manage and monitor containers, and helps keep containers light.
- multi-stage builds: Use multi-stage builds to create smaller, more efficient images.
.
Keep the image
Use the
Dockerfile. Use a linter: Use a
. Use only one process
Use
Potential Docker build issues
If there is a
- syntax error or invalid argument in Dockerfile, the docker build command will fail with an error message. Correct the syntax to resolve this.
- Always try to give the container name using the docker run command, otherwise Docker automatically assigns a name to the container and can lead to various problems.
- Sometimes we get Bind for 0.0.0.0.:8080 failed: port is already assigned error, this is because some other software/service is using these ports. We can check the listening ports using the netstat or ss command. Use a different port to resolve this or stop that service.
- Sometimes, Docker could not download the packages with this error Error downloading package <package-name>. This is because the container may not be able to access the Internet or other dependency issues.
Docker Image Logs
As mentioned in step 1, you should always choose verified official base images for your
application.
The following table lists the publicly available container records where you can find base images and officially verified application images.
Docker
image
vs. containers
A Docker image is a snapshot of the file system and application dependencies. It is an executable software package that includes everything needed such as application code, libraries, tools, dependencies, and other files to run an application. You can compare it to a gold VM image.
A Docker image is organized into read-only layers stacked on top of each other.
A Docker container
is a running instance of a Docker image. We create virtual machines from virtual machine images. Similarly, we create a container from a container image. When you create a container from a Docker image, you create a writable layer on top of the existing image layers.
The key difference between a Docker image and a container is the writable layer at the top of the image. This means that if you have five containers running from one image, all containers share the same read-only layers of the image and only the top writable layer is different for all five containers.
This means that when you delete the container, the writable layer is removed.
Images can exist without containers, while a container needs an image to run. We can create multiple containers from the same image, each with its own unique data and status.
Frequently asked questions about building
Docker images
Conclusion
In this article, we discuss how we can create a Docker image and run our application as a Docker container using a Dockerfile. We discussed Dockerfile
in
detail and reviewed some best practices for writing it
.
As a DevOps engineer, it’s important to have a solid understanding of Docker best practices before implementing them into a project. In addition, to learn Kubernetes, you need to understand the workflow of creating container images.
Podman is another container tool with which you can manage containers. To learn more, check out the podman tutorial.