Use the Official Docker Image for your Base Image. Download the image from the docker.io site.
Never use the Latest tag for an image. In order for consistency, identify the version you want to use. This also prevents potentially breaking changes.
Use the image that satisfies your requirements. Many time something like Alpine, which is a tiny image, has all the functionality you need for your image. Something like Ubuntu/Debian or CentOS will have a ton of extra, unnecessary tools. Similar to when we build server, only start services that are needed and disable or even remove the services that aren’t used.
Optimize Caching Image Layers. If you check the dockerfile for an image, you can see how what’s been installed. Each command adds a layer to the final container. To optimize, consider how your container is built. Whatever is changed the most often, should be later in the final container. For example, your application code might be best at the end of the dockerfile. Then the rest of the unchanged layers won’t need to be rebuilt as everything after a rebuilt image is also rebuilt.
docker history image:tag
Exclude unnecessary content to reduce the size of the image. Use the .dockerignore file. Ignoring .git, etc.
Remove unnecessary files after the container is built. Use multi-stage builds. Lets you use staging images to prevent development files from the final image. For example, when you compile a C program, you have makefiles and .obj files. You would use a multi-stage build where the first image compiles the final program but the last image only contains the compiled binary.
Set up an appropriate user to run the final application and not root. It’s a security bad practice.
# create group and user
RUN groupadd -r tom && useradd -g tom tom
# set ownership and permissions
RUN chown -R tom:tom /app
# switch to user
CMD node index.js
Scan image for vulnerabilities. Use the docker scan from the docker hub system. docker scan image:tag.