Docker Build Best Practices
Tags: docker, devops
Published: 2020-03-11 02:54:12

As more and more applications are being containerized it is important to point out some best practices to optimize and better support the building of those images.

## Optimize Build Context
The one argument that is passed to a `docker build` command is the context location.  This location represents the directory that the build is going to have access to for all of the instructions it is going to process.  Any `COPY` or `ADD` instruction uses this location as the base directory.  The build also uses this location to determine if a file has changed and thus should break the layer cache.  It is for this reason that careful consideration should be made to the .dockerignore file to make sure directories or files that are not needed are not included in the context.  Examples are excluding things like reports, documentation, the node_modules directory, and most commonly the .git directory. A common example is the `COPY . .` command putting the .git files in the image.

## Use Labels
Labels are useful to store metadata about the image such as the git commit hash or the maintainer.  If your organization builds many images it is a good practice to store such metadata in a common form so that the information can be retrieved in a common way.  Here is an example of how to add the git commit hash to the image as a label and then retrieve it.

Dockerfile contents:
```
ARG GIT_COMMIT=unspecified
LABEL git_commit=$GIT_COMMIT
```

Build command:
```
docker build --build-arg GIT_COMMIT=$(git log -1 --format=%h) -t my-image .
```

Get the value:
```
docker inspect –f {{index .Config.Labels.git_commit}} my-image
```

## Document Ports
The `EXPOSE` directive in a Dockerfile does not actually expose the port to the host at runtime.  The only thing it can automatically do at runtime is bind that port to a random port on the host machine if the `-P` option is used in the run command.  It is for this reason that the `EXPOSE` directive is most commonly used, and should be used, to document that if you run the image as a container then you should map a host port to the port specified.  This is useful for times when an application has configured the server runtime to use a different port than the default.

## COPY vs ADD
Both the `COPY` and `ADD` directives provide the ability to transfer files into the image.  However, `ADD` has two additional features in that the first argument can be a URL and if the first argument is an archive it will automatically be extracted.  It is for this reason that it is useful to only use the `ADD` directive if you are doing one of those two things.  Otherwise it is preferred to use the `COPY` directive.

## Optimize Layer Cache
Each directive in a Dockerfile results in a layer and each layer is evaluated during the build to determine if the cached layer should be used vs executing the directive.  Once a layer is determined to not use the cache all other subsequent directives are executed and the cache will not be considered or used.  It is for this reason that careful consideration should be made to the order of which directives are listed in the Dockerfile.  The things that change the least should come before things that change more often.

Here is an example of the difference between two Dockerfiles and what would happen if a change was made to some code written in node:
```
FROM node
WORKDIR /usr/src/app
COPY . .                   <------- Cache is broken here
RUN npm install
EXPOSE 8080
CMD [“npm”, ”start”]
```

With a subtle change the layer cache will only be broken if a change is made to package.json.  The `npm install` command will only run in that case but the cached layer will be used if the change was made to a file other than package.json.
```
FROM node
WORKDIR /usr/src/app
COPY package*.json .
RUN npm install
COPY . .                   <------- Cache is broken here
EXPOSE 8080
CMD [“npm”, ”start”]
```

## Summary
These are just some of the best practices that can be adopted to save build time, save development time and also provide documentation to avoid confusion.  There are many ways an image can be built and provide the same functionality so careful consideration should be made with these details in mind.