List of things to do, to improve your Docker experience
Never map the public port on a DockerFile
If you map it, you’ll only be able to have one instance of this container running. If the user wants to map the port, he’ll be able to do it in a compose
script or with -p
option.
# public and private mapping
EXPOSE 80:8080 # don't do this
# private mapping
EXPOSE 80
CMD and ENTRYPOINT better together
The difference is CMD
arguments may be overwritten while ENTRYPOINT
may not.
The best use of them is together, with ENTRYPOINT
setting the main application to launch and CMD
setting the args or flags for this application.
ENTRYPOINT ["bundle", "exec", "jekyll", "serve"]
CMD ["--host", "0.0.0.0"]
Give CMD (or ENTRYPOINT) an interactive shell
In most other cases CMD
should be given an interactive shell such as python or bash. Using this form means, that when you execute something along the lines of docker run -it python
, you’ll get dropped into a usable shell.
CMD ["python"]
Keep your images small
Use .dockerignore
This excludes files not relevant to the build, without restrucuring your sources. It supports the same syntax as .gitignore
.
Use the Cache
Try to always keep your DockerFiles consistent. If they have the same parent image and the same commands’ order (except ADD
), it will hit the cache instead of executing them.
Put all common instructions on top and all the changes at the bottom of your DockerFile.
Minimize the number of instructions
(This is still important for Docker < 17.05. For newer versions, create a multistage build)
Each RUN
, COPY
and ADD
instruction in a DockerFile adds a layer to the image. It’s a good practice to group them together with shell tricks to avoid unneded new layers.
# For example, is better to use just one RUN with && instead of two
RUN go get -d -v golang.org/x/net/html \
&& CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
apt-get
Avoid RUN apt-get upgrade
and dist-upgrade
. Many essential packages cannot upgrade in a unpriviliged contaier.
If you’re going to do it, always combine apt-get upgrade
with apt-get install
in the same RUN
statement. Otherwise it causes issues with Docker’s image cache.
Also, clean up apt’s cache when done.
RUN apt-get update && apt-get install -y \
package-bar \
package-baz
&& rm -rf /var/lib/apt/lists/*
Use multistage builds
(This requires Docker > 17.05)
This tries to improve the bad maintainability at the previous point. Now it’s possible to use one image as builder and after doing all the steps we need to get our item together, we can just copy it into a new image and deploy from there. Use it when possible
One practical example for this would be building a Java App in a Maven image and then deploying in another Java-alpine or Tomcat image. This way we don’t have all the dependencies for Maven at our final build.
# Build image - won't be included for release. Just compiles.
FROM maven:3.6.1-jdk-8-alpine AS builder
WORKDIR /usr/src/app
COPY app .
RUN mvn -f pom.xml clean package
# Deploy image
FROM openjdk:8u212-jre-alpine
COPY --from=builder /usr/src/app/target/my-java-app-*-SNAPSHOT.jar \
/usr/src/myapp/my-java-app.jar
EXPOSE 8080
ENTRYPOINT ["java","-jar","/usr/src/myapp/my-java-app.jar"]
Reference(s)
http://crosbymichael.com/dockerfile-best-practices.html
https://docs.docker.com/develop/dev-best-practices/
https://docs.docker.com/develop/develop-images/multistage-build/
https://docs.docker.com/develop/develop-images/dockerfile_best-practices/