Dockerfile Best Practices

This page summarizes the projects mentioned and recommended in the original post on

Our great sponsors
  • Scout APM - Less time debugging, more time building
  • OPS - Build and Run Open Source Unikernels
  • SonarQube - Static code analysis for 29 languages.
  • dockerfile

    Dockerfile best-practices for writing production-worthy Docker images.

    Do not hesitate to contribute then...


  • hadolint

    Dockerfile linter, validate inline bash, written in Haskell

    Another useful resource is hadolint (, which not only gives additional recommendations, but also a way to enforce this.

  • Scout APM

    Less time debugging, more time building. Scout APM allows you to find and fix performance issues with no hassle. Now with error monitoring and external services monitoring, Scout is a developer's best friend when it comes to application development.

  • Moby

    Moby Project - a collaborative project for the container ecosystem to assemble container-based systems

    I believe we setup containers in Docker 20.10 such that containers can bind to < 1024 by default without giving it cap_net_bind:

  • caddy-docker

    Source for the official Caddy v2 Docker Image

    I find it somewhat curious, that the article starts with listing examples where root user is used to run containers:

    > Writing production-worthy Dockerfiles is, unfortunately, not as simple as you would imagine. Most Docker images in the wild fail here, and even professionals often[1] get[2] this[2] wrong[3].

    And yet, in some of the linked URLs, people are presenting reasons for why that approach was used in particular, instead of the supposedly safer alternatives.

    For example,

    > We actually originally did run as non-root by default, but simplicity we decided to drop that (see #24, and also #103 for some other related discussion).

    > If your Dockerfile works for you, that's great. In most cases where users want to run as non-root, they also don't need to listen to :80/:443 in the container, so the setpcap magic isn't necessary at all.

    > It's also worth noting that caddy is an official image, and as such needs to be similarly-shaped to other official images of the same type. At a quick glance, none of nginx, traefik, httpd, or haproxy support running as non-root out of the box either.

    > Finally, it's worth considering why you want to run as non-root. What attack vectors are you trying to avoid? Container escape vulnerabilities are pretty much the only real risk, but anyone running a modern Docker version is immune to many of them. It's also worth considering user namespace remapping as a mitigation. In my experience the main reason for running as non-root is to pass compliance checks - not a bad reason, but it's also worth recognizing that non-compliance does not automatically equal decreased security (and vice-versa).

    If larger projects, like Nginx, Traefik, Httpd and HAProxy were all creating containers like that, it makes you think about the reasoning behind it. Is it easier to just run containers as root and not worry about the permissions inside of the container? If so, wouldn't it really make more sense for the container runtime to have some sort of mechanisms in place to allow people to do what's easy within the containers while also making sure that it has no harmful impact outside of them?

    Because to me it seems like people will continuously take the path of least resistance and from where i stand, it should be up to the creators of the container technologies to make sure that this path is safe by default.

  • trivy

    Scanner for vulnerabilities in container images, file systems, and Git repositories, as well as for configuration issues

    +1 for hadolint. There's also and if you want more security emphasis.

  • docker-flask-example

    A production ready example Flask app that's using Docker and Docker Compose.

    I have one here:

    The basic idea is you create a user in your Dockerfile, switch to that user with the USER instruction and now future instructions in your Dockerfile will be run as that user.

    Also when COPY'ing you'll want to add --chown myuser:myuser too.

    The above Dockerfile shows examples of all of that.

    I'm not a fan of customizing the UID / GID because then in development with volumes you can get into trouble. Technically you could put UID / GID as build arguments but in practice I never ran into a scenario where this was needed because 99% of the time on a dev box your uid:gid will be 1000:1000 and in production chances are you are in control of provisioning your VPS so your deploy user will be 1000:1000. Also you probably won't be using volumes, but if you did it will work out of the box.

NOTE: The number of mentions on this list indicates mentions on common posts plus user suggested alternatives. Hence, a higher number means a more popular project.

Suggest a related project

Related posts