13 ms·
Dockerfile Best Practices
- etaioinshrdlu 6y agoDoes anyone have an example of adding a non-root user in the Dockerfile as mentioned, that they like to use in a cookie-cutter approach?
- nickjj 6y agoI have one here: https://github.com/nickjj/docker-flask-example/blob/ca1d48499a4007378bbe363bd5dc117c1a33cc3a/Dockerfile#L47 https://github.com/nickjj/docker-flask-example/blob/ca1d4849... 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 set 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 too. Also you probably won't be using volumes, but if you did they will work out of the box with the above example.
- LucidLynx 6y agoMaybe you could be interested in this article: https://www.rockyourcode.com/run-docker-nginx-as-non-root-user/ https://www.rockyourcode.com/run-docker-nginx-as-non-root-us...
- sdwolfz 6y agoHere's my approach: https://www.codrut.pro/snippets/docker-images-basic-ruby-setup/ https://www.codrut.pro/snippets/docker-images-basic-ruby-set... [Edit] This is what I use for local development docker images. Note: I disagree with this article, none of the things listed are "best" practices in my opinion... more like random practices somebody prefers. Use what you like.
- kubanczyk 6y agoOP's version: > UIDs below 10,000 are a security risk on several systems, because if someone does manage to escalate privileges outside the Docker container their Docker container UID may overlap with a more privileged system user's UID granting them additional permissions. > [...] there may sometimes be reasons to not do what is described here, but if you don't know then this is probably what you should be doing. Your version: > Use what you like. > UID=1000
- sdwolfz 6y agoI should probably also mention this example is for docker images that I use for local development with a shared file mount having the same UID/GID as your host, so you don't have to `chown` all the time while developing.
- BossingAround 6y ago> more like random practices somebody prefers Surely, if "X" has posed a security flaw in the past, and "Y" achieves the same as "X" without being vulnerable to the exploits (e.g. the UID advice), then "Y" is objectively better and not a matter of style, is it not?
- rualca 6y agoDocker's official article on best practices has a section dedicated to showing how to add user accounts. https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ https://docs.docker.com/develop/develop-images/dockerfile_be...
- KronisLV 6y agoI find it somewhat curious, that the article starts with listing examples where root user is used to run containers (amongst other issues): > 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, https://github.com/caddyserver/caddy-docker/issues/104 https://github.com/caddyserver/caddy-docker/issues/104 > 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.
- rualca 6y ago> 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. That does not sound like an adequate excuse, does it? I'm afraid none of your remarks makes a reasonable point. Even if you believe that running random stuff as root is ok, if you do not have any reason to do that then why should we mindlessly follow bad practices? If we aim at running a safe system.and there is absolutely zero drawback in following best practices, then why should we continue to make the mistake of intentionally using poor practices?
- gbrindisi 6y agoVery nice! Taking care of Dockerfiles is such a low hanging fruit to prevent so many issues down the line. If you are interested I’ve collected more best practices to prevent more common security issues: https://cloudberry.engineering/article/dockerfile-security-best-practices/ https://cloudberry.engineering/article/dockerfile-security-b...
- notretarded 6y agoYou give examples of what not to do, but not what an alternative is. This makes the guide useless for any intended purpose of educating.
- KrishnaAnaril 6y agoI find this comment a bit harsh as the blog was informative for me. If there are multi ways to achieve a target, informing about which path won’t work is really helpful.
- Already__Taken 6y agoIt covers WHY not to do things which is IMO more valuable at least than the many blogs "How to do X" in 6 steps the have no documentation _at_all_
- rualca 6y agoIn general this article was very disappointing. With the exception of suggesting using tini as an entry point, this article adds nothing beyond the very basic advice of not running stuff at root. There's no mention of multi-stage builds, labelling images, using env variables to pass ephemeral config settings, setting up tmpfs folders to avoid cluttering containers with temp data, the importance of rebuilding images periodically... In short, the basics. Edit: this stuff is already covered by Docker in it's official Dockerfile best practices guide. https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ https://docs.docker.com/develop/develop-images/dockerfile_be... I was expecting far more from an article on HN about Dockerfile best practices, particularly one which is currently being listed so prominently. I mean, does the HN crowd need a top ranking link to be reminded that we should not be running external-facing services as root?
- LucidLynx 6y agoDo not hesitate to contribute then... -> https://github.com/hexops/dockerfile/pulls https://github.com/hexops/dockerfile/pulls
- rualca 6y ago> Do not hesitate to contribute then... This info is quite literally made available by Docker itself in the form of it's official best practices guide, which exists for years now. https://docs.docker.com/develop/develop-images/dockerfile_best-practices/ https://docs.docker.com/develop/develop-images/dockerfile_be... How is anyone publishing a so-called best practices guide when they aren't even aware of the stuff covered by the official best practices guide?
- Gigablah 6y agoYou're hastily assuming that they "aren't aware" when they could have simply chosen to avoid redundancy.
- radicalbyte 6y agoBecause they can post something wrong, get the real experts on HN to fix it, then they can use their now awesome repository to sell consultancy service as one of the biggest Docker experts in the world. :-)
- choeger 6y agoPodman solves the UID issue quite nicely with user namespaces.
- captn3m0 6y agoI suggest running hadolint, which scans for most of this.
- vsupalov 6y ago+1 for hadolint. There's also https://github.com/goodwithtech/dockle https://github.com/goodwithtech/dockle and https://github.com/aquasecurity/trivy https://github.com/aquasecurity/trivy if you want more security emphasis.
- znpy 6y ago> Do not use a UID below 10,000 I see the point but this kinda looks like extreme paranoia. I actually advise developers that are not so strong at docker and containers to use his 1000 in their container. Such uid is usually the default first uid in Ubuntu and thus makes running containers in their development machine easier (since now they don't have to deal with file permissions and uids without a corresponding user on their system). Someone will boo this and maybe even downvote, but it really helps developer "think operationally", and gets their dev environment closer to prod.
- toolslive 6y agoit's not bad advice to use UID 1000, BUT if you're using docker both for development and continuous integration, you might run into problems because jenkins (or whatever you're using) might not run with UID 1000. At that point, you're gonna to need to change your strategy and properly map the user/group outside the container to the user/group inside the container anyway.
- kortex 6y agoRight. This is great if you are still learning the ropes. However the title is best practices. Using uid 1000 and using rw file mounts exposes your main user to potential exploits. Often 1000 is in the docker group for convenience. That allows an attacker to potentially append some nasty code outside the container and get password-less sudo. Unlikely? Very. But security is all about layers. I've seen full-fat Ubuntu containers with uid 1000 which is in docker group on the host and ~ mounted to run some python flask app, in production.
- znpy 6y ago> I've seen full-fat Ubuntu containers with uid 1000 which is in docker group on the host and ~ mounted to run some python flask app, in production If you mount ~ in your containers you have bigger problems than uid 1000.
- wwright 6y agoRunning with uid 1000 in the Docker group in prod is a very bad idea to begin with
- mrweasel 6y agoOn recommandation, from the official Docker documentation, I frequently see people ignore is the ‘do not use a supervisor process in your container’. Stuffing something like Tomcat into a Docker container is trick, you risk having your application crash, but Docker doesn’t handle it, because the Tomcat process is still running. You also see people use different supervisor processes to have multiple application in one container, e.g. a uwsgi app and nginx in one container, while that should be two seperate containers.
- rualca 6y ago> (...) but Docker doesn’t handle it, because the Tomcat process is still running. ...and that's why no one uses it that way, and instead uses Docker's built-in support for healthchecks. Check out the HEALTHCHECK entry in Docker's reference https://docs.docker.com/engine/reference/builder/ https://docs.docker.com/engine/reference/builder/
- mrweasel 6y agoBut they don’t.... they should, but don’t. I’ve helped plenty of client either redesign their containers, or implement healthchecks. Note that adding the healthcheck isn’t enough. Docker won’t actually do anything if a container is unhealthy. You need a seperate process to restart the unhealthy container.
- technological 6y agoWe use hashicorp nomad, which handles the restart of unhealthy container. I think if ppl are using large number of containers (scale of more than 1000) then they would have some sort of tool that manages them
- 2ion 6y agoAll our containers expose meaningful healthcheck endpoints. We practice STONITH for fencing. And we define service health purely using SLIs that reflect actual service usage (or ability to use the service). As such, we do not rely on Docker HEALTHCHECKs. We don't want to rely on a container measuring its own health; we often had issues with developers putting the equivalent of "return true;" inside their checks…
- vsupalov 6y agoShameless plug: if you want to learn more about building better Docker images (beyond using hadolint, which is an excellent quick win) I put together a comprehensive free email course about that topic over at https://vsupalov.com/courses/ https://vsupalov.com/courses/
- timwis 6y agoPutting the binary in entrypoint instead of CMD seems like a misuse of what CMD was intended for, no? What if you want to run the image to do some one-off task instead of the normal CMD? (e.g. override the binary)
- kortex 6y agoThen you override entrypoint. docker run --entrypoint=foo Docker compose has the key word as well.
- rualca 6y ago> Putting the binary in entrypoint instead of CMD seems like a misuse of what CMD was intended for, no? No. From Docker's official reference: > The main purpose of a CMD is to provide defaults for an executing container. These defaults can include an executable, or they can omit the executable, in which case you must specify an ENTRYPOINT instruction as well. https://docs.docker.com/engine/reference/builder/#cmd https://docs.docker.com/engine/reference/builder/#cmd ENTRYPOINT should point to the entrypoint, and CMD should store default command line arguments. This allows a container image to be executed as a command line application.
- ros86 6y agoAnother useful resource is hadolint (https://github.com/hadolint/hadolint https://github.com/hadolint/hadolint), which not only gives additional recommendations, but also a way to enforce this.
- kortex 6y agoIs tini still considered best practice? I remember reading that it was included and now you can just docker run --init (init: true in compose)
- jitl 6y agoFrom TFA: > Unfortunately, although Docker did add it natively, it is optional (you have to pass --init to the docker run command). Additionally, because it is a feature of the runtime and e.g. Kubernetes will not use the Docker runtime but rather a different container runtime it is not always the default so it is best if your image provides a valid entrypoint like tini instead.
- kortex 6y agoThis is what I get for skimming, thanks for pointing it out. I have noticed that after moving away from tini, some of my containers take a lot longer to shut down, despite the init flag. I think this might be related.
- mjh2539 6y agoWhat about consolidating `RUN` stuff to as few lines as possible so that your images aren't gigantic?
- pg_bot 6y agoI agree with never using latest, but disagree with the use of `major.minor` over `major.minor.patch`. Subscribe to all of your core dependencies release/security notes (OS, language, framework) and bump to the most recent patch versions as soon as possible. This gets you in the habit of updating dependencies regularly which is one of the most important things to do when trying to run secure services.