DEV Community

Abhay Singh Kathayat
Abhay Singh Kathayat

Posted on

Best Practices for Writing Efficient and Maintainable Dockerfiles

Dockerfile Best Practices

A well-crafted Dockerfile is essential for creating efficient, secure, and maintainable Docker images. Following best practices ensures that your images are lightweight, fast to build, and easy to manage. In this article, we’ll cover essential Dockerfile best practices that can help improve the quality of your Docker images and optimize your development workflows.


1. Use a Minimal Base Image

Start with a minimal base image to reduce the size of the final Docker image. For example, instead of using large images like ubuntu or debian, consider using images like alpine or slim versions that have fewer dependencies and a smaller footprint.

Example:

# Use a minimal base image (e.g., Alpine) for smaller image size
FROM node:16-alpine
Enter fullscreen mode Exit fullscreen mode

This practice reduces the overall size of the image, which is beneficial for faster builds, easier uploads to registries, and reduced resource consumption during runtime.


2. Order Instructions to Leverage Caching

Docker caches each layer of the image during the build process. To optimize build time, place instructions that change less frequently at the top of your Dockerfile and more frequently changing instructions towards the bottom.

Best Practice:

  • Install dependencies first.
  • Copy static files (like package.json, requirements.txt, etc.) early.
  • Copy your source code at the end.

This strategy leverages Docker’s layer caching, so if only the code changes, Docker doesn’t have to reinstall dependencies, speeding up subsequent builds.

Example:

# Install dependencies first, as these change less frequently
COPY package.json /app/
WORKDIR /app
RUN npm install

# Copy source code after installing dependencies
COPY . /app/
Enter fullscreen mode Exit fullscreen mode

3. Avoid Installing Unnecessary Packages

Always aim to install only the packages necessary for your application to function. Installing unnecessary tools or dependencies can significantly increase the size of your image.

  • Avoid installing unnecessary utilities like curl, git, or vim unless they are essential for your container’s runtime environment.

Example:

# Only install necessary dependencies
RUN apk add --no-cache bash
Enter fullscreen mode Exit fullscreen mode

By using the --no-cache option, we prevent Docker from caching package index files, which saves space.


4. Reduce the Number of Layers

Each Dockerfile instruction creates a new image layer. The more layers there are, the larger the image becomes, and the slower the build process is. Try to consolidate multiple commands into fewer layers.

Best Practice:

Combine RUN instructions where possible. For example, instead of running separate RUN commands for each package installation, combine them into a single RUN statement.

Example:

# Instead of running multiple RUN commands
RUN apt-get update && apt-get install -y \
    curl \
    vim \
    git

# Combine them into a single RUN command to reduce layers
RUN apt-get update && apt-get install -y curl vim git
Enter fullscreen mode Exit fullscreen mode

5. Use .dockerignore to Exclude Unnecessary Files

Similar to .gitignore for Git, .dockerignore is used to prevent unnecessary files from being copied into your Docker image, which can reduce the image size and improve build speed.

Best Practice:

Create a .dockerignore file to exclude files like:

  • .git/
  • node_modules/
  • *.log
  • Temporary files

Example:

# .dockerignore
node_modules/
.git/
*.log
*.md
Enter fullscreen mode Exit fullscreen mode

By ignoring files that aren’t needed for the application’s runtime, you minimize the size of your image and prevent Docker from copying unnecessary files during the build process.


6. Leverage Multi-Stage Builds

Multi-stage builds allow you to separate the building of your application from the final image, which helps you produce smaller, cleaner images. You can use one stage to build the app (with build tools) and another stage to copy the built assets into a minimal runtime image.

Best Practice:

  • Use a build stage for compiling code, installing dependencies, or running tests.
  • Copy the resulting artifacts into a smaller final image.

Example:

# Build stage
FROM node:16-alpine AS build
WORKDIR /app
COPY package.json package-lock.json /app/
RUN npm install
COPY . /app/
RUN npm run build

# Final stage
FROM node:16-alpine
WORKDIR /app
COPY --from=build /app/dist /app/dist
CMD ["node", "dist/server.js"]
Enter fullscreen mode Exit fullscreen mode

