User Tools

Site Tools


docker:dockerfile:dockerfile_build_-_best_practices_errors

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.

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.


docker/dockerfile/dockerfile_build_-_best_practices_errors.txt · Last modified: 2025/05/21 12:36 by peter

Donate Powered by PHP Valid HTML5 Valid CSS Driven by DokuWiki