Published on

Top 5 Docker Best Practices

4 min read

Authors

In this article, we will go over some essential Docker best practices which will help us to optimize our images for better size, security and developer experience.

#1 Least Privileged User

By default if the user isn't specified in the Dockerfile, it will use the root user, which can be a big security risk as 99% of the time your application doesn't need a root user and this might make it easier for an attacker to escalate privileges on the host.

To avoid this simply create a dedicated user and a dedicated group in the Dockerfile and add the USER directive to use that user to run the application.

least-previleged

Note: Some images already come with a generic user, which we can use. For example, node.js images comes with a user called node.

#2 Multi-Stage Builds

Docker images are often much bigger than they have to be which ends up impacting our deployments, security and dev experience. Optimizing a build can be complex as it's hard to keep your image clean and eventually, it gets messy, and hard to follow. We also end up shipping unnecessary assets like tooling, dev dependencies, runtime or compiler in our releases.

TLDR

The main idea is to separate the build stage from the runtime stage.

  • Derive from a base image with the whole runtime or SDK
  • Copy our source code
  • Install dependencies
  • Produce build artifacts
  • Copy the built artifacts to a much smaller release image

Below is an example for Go, the final production image is just round 10mb! multistage-build

Note: To learn more about multi-stage builds, check my earlier article on Art of building small containers

#3 Scanning for Security Vulnerabilities

Security is an essential part of any application especially if you're working in a highly regulated industry such as healthcare, finance, etc. Lucky for us, docker comes with a docker scan command, which can scan our images for security vulnerabilities. It is recommend to use this as part of your CI setup.

$ docker scan <your-image>

Here are scan results for node:17-alpine and node:17. As we can see node:17 with full OS distribution has tons of vulnerabilities.

good-example bad-example

#4 Use smaller size Official Images

Let's be honest, no one likes pulling huge containers whether for development or during CI builds. While sometimes we have to use a container with full OS distribution for particular task but in my opinion, containers should be small and just act as an isolated wrapper around our applications when it comes to shipping code. Hence, it is recommended to use smaller images with leaner OS distributions, which only bundle the necessary system tools and libraries, also minimizing the attack surface and making sure that we have more secure images.

For example, using alpine often a common practice when it comes to optimizing image sizes. Alpine Linux is a security-oriented, lightweight Linux distribution based on musl libc and busybox.

small-images dockerhub

#5 Use caching

As we know, docker images contains various layers and in a Dockerfile each command or instruction creates an image layer. So if we rebuild our docker image and the Dockerfile or the layer hasn't changed, Docker will just use the cached layer to build the image. This results in significantly faster image rebuilds.

Below is an example of how we can cache node_modules by taking advantage of docker layer caching. This can be implemented for multiple situations depending on our Dockerfile. caching

Conclusion

In this article we discussed best practices for Docker, I hope this article was helpful and as always if you have any questions feel free to reachout to me on LinkedIn or Twitter.

© 2024 Karan Pratap Singh