Docker has evolved significantly since its introduction, with each release bringing new features and improvements to the Dockerfile specification. Some of these changes are revolutionary, making builds faster, more secure, and easier to maintain. This article explores seven of the most impactful recent Dockerfile features you should consider adopting in your containerization workflow.
Whether you're a Docker veteran or just starting your containerization journey, these features can significantly improve your build process and resulting images. Let's dive in!
1. BuildKit Mounts
v18.09+BuildKit introduced a powerful mounting system that allows you to bind, cache, temporary storage, and even secrets during your build process. This is a game-changer for performance and security.
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# syntax=docker/dockerfile:1.4
FROM node:16-alpine
WORKDIR /app
COPY package*.json ./
RUN --mount=type=cache,target=/root/.npm \
npm install
COPY . .
RUN --mount=type=cache,target=/root/.npm \
npm run build
Four types of mounts are supported:
- Cache mounts: Persist cache between builds, like package manager caches
- Bind mounts: Share content from other stages or the build context
- Tmpfs mounts: Use memory-based temporary storage
- Secret mounts: Securely access sensitive information during build
The most common use case is caching package manager files to speed up builds dramatically. The cache persists between builds but isn't included in the final image, keeping it lean.
2. Multi-stage Builds with External Images as Base
v17.05+Multi-stage builds have been a staple of efficient Dockerfiles since their introduction, but a recent improvement allows you to use external images as stages, making your builds even more flexible.
# Define a local build stage
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# Use an external image with a specific digest as a stage
FROM alpine:3.16.2@sha256:1304f174557314a7ed9eddb4eab12fed18eb1a1a3588322288286dc2c449f835 AS base
RUN apk add --no-cache ca-certificates tzdata
# Copy from the local build stage to the final image
FROM base
COPY --from=builder /app/myapp /usr/local/bin/
CMD ["myapp"]
This approach offers several benefits:
- Enhanced security by using image digests instead of tags
- Better modularity by referencing external images directly
- Simplified CI/CD pipelines by leveraging pre-built components
This feature is perfect for creating a consistent base environment across multiple projects or when you want to leverage pre-built components from other teams or public images.
3. Heredoc Syntax Support
v20.10+Gone are the days of awkward multi-line RUN commands with backslashes. BuildKit now supports "heredoc" syntax, making complex multi-line scripts much more readable in your Dockerfile.
FROM ubuntu:22.04
RUN apt-get update && \
apt-get install -y python3 python3-pip && \
pip3 install requests && \
rm -rf /var/lib/apt/lists/* && \
mkdir -p /app/data
# syntax=docker/dockerfile:1.4
FROM ubuntu:22.04
RUN <
This syntax is especially useful for:
- Complex installation scripts
- Creating configuration files inline
- Running multi-line commands
You can also use heredoc to insert files directly in your Dockerfile:
# syntax=docker/dockerfile:1.4
FROM nginx:alpine
RUN < /etc/nginx/conf.d/default.conf
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
EOF
This makes your Dockerfile more self-contained and easier to understand, as configuration files can be included directly in the Dockerfile rather than as separate files in your build context.
4. BuildKit's --mount=type=secret
v18.09+One of the most significant security improvements in modern Docker builds is the ability to use secrets during build time without embedding them in the final image.
# syntax=docker/dockerfile:1.4
FROM alpine
RUN --mount=type=secret,id=mysecret,dst=/root/.secret \
cat /root/.secret
And then build the image with:
$ echo "MY_SECRET_VALUE" > mysecret.txt
$ DOCKER_BUILDKIT=1 docker build --secret id=mysecret,src=mysecret.txt .
This solves a critical security issue in Docker builds. Previously, developers had to choose between:
- Hard-coding secrets in Dockerfiles (extremely insecure)
- Using build arguments (which remain visible in image history)
- Complex multi-stage build patterns
With secret mounts, you can securely use:
- API keys for downloading resources
- Private repository credentials
- Certificates and keys
- Any sensitive data needed during build but not at runtime
Security Note
Secret mounts are only available during build time and do not persist in the resulting image layers, ensuring your secrets don't leak into your production images.
5. ARG Before FROM
v17.05+A seemingly small but highly useful feature is the ability to use ARG instructions before FROM in your Dockerfile, which allows you to parameterize base images.
# Use ARG before FROM to define base image parameters
ARG NODE_VERSION=16
ARG ALPINE_VERSION=3.16
# Use the ARG values in the FROM instruction
FROM node:${NODE_VERSION}-alpine${ALPINE_VERSION}
# The rest of your Dockerfile
WORKDIR /app
COPY . .
RUN npm install
CMD ["npm", "start"]
This feature enables several powerful use cases:
- Create matrix builds with different base images
- Centralize version management
- Simplify CI/CD pipelines with parameterized builds
- Test application compatibility across different runtime versions
You can override these arguments at build time:
$ docker build --build-arg NODE_VERSION=18 --build-arg ALPINE_VERSION=3.17 .
This approach is excellent for maintaining a single Dockerfile that can adapt to different environments or testing requirements, significantly reducing duplication in your containerization code.
6. Improved COPY with --link Flag
v23.0+BuildKit introduced the --link flag for COPY commands, which enhances layer caching and makes your builds faster by tracking content changes more efficiently.
# syntax=docker/dockerfile:1.4
FROM node:18-alpine AS builder
WORKDIR /app
COPY --link package*.json ./
RUN npm ci
COPY --link . .
RUN npm run build
FROM nginx:alpine
COPY --link --from=builder /app/dist /usr/share/nginx/html
The --link flag changes how BuildKit tracks dependencies and can lead to more efficient caching. Rather than invalidating cache based on metadata changes (like file timestamps), it focuses on actual content changes.
Benefits include:
- More reliable cache hits, even when file metadata changes
- Improved performance when using --from in multi-stage builds
- Better parallelization of build steps
This feature is particularly valuable in CI/CD environments where you want consistent, predictable build behavior regardless of how or when files were updated.
7. RUN --network and Network Control
v19.03+BuildKit provides granular control over network access during build steps with the --network flag for RUN commands, helping to create more secure and deterministic builds.
# syntax=docker/dockerfile:1.4
FROM python:3.10-slim
# No network access for this step
RUN --network=none mkdir -p /app/src
# Default network for this step
COPY requirements.txt .
RUN --network=default pip install -r requirements.txt
# No network for security-sensitive operations
RUN --network=none adduser --disabled-password --gecos '' appuser
COPY . .
USER appuser
CMD ["python", "app.py"]
The --network flag accepts several values:
- default: Standard Docker bridge network access
- none: No network access (isolated)
- host: Use the host network (careful, security implications)
- custom-network-name: A specific Docker network
This feature offers important benefits:
- Enhanced security by isolating build steps that don't need network access
- Improved build determinism by controlling network dependencies
- Better error detection for hidden network dependencies
- Ability to connect to custom networks for specialized build requirements
Best Practice
Consider using --network=none
as the default for all build steps, and only enable network access for steps that specifically require it. This follows the principle of least privilege and makes your builds more secure.
Compatibility and Adoption Considerations
Before implementing these features in your production Dockerfiles, it's important to consider compatibility with your current Docker environment.
Feature | Requires BuildKit | Min Docker Version | CI/CD Compatibility |
---|---|---|---|
BuildKit Mounts | 18.09+ | Good; widely supported | |
External Images as Base | 17.05+ | Excellent; works everywhere | |
Heredoc Syntax | 20.10+ | Good; requires BuildKit | |
Secret Mounts | 18.09+ | Good; special setup needed in some CI systems | |
ARG Before FROM | 17.05+ | Excellent; works everywhere | |
COPY with --link | 23.0+ | Limited; requires recent Docker | |
RUN --network | 19.03+ | Good; requires BuildKit |
To enable BuildKit in your environment (required for many of these features):
# Enable BuildKit for a single build
DOCKER_BUILDKIT=1 docker build .
# Enable BuildKit by default in Docker daemon
# Edit /etc/docker/daemon.json:
{
"features": {
"buildkit": true
}
}
Conclusion
The Docker ecosystem continues to evolve rapidly, with BuildKit bringing significant improvements to the containerization workflow. By adopting these modern Dockerfile features, you can create more efficient, secure, and maintainable container images.
When implementing these features, consider:
- Start with backward-compatible changes if you're working in a team
- Update your CI/CD pipelines to enable BuildKit
- Document your usage of advanced features for team awareness
- Consider creating a custom Dockerfile syntax version header to clearly indicate which features you're using
Remember that while these features improve your Dockerfile, good containerization practices remain essential: keep your images small, minimize layer count, and follow the principle of least privilege.
Pro Tip
If your team uses different Docker versions, consider maintaining a "classic" and "modern" version of critical Dockerfiles. The classic version can serve as a fallback for environments without BuildKit support.
Comments
Comments system placeholder. In a real implementation, this would be integrated with a third-party comments system or custom solution.