Table of Contents
Docker - Dockerfile - Dockerfile Build - Best Practices & Errors
Use specific base images
Start with a minimal, specific base image
FROM node:18-alpine
NOTE: Always use specific version tags rather than latest to ensure reproducible builds.
- Alpine-based images are significantly smaller than their Debian/Ubuntu counterparts.
- The more specific your tag, the better for consistency and security updates.
Order instructions by change frequency
Place instructions that change least at the top
FROM node:18-alpine # Tools that rarely change RUN apk add --no-cache python3 make g++ # Dependencies that change occasionally COPY package*.json ./ RUN npm ci # Application code that changes frequently COPY . .
NOTE: The Docker build cache invalidates all subsequent layers when a layer changes.
- By placing more stable instructions at the top, you maximize cache usage and minimize rebuild time.
- This will significantly speed up your development workflow.
Combine related commands
Use && to chain commands and reduce layers.
# Bad practice (creates 3 layers) RUN apt-get update RUN apt-get install -y curl RUN rm -rf /var/lib/apt/lists/* # Good practice (creates 1 layer) RUN apt-get update && \ apt-get install -y curl && \ rm -rf /var/lib/apt/lists/*
NOTE: Each RUN instruction creates a new layer.
- Combining related commands reduces image size and improves build performance.
- Always clean up package manager caches to keep images small.
</file>
Use .dockerignore file
Exclude unnecessary files from the build context.
cat .dockerignore
node_modules
npm-debug.log
Dockerfile
.git
.gitignore
README.md
NOTE: A .dockerignore file prevents the specified files from being sent to the Docker daemon during build.
- This speeds up builds and prevents sensitive files from being included in your image.
Implement multi-stage builds
Separate build and runtime environments.
FROM node:18 AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . RUN npm run build FROM node:18-alpine WORKDIR /app COPY --from=builder /app/dist ./dist COPY --from=builder /app/node_modules ./node_modules COPY package*.json ./ CMD ["npm", "start"]
NOTE: Multi-stage builds lets you use one image for building (with all build tools) and another for running your application.
- This results in significantly smaller production images and improved security by not including build tools in the final image.
In the example above:
- The first stage named builder uses a full Node.js image which includes all build tools.
- We install dependencies and build the application in this first stage.
- The second stage starts fresh with a minimal Alpine-based image.
- Using COPY –from=builder, we selectively copy only the build artifacts and runtime dependencies.
- Everything else from the build stage is discarded, including node_modules with dev dependencies, source code, and build tools.
Multi-stage builds are particularly valuable for compiled languages like Go, Rust, or Java, where the final binary can be copied to a minimal image.
- For example, a Go application might use:
FROM golang:1.20 AS builder WORKDIR /app COPY go.* ./ RUN go mod download COPY . . RUN CGO_ENABLED=0 GOOS=linux go build -o /app/server FROM alpine:3.18 RUN apk --no-cache add ca-certificates COPY --from=builder /app/server /usr/local/bin/ CMD ["server"]
- This approach can reduce image sizes by up to 99% in some cases (from 1GB+ to ~10MB).
- You can even use more than two stages when you need separate phases for testing, security scanning, or generating different artifacts.
Set appropriate user permissions
Avoid running containers as root.
RUN addgroup -S appgroup && adduser -S appuser -G appgroup USER appuser
NOTE: Running containers as root is a security risk.
- Create a non-privileged user and switch to it before running your application.
- This limits the potential damage if the container is compromised.
Use ENTRYPOINT and CMD correctly
Understand their differences.
<file bash> # For applications ENTRYPOINT [“node”, “app.js”] CMD [“–production”]
# For utilities ENTRYPOINT [“aws”] CMD [“–help”] </file?
NOTE: ENTRYPOINT defines the executable that runs when the container starts, while CMD provides default arguments to that executable.
- Using them together makes your containers more flexible and user-friendly.
Diagnose common errors
Understand build failures
docker build -t myapp .
NOTE: Common build errors include:
- Base image not found: Verify the base image exists and you have proper access
- COPY/ADD failures: Ensure source paths exist and are correctly specified
- RUN command failures: Run the commands locally to debug or use docker build –progress=plain for verbose output.
Optimize Docker Build Performance
When working with large applications, consider these additional optimizations.
- Use BuildKit by setting DOCKER_BUILDKIT=1 before your build commands.
- Leverage build caching with –cache-from in CI/CD pipelines.
- For Node.js applications, use npm ci instead of npm install for faster, more reliable builds.
- Consider Docker layer caching services like BuildJet for CI/CD pipelines.
These techniques can reduce build times by up to 80% for complex applications.