This is my note from this this talk on Dockerfile good practices
- Incremental build time
- Image size
- Consistency / Repeatability
- Order is important: if you make changes to any line or any stage of your dockerfile then subsequent stages cache will be busted. So, order your steps from least to most frequently changing steps to optimize caching.
Let's look at this sample dockerfile
FROM ubuntu:18.04 COPY . /app RUN apt-get update RUN apt-get install openjdk-8-jdk
This dockerfile copies the application files immediately after the start. Because caching is based on previous steps, whenever something changes in that content, all the steps after the copy has to be invalidated. So, the cache will have to be busted and the steps below should run again.
This is a problem here, since if you want to change your application code, and want to build your image reflecting the latest change, you'll have run all the commands below the COPY command.
A better way here, would be to run the
COPY command at the last.
FROM ubuntu:18.04 RUN apt-get update RUN apt-get install openjdk-8-jdk COPY . /app
Only copy what's needed Avoid
COPY .if possible. When you are copying files into your image make sure you're very specific as to what you want to copy, because any changes to files you're copying will bust the cache
Identify cacheable units Sometimes you want things to be cached together. Identify cacheable units. For example change this
RUN apt-get update RUN apt-get -y install openjdk-8-jdk
RUN apt-get update \ && apt-get -y install \ openjdk-8-jdk
This prevents using an outdated package cache.
- Fetch dependencies in a separate step: This is also about identifying the cacheable units.
Remove unnecessary dependencies: Don't install debugging tools and other unnecessary dependencies. You can also use the
--no-install-recommendsflag. You don't want to deploy your build tools into production, as you will not need them at runtime.
Remove package manager cache: You don't need the cache after installing the packages. It's good to remove them as well
RUN apt-get update \ && apt-get -y install --no-install-recommends \ openjdk-8-jdk \ && rm -rf /var/lib/apt/lists/*
Use official images where possible: Official images are pre-configured for container use and built by smart people. It can save you a lot of time in maintenance. This also allows you to share layers between images, as they use exactly the same base image.
For the above sample Dockerfile instead of using debain and installing the dependencies simply start your base image from
Use more specific tags: The
latesttag is a rolling tag. Be specific to prevent unexpected changes in your base image.
Look for minimal flavors Maybe you don't need all the things that is in the bigger variants.
REPOSITORY TAG SIZE ----------------------------------------- openjdk 8 624MB openjdk 8-jre 443MB openjdk 8-jre-slim 204MB opendjk 8-jre-alpine 83MB
The dockefile as a blueprint of your image, source code the source of truth for your application
Make the dockerfile your blueprint
- It describes the build environment
- Correct versions of build tools installed
- Prevent inconsistencies between environments
- There may be system dependencies
- Separate build from runtime environment
- Slight variations on images (DRY)
Build dev/test/lint/ specific environments
- builder: all build dependencies
- build: builder + build artifacts
- cross: same as build but for different envs
- dev: builder + dev/debug tools
- lint: minimal lint dependencies
- test: all test dependencies + build artifacts to be tested
- release: final minimal image with build artifacts
Delinearizing your dependencies (concurrency)
Platform specific stages
When you name a stage you can only build that stage.
FROM image_or_stage AS stage_name
$ docker build --target stage_name