Docker Best Practices
Inheritance
Inheritance is when you use FROM some-image:1.2.3
(vs FROM scratch
) in a Dockerfile
. We recommend to leverage lean base images (E.g. alpine
or busybox
).
Try to leverage the same base image in as many of your images as possible for faster docker pulls
.
Multi-stage Builds
There are two ways to leverage multi-stage builds:
- Build-time Environments The most common application of multi-stage builds is for using a build-time environment for compiling apps, and then a minimal image (E.g.
alpine
orscratch
) for distributing the resultant artifacts (e.g. statically-linkedgo
binaries). - Multiple-Inheritance We like to think of "multi-stage builds" as a mechanism for "multiple inheritance" as it relates to docker images. While not technically the same thing, using multi-stage images makes it possible to
COPY --from=other-image
to keep things very DRY.
Use Scratch Base Image
One often overlooked, ultimately lean base-image is the scratch
image. This is an empty filesystem which allows one to copy/distribute the minimal set of artifacts. For languages that can compile statically linked binaries, using the scratch
base image (e.g. FROM scratch
) is the most secure way as there will be no other exploitable packages bundled in the image.
We use this pattern for our terraform-root-modules
distribution of terraform reference architectures.
Configure Cache Storage Backends
When using BuildKit, you should configure a cache storage backend that is suitable for your build environment. Layer caching significantly speeds up builds by reusing layers from previous builds, and is enabled by default as BuildKit has a dedicated local cache. However, in a CI/CD build environment such as GitHub Actions, an external cache storage backend is essential as there is little to no persistence between builds.
Fortunately, Cloud Posse's cloudposse/github-action-docker-build-push action uses gha
(the GitHub Actions Cache) by default. Thus, even without any additional configuration, the action will automatically cache layers between builds.
When using self-hosted GitHub Actions Runners in an AWS environment, however, we recommend using ECR as a remote cache storage backend. Using ECR as the remote cache backend—especially in conjunction with a VPC endpoint for ECR—results in reduced NAT Gateway costs and faster layered cache imports when compared to the GitHub Actions Cache.
The following example demonstrates how to configure the cloudposse/github-action-docker-build-push action to use ECR as the remote cache storage backend:
- name: Build
id: build
uses: cloudposse/github-action-docker-build-push@main
with:
registry: registry.hub.docker.com
organization: "${{ github.event.repository.owner.login }}"
repository: "${{ github.event.repository.name }}"
+ cache-from: "type=registry,ref=registry.hub.docker.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}:cache"
+ cache-to: "mode=max,image-manifest=true,oci-mediatypes=true,type=registry,ref=registry.hub.docker.com/${{ github.event.repository.owner.login }}/${{ github.event.repository.name }}:cache"
For more information with regards to the cache-from
and cache-to
options, please refer to the docker buildx documentation.