10 ms·
Intro Guide to Dockerfile Best Practices
- mothsonasloth 7y agoOrder of copying is especially important with Java (Maven/Gradle) builds and NPM, it saves time. Its also good to speed up builds with configuring them to cache artifacts in a separate layer before the build happens or you can get them to use the host machines cached .m2 /.npm folders as a volume, however that might not work with pipelines etc. that build the docker containers.
- pulse7 7y agoDockerfile = docker run + docker commit + MANY unneeded limitations
- RayVR 7y agoCould you elaborate?
- pulse7 7y agoOne example: With Dockerfile you almost can not create your own slim Docker image in an isolated environment (without internet connection) if you need to install some packages from a mounted volume where you have downloaded cached dependencies - because docker build does not allow you to temporarily mount the cached dependencies. With docker run + docker commit combination you can do that without limitations. Latest experimental BuildKit has the option to make a temporal mount, BUT it can not work without internet connection...
- peterwwillis 7y agoYou can do that with a multi-stage build with the cached packages in your context, or you can spin up an http server with the files and ADD the URLs, or set an http proxy environment variable for apt in the container, or add your mirror to the image's apt repositories list. You could do all that with a docker compose file, too.
- pulse7 7y agoIf you do it with multi-stage build (where you would copy cached packages in the intermediate stage and remove it after being done) and copy over the whole root (/) from intermediate to the final stage then docker will make a new layer with the completely the same content and your image size will double -> this is a no go. And yes, you can spin up an http server and serve the cached packages and setup up all this before using docker build, but hey I can do it with docker run + docker commit much quicker... (I wrote "you almost can not create" and not "you can not create" - so it is possible with Dockerfile, but is not trivial.)
- geezerjay 7y ago> and copy over the whole root (/) Why on earth would anyone do that? That's simply wrong on so many levels, and is far from being standard practice.
- geezerjay 7y ago> because docker build does not allow you to temporarily mount the cached dependencies. Why do you believe this point is relevant? If you can mount a drive then you can access its contents on the host, and if you can access files on the host then those contents are also accessible in a Docker build.
- pulse7 7y agoYou CAN NOT mount a drive with "docker build". You CAN however mount a drive with "docker run". So therefore: Dockerfile = docker run + docker commit + MANY unneeded limitations
- geezerjay 7y ago> You CAN NOT mount a drive with "docker build". No one said that. I stated the fact that you can access files from the host during a docker build, thus it's irrelevant if you can mount drives or not. Just copy the files into your build stage and that's it.
- AnthonBerg 7y agoPlease don't downvote parent. This is true. It is a fact that everything that happens in a Dockerfile execution via `docker build` can be done with a `docker run` and executing commands and ending with `docker commit`. Even the caching mechanism can be replicated. (Except with more control in my opinion.) It is also a fact that `docker run` has more capabilities than `docker build`, such as being able to mount things in.
- quickthrower2 7y agoThanks as an occasional docker user this wouldn’t have occurred to me.
- viraptor 7y agodocker = bash script + unshare + MANY unneeded limitations But the nicer packaging and simpler model make that new layer of abstraction useful.
- nisa 7y agoIf you use multi-stage builds be aware that COPY --from also can do a chown and save image size - doing a RUN chown -R that is sometimes necassary to run stuff as a regular user duplicates the image size because changed metadata equals a copy for Docker. Also if you dare to enable user-namespaces for Docker because, well also security - multi-stage builds fail (https://github.com/moby/moby/issues/34645 https://github.com/moby/moby/issues/34645)
- avip 7y agoCOPY --chown
- AnthonBerg 7y agoCOPY --chown is a big improvement, but the way that COPY works differently than a Unix cp can be an impediment. (It flattens directory structures.)
- denisstepanov 7y agoFor Java it’s better to use Jib Gradle/Maven plugin from Google. It produces docker image directly and creates layers with dependencies and class files.
- avip 7y agoI've read several "Dockerfile best practices" would-be tutorials, and this one stands out as both correct, concise, well explained, and ordered from simple and important to more nuanced. To the author - great job.
- barrkel 7y agoDockerfiles are a mostly adequate prototyping tool but are not great for generating production builds. Lack of modularity, cascading versioning / dependency management, reproducible builds, ... every time I've used Dockerfiles in anger I've cobbled together another 60% of a build system out of bash scripts. I wish Dockerfiles would just fade away into the background, and be replaced by something more similar to an archiver but with better integration with repositories and versioning metadata.
- itamarst 7y agoThere are some people trying to build alternative toolchains for Docker builds that remove Dockerfiles altogether, e.g. https://buildpacks.io/ https://buildpacks.io/ and (in a very different approach) Nix-based builds. My personal approach for Python applications' Docker packaging (https://pythonspeed.com/products/pythoncontainer/ https://pythonspeed.com/products/pythoncontainer/) was similar to yours: wrap the build process in a script. I wrote it in Python rather than bash, so it's more maintainable, but a Ruby shop might want to write theirs in Ruby, etc..
- AnthonBerg 7y agoUse the experimental BuildKit Dockerfile frontend for much improved build time mounting: https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md https://github.com/moby/buildkit/blob/master/frontend/docker... * You can mount build-time secrets in safely with `--mount-type=secret`, instead of passing them in. (Multistage builds do alleviate the problems with passing secrets in, but not completely.) * Buildkit automatically parallelizes build stages. (Of course!) * Mount apt-cache dirs in at build time with `--mount-type bind` so that you don't have to apt-get update every single time, and you don't have to clear apt-caches either. And lots more. Notice that this mostly involves capabilities that Docker already has to build time.
- pulse7 7y agoBut in order to build with BuildKit you MUST BE connected to the internet during the build... So not ready to go yet...
- AnthonBerg 7y agoThank you! I didn't know that!
- ses1984 7y agoWhy in the world would you need an internet connection to do this? Never used nor heard of build kit but that seems crazy.
- pulse7 7y agoBuildKit is an experimental replacement for docker build from Docker Ltd. When building it fetches some metafiles from the internet. So you can not use it in isolated environments...
- AnthonBerg 7y agoExactly. It's experimental, so this WIP compromise in implementation is understandable. I'm very grateful for the work on BuildKit.
- sjmulder 7y agoI thought about using Docker for a reproducible build environment but, in that context, found it problematic that every time a Dockerfile is built you may end up with new base images and different package versions. That's hardly reproducible. Perhaps I'm coming at this from a wrong angle.
- yakattak 7y agoIf you use the latest tag of your base image, this is definitely true. It can vary by official image, but I’ve noticed a lot of them will tag a version and leave it untouched until a new version is released. But, it is technically possible they could push a change to an older version image.
- emj 7y agoBuild a java application with RHEL-base-jdk:10 and from commit id:[HASH], that is as reproducible as it gets. As is stated you can save all the build dependencies in an Docker image, so you can go back and look at it later. Usually it is enough to have the build logs though. We set system time as well.
- glckr 7y agoTip #6 (Use official images when possible) is certainly convenient when you're just spinning up something (I use them in local docker-composes all the time), but it's surely opening yet another security hole when it comes to prod. We're not lacking examples where packages are hijacked (feels like it happens constantly on npm, rubygems had it just the other day...), and docker hub has already had one security breach. Perhaps worth a mention in this blogpost?
- sudhirj 7y agoYou can use the official images and tag them with the SHA image ID - that should give cryptographically enforced security and reproducibility.
- akanet 7y agoThis counts for absurdly little in the Docker ecosystem
- zoobab 7y agoActually, if you use the SHA256 of the image as a reference for the FROM (ex: tomcat@sha256:c34ce3c1fcc0c7431e1392cc3abd0dfe2192ffea1898d5250f199d3ac8d8720f), and if there is no tag associated to that SHA, there is a chance that the Docker Registry will garbage clean it. The Docker Inc garbage cleaning frequency is not very documented.
- rickycook 7y ago
- gorn 7y agoCould someone explain why tip#9 is a good idea? To me it makes more sense to build the application in the CI pipeline and use Dockerfile only to package the app. The post is focused on Java apps but, for example, there is a distinction on runtime and SDK images in .NET Core. If you want to build in Docker, you have to pull the heavier SDK image. If you copy the built binaries to image, you can use the runtime image. I guess there could be similar situations in other platforms too. Other than that, it looks like a decent guide. Thanks to the author.
- CSMastermind 7y agoAs I read it, the tip is to always build in a consistent environment. I think a CI pipeline counts in that regard. The way I read it, they're more talking about when you're developing locally everyone should be building the application inside of a container rather than on their personal machines with differing set ups.
- sudhirj 7y agoThe same problems that apply to production environments also apply to CI systems - you need to make sure those build agents are project-aware, up to date, and if you decide to move to a new JDK on one project you'll need to update your build servers, and good luck to you if you want to update only some of your projects. The appeal of docker is completely & reproducibly owning production (what runs on your laptop runs on prod), and that also applies to the build (what builds on your laptop builds on prod). Not to mention the add on benefits that you can now use standard build agents across every tech stack and project, no need to customize them or keep them up to date, etc.
- tda 7y agoFor me the big advantage of doing more in docker and lees in the CIT environment is that I have less lock-in/dependency to whatever my CI provider does. I try to reduce my CI scripts to something like docker build docker run image test All complexities of building and collecting dependencies go in dockerfiles, so I can reproduce it locally, or anywhere else. And importantly, without messing with any system settings/packages. No more makefiles or shell scripts that make a ton of assumptions about your laptop that need to be set just right to build something from source; just docker build and off you go. Such a hassle when you need to follow pages of readme just to build something from source (plus a lot of installed dependencies that you have to clean up afterwards)
- itamarst 7y agoAs is the case with the Docker's best practices for Dockerfiles in the official documentation, they're leaving out some really important details. Specifically, they don't really express how Docker packaging is a process integrating the way you build, where you build, and how you build, not just the Dockerfile. 1. Caching is great... but it can also lead to insecure images because you don't get system package updates if you're only ever building off a cached image. Solution: rebuild once a week from scratch. (https://pythonspeed.com/articles/docker-cache-insecure-images/ https://pythonspeed.com/articles/docker-cache-insecure-image...) 2. Multi-stage builds give you smaller images, but if you don't use them right they result in breaking caching completely, destroying all the speed and size benefits you get from layer caching. Solution: you need to tag and push the build-stage images too, and then pull them before the build, if you want caching to work. (Long version, this is a bit tricky to get right: https://pythonspeed.com/articles/faster-multi-stage-builds/ https://pythonspeed.com/articles/faster-multi-stage-builds/)
- avip 7y agoI honestly don't understand this sentiment (other than shamelessly plugging your posts, which is ok). The tradeoff in any packaging mechanism is always "pin everything to the byte" for maximum security and minimum updateability vs. "blindly update to latest without thinking". We normally develop with the second and deploy using the first and docker is no different.
- itamarst 7y agoThis isn't really about "getting the latest version", it's about "getting the latest security patches for a stable version." The presumption here is that you're running on a stable base image, Debian Stable or Ubuntu LTS or something. Updates to system packages are therefore basically focused on security fixes and severe bug fixes (data corruption in a database, say). Even if you're pinning everything very specifically (and you probably should), you still want to at some point get these (severe-bugfix-only) updates on a regular basis for security and operational reasons. Blind reliance on caching prevents this from happening.
- oliveralbertini 7y agoWhat about changing the default USER in the dockerfile?
- yebyen 7y agoI think you're not the only one that mentioned this omission, I'll throw in since it seems to be pretty poorly highlighted and has some surprising failure modes: When you use the USER directive to set up a working user in Dockerfile, be explicit about UID and use the numeric UID. What works 99% of the time is to use alpha username in the "USER" directive. You can get some surprising artifacts if the container runtime hits this rare bug[1]. There are likely several other great ways this can go wrong, as well. Somewhere deep in the manual, it is suggested that you should only use a numeric UID, even though USER accepts an alphanumeric username most of the time, but if you look at container images built by OpenShift and other pro docker-ers, you will see they always do this with a numeric UID. [1]: https://forums.docker.com/t/unable-to-find-user-root-no-matching-entries-in-passwd-file/26545 https://forums.docker.com/t/unable-to-find-user-root-no-matc...
- zapita 7y agoI highly recommend checking out https://github.com/docker/buildx https://github.com/docker/buildx . It’s still in tech preview but it’s an exciting look at the future of docker-build.
- liveoneggs 7y agoso if you want a vi in your image (the article has a vim line) you can get one (because they are occasionally useful) for a low cost by installing nvi instead of vim. It's great and only a few K.
- mkesper 7y agoYou should not include binaries not needed for production for security reasons, not because of size alone.
- remram 7y agoCan you elaborate on this? If someone pwned my container and is able to execute commands, what does it change whether they get to use a text editor? Otherwise, what does it change whether unused programs are around?
- geezerjay 7y agoI believe the whole point is that adding unnecessary software to a container also increases the attack surface unnecessarily.
- zimbatm 7y agoCOPY caching doesn't work between computers. This was surprising to me. I thought I could `docker pull` the layers from the registry and only re-build what had changed on my machine. But no, this doesn't work. The reason is that the docker client archives the source files, including all the file attributes like uid,gid,mtime, ... Between two computers those are bound to be different.
- toniti 7y agoI recommend you to upgrade to BuildKit. Also, the above isn't really true for Docker CLI, mtime was never taken into account in the old builder and Docker CLI never sends uname/gname. With API it was possible in the old builder though (hence the incompatibility with compose for example). In more practical cases I've seen it caused by git directory instead, that isn't stable. In that case, you can put .git into .dockerignore or if you don't use it, BuildKit will discard it automatically.
- musicale 7y agoMy personal best practice is to never use docker.