In this example, the build stage installs dependencies, runs the build command, and compiles the application. The final stage only includes the built files, resulting in a much smaller image.


7. Use Specific Version Tags for Base Images

Avoid using the latest tag for base images, as it can lead to unpredictable behavior when base images are updated. Always specify a fixed version to ensure consistency across builds.

Best Practice:

  • Use specific tags (e.g., node:16-alpine instead of node:alpine).
  • If possible, use exact versions for base images to prevent future breaks.

Example:

# Good practice: use a specific version tag for reproducibility
FROM node:16-alpine
Enter fullscreen mode Exit fullscreen mode

8. Minimize the Use of RUN Instructions

Every RUN instruction adds a new layer to your image. Minimize the number of RUN instructions to reduce image size and build time. Instead of running multiple commands in separate RUN instructions, combine them into a single command.

Best Practice:

Combine multiple commands into a single RUN statement using && to chain them together.

Example:

# Multiple RUN instructions (not ideal)
RUN apt-get update
RUN apt-get install -y curl
RUN apt-get install -y git

# Combined RUN instruction (ideal)
RUN apt-get update && apt-get install -y curl git
Enter fullscreen mode Exit fullscreen mode

9. Set Non-Root User for Security

Running containers as root is a security risk. It’s recommended to use a non-root user inside the container to reduce the impact of potential vulnerabilities in the containerized application.

Best Practice:

  • Create and switch to a non-root user after installing necessary packages.

Example:

# Create a non-root user and switch to that user
RUN adduser --disabled-password myuser
USER myuser
Enter fullscreen mode Exit fullscreen mode

10. Explicitly Set Environment Variables

Set environment variables explicitly in the Dockerfile using the ENV instruction. This can help to configure the application and make it more portable across different environments.

Example:

# Set environment variables
ENV NODE_ENV production
ENV PORT 3000
Enter fullscreen mode Exit fullscreen mode

Conclusion

Following Dockerfile best practices is crucial for creating efficient, maintainable, and secure Docker images. By using minimal base images, reducing the number of layers, excluding unnecessary files, and following the other recommended practices, you can create optimized Docker images that are faster to build, smaller in size, and easier to manage. Additionally, employing multi-stage builds and non-root users can improve both security and performance.

By adhering to these best practices, you ensure that your Docker images are efficient, portable, and consistent across different environments.


Image of AssemblyAI

Automatic Speech Recognition with AssemblyAI

Experience near-human accuracy, low-latency performance, and advanced Speech AI capabilities with AssemblyAI's Speech-to-Text API. Sign up today and get $50 in API credit. No credit card required.

Try the API

Top comments (6)

Collapse
 
joodi profile image
Joodi

Love this 🌱

Collapse
 
abhay_yt_52a8e72b213be229 profile image
Abhay Singh Kathayat

Thank you, Joodi! 🌱

Collapse
 
lurodriguez profile image
L Rodríguez

Knowing how to deal with dockerfiles nowadays is a must have skill, thanks for sharing!

Collapse
 
abhay_yt_52a8e72b213be229 profile image
Abhay Singh Kathayat

Absolutely, Victor! Dockerfiles have become an essential tool for modern development workflows. I'm glad you found the post valuable—happy coding!

Collapse
 
nextbot83_1b565f34d8c8f0f profile image
Nextbot83

This is NICE 🔥🔥🔥🔥🔥💯💯💯

Collapse
 
abhay_yt_52a8e72b213be229 profile image
Abhay Singh Kathayat

Thank you so much for the energy, Nextbot! 🔥🔥 I’m thrilled you liked it. Let’s keep building great things!"

Some comments may only be visible to logged-in visitors. Sign in to view all comments.

Sentry workshop image

Flaky tests got you down?

Learn how to merge your code without having to hit “rerun” every 5 minutes 😮‍💨

Save your spot now.

👋 Kindness is contagious

Explore a sea of insights with this enlightening post, highly esteemed within the nurturing DEV Community. Coders of all stripes are invited to participate and contribute to our shared knowledge.

Expressing gratitude with a simple "thank you" can make a big impact. Leave your thanks in the comments!

On DEV, exchanging ideas smooths our way and strengthens our community bonds. Found this useful? A quick note of thanks to the author can mean a lot.

Okay