Building Docker Images Made Easy: A Complete Dockerfile Tutorial

Docker has revolutionized the way we build, package, and deploy applications. By using Docker, developers can ensure their applications run consistently across different environments. One key component of Docker is the Docker image, which is created from a set of instructions written in a Dockerfile. In this tutorial, we will cover everything you need to know about Dockerfiles, including how to create one, best practices, and some advanced features. So, let's dive in and start building Docker images like a pro!

What is a Dockerfile?

A Dockerfile is a text document that contains all the instructions required to create a Docker image. It is essentially a blueprint for your container, specifying the base image, application code, libraries, and any other dependencies that your application needs to run. When you build a Docker image using a Dockerfile, the result is a portable, self-sufficient unit that can be shared and deployed across different platforms.

Creating a Basic Dockerfile

To get started, let's create a simple Dockerfile for a Node.js application. First, create a new directory for your project and navigate to it in your terminal:

$ mkdir my-node-app $ cd my-node-app

Next, create a new file named Dockerfile (with no file extension) in the project directory:

$ touch Dockerfile

Open the Dockerfile in your favorite text editor and add the following content:

# Use the official Node.js base image FROM node:14 # Set the working directory WORKDIR /usr/src/app # Copy package.json and package-lock.json to the working directory COPY package*.json ./ # Install the application dependencies RUN npm install # Copy the application source code to the working directory COPY . . # Expose the application port EXPOSE 8080 # Start the application CMD ["npm", "start"]

Let's go through each line of the Dockerfile to understand what's happening:

  1. FROM node:14: This line tells Docker to use the official Node.js 14 image as the base image for our container. This image includes a minimal Node.js installation, which is perfect for our needs.
  2. WORKDIR /usr/src/app: This line sets the working directory for any subsequent instructions in the Dockerfile. In this case, we're setting it to /usr/src/app.
  3. COPY package*.json ./: This line copies both package.json and package-lock.json (if it exists) from your local machine to the container's working directory.
  4. RUN npm install: This line installs the application dependencies defined in package.json using npm.
  5. COPY . .: This line copies the rest of your application code (excluding the contents specified in .dockerignore) to the container's working directory.
  6. EXPOSE 8080: This line tells Docker to expose port 8080, which is the port our application will listen on.
  7. CMD ["npm", "start"]: This line specifies the command that Docker will run when the container starts. In this case, it will run npm start to start our Node.js application.

Building the Docker Image

Now that we have a Dockerfile, we can build the Docker image by running the following command in the same directory as the Dockerfile:

$ docker build -t my-node-app .

This command tells Docker to build the image using the Dockerfile in the current directory (.) and tag it with the name my-node-app.

Running the Docker Container

Once the image has been built, you can run a container from it using the following command:

$ docker run -p 8080:8080 my-node-app

This command tells Docker to run a container from the my-node-app image, and map port 8080 on your local machine to port 8080 on the container. Now, you can access your application at http://localhost:8080.

Best Practices for Writing Dockerfiles

To create efficient and secure Docker images, follow these best practices when writing Dockerfiles:

  1. Use a specific base image: Instead of using a generic base image like node, use a specific version like node:14. This ensures your application runs consistently, even if the base image is updated.
  2. Keep your images small: Smaller images are faster to build, transfer, and start. To achieve this, use multi-stage builds and minimize the number of layers in your image.
  3. Minimize the use of RUN, COPY, and ADD instructions: Each RUN, COPY, and ADD instruction creates a new layer in the image. To minimize the number of layers, chain multiple RUN instructions with &&, and use .dockerignore to exclude unnecessary files and directories.
  4. Use a .dockerignore file: A .dockerignore file is similar to a .gitignore file. It allows you to exclude files and directories from the build context, which reduces the size of your image and speeds up the build process.
  5. Don't run processes as root: Running processes as root can pose security risks. Instead, create a non-root user and switch to that user before running your application.
  6. Use the COPY instruction instead of ADD: The ADD instruction has additional functionality (e.g., extracting archives), which is often unnecessary. Use the simpler COPY instruction whenever possible.

Advanced Dockerfile Features

In addition to the basic Dockerfile features we covered earlier, there are several advanced features that can help you create more efficient and flexible Docker images:

  1. Multi-stage builds: Multi-stage builds allow you to use multiple FROM instructions in a single Dockerfile. This is useful for creating smaller images, as you can copy artifacts from one stage to another and leave behind unnecessary files and dependencies.
  2. Build arguments: You can use build arguments to pass variables to your Dockerfile at build time. This is useful for customizing the build process or for providing secret values, like API keys, without storing them in the Dockerfile.
  3. ONBUILD triggers: ONBUILD triggers are special instructions that are executed when an image is used as a base image for another build. This allows you to create base images that automatically perform common tasks, like installing dependencies or running tests.


Q: What is the difference between CMD and ENTRYPOINT in a Dockerfile?

A: Both CMD and ENTRYPOINT instructions define the command that will be executed when a container is started. The main difference is that ENTRYPOINT provides a default command that cannot be overridden, while CMD provides a default command that can be overridden by providing arguments when starting the container.

Q: Can I use environment variables in a Dockerfile?

A: Yes, you can use environment variables in your Dockerfile using the ENV instruction. This allows you to set default values for your application that can be overridden at runtime.

Q: How can I include secret values, like API keys, in my Docker image without storing them in the Dockerfile?

A: You can use build arguments to pass secret values to your Dockerfile at build time. These values will not be stored in the final image. Atruntime, you can use environment variables to pass the secret values to your application. You can also use Docker secrets or third-party secret management solutions like HashiCorp Vault to securely store and manage your secrets.

Q: Can I use a Dockerfile to build images for different platforms (e.g., Linux, Windows, ARM)?

A: Yes, you can use the same Dockerfile to build images for different platforms by using build arguments and conditional instructions. You can also use the --platform flag when building the image to specify the target platform.

Q: How do I optimize the build process for my Docker images?

A: To optimize the build process, minimize the number of layers in your image by chaining multiple RUN instructions with &&, use a .dockerignore file to exclude unnecessary files and directories, and take advantage of build caching by ordering your instructions from least to most likely to change.


In this tutorial, we covered the basics of Dockerfiles, including how to create one, best practices, and some advanced features. With this knowledge, you should be able to build Docker images more easily and efficiently. Remember to follow best practices and keep your images small and secure. Happy Dockering!

Sharing is caring

Did you like what Mehul Mohan wrote? Thank them for their work by sharing it on social media.


No comments so far

Leave a question/feedback and someone will get back to you