Essentials of Dockerfile for Building Custom Docker Images


Introduction


A Dockerfile is a text file containing a set of instructions that Docker uses to build a custom Docker image. By writing a Dockerfile, you can automate the creation of images that define everything needed to run an application: the operating system, libraries, dependencies, configurations, and more. This article will explore the basics of Dockerfiles and show you how to create your own custom images.


What is Dockerfile?

A Dockerfile is essentially a script containing a series of instructions on how to build a Docker image. Each instruction in the Dockerfile corresponds to a layer in the resulting image. These layers are cached to speed up future builds. By building an image from a Dockerfile, you can create a consistent, reproducible environment for your applications, ensuring that the app will run the same way on any machine that uses the image.


Structure of a Dockerfile

A Dockerfile consists of a series of commands that define how the image should be built. Each command has a specific purpose, and the Docker engine processes these instructions one by one. Below is a breakdown of the most commonly used instructions in a Dockerfile:

  • FROM: Specifies the base image to use for the new image. This is typically an official image from Docker Hub, such as Ubuntu or Alpine.
  • RUN: Executes a command inside the container. This is often used for installing software packages or setting up the environment.
  • COPY: Copies files from your local machine into the container.
  • ADD: Similar to COPY but with more features, like automatically extracting tar files.
  • CMD: Provides the default command to run when the container starts. This can be overridden when running the container.
  • ENTRYPOINT: Defines a command that will always be run when the container starts, even if a different command is provided.
  • EXPOSE: Informs Docker that the container listens on the specified network ports.
  • WORKDIR: Sets the working directory for subsequent commands.

Basic Dockerfile Example

Here’s an example of a simple Dockerfile to create an image for a Node.js application:


# Use an official Node.js runtime as a base image
FROM node:14

# Set the working directory inside the container
WORKDIR /app

# Copy the package.json and install dependencies
COPY package.json /app
RUN npm install

# Copy the rest of the application code
COPY . /app

# Expose port 8080
EXPOSE 8080

# Define the command to run the application
CMD ["npm", "start"]
        

Let’s break this down:

  • FROM node:14: This instruction tells Docker to use the official Node.js image with version 14 as the base image.
  • WORKDIR /app: This sets the working directory to /app inside the container, where all subsequent commands will run.
  • COPY package.json /app: Copies the package.json file to the working directory in the container.
  • RUN npm install: Runs the npm install command to install the application’s dependencies inside the container.
  • COPY . /app: Copies the rest of the application code into the container.
  • EXPOSE 8080: Tells Docker that the container will listen on port 8080, making it accessible to the outside world.
  • CMD ["npm", "start"]: Defines the default command to start the application when the container runs.

Building and Running a Docker Image

Once you’ve created a Dockerfile, you can use the Docker CLI to build and run the image:

Build the Docker image:

docker build -t my-node-app .

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

Run the Docker image:

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

This command starts a container from the `my-node-app` image and maps port 8080 from the container to port 8080 on the host machine.


Best Practices for Writing Dockerfiles

Here are some best practices to follow when writing Dockerfiles:

  • Use a Minimal Base Image: Use the smallest base image possible to minimize the size of your image. For example, use `node:14-alpine` instead of `node:14` for a smaller, more lightweight image.
  • Minimize the Number of Layers: Each instruction in a Dockerfile creates a layer in the image. To optimize build time and image size, combine related commands (e.g., RUN) into a single layer.
  • Leverage Build Caching: Docker caches each layer, so you should order your instructions logically to take advantage of caching. For example, copy `package.json` first and run `npm install` before copying the rest of the application code, so that dependencies don’t need to be reinstalled on every build.
  • Clean Up Temporary Files: After installing dependencies or compiling code, be sure to remove unnecessary files to reduce the image size. For example, use `RUN apt-get clean` after installing packages.

Conclusion

Dockerfiles are a powerful way to automate the process of building custom Docker images. By writing a Dockerfile, you can ensure that your application environment is consistent and reproducible. Remember to follow best practices to optimize your Dockerfiles for speed, security, and size.