Skip to main content

Creating Docker Images Using Dockerfile for Beginners

Photo by Bernd 📷 Dittrich on Unsplash


Creating efficient and secure Docker images is critical to optimizing your containerized applications. In this guide we will walk through some of the best practices for creating Docker images using Dockerfiles and techniques for reducing image size while maintaining functionality and security.

Assumptions/Prerequisites

To make the most out of this guide, it is assumed that you have:

  • Basic knowledge of Docker and Dockerfile syntax.
  • Familiarity with Node.js applications, including package.json and npm.
  • Docker installed and set up on your machine.
  • A sample Node.js project to test the examples.

Best Practices

1. Start with a Minimal Base Image

  • Use lightweight base images like node:alpine to reduce image size.
  • For example:
FROM node:alpine

2. Specify an Explicit Version

  • Avoid using latest as the tag for your base image since it can lead to inconsistencies.
  • Instead:
FROM node:18.16.0-alpine

3. Minimize the Number of Layers

  • Combine related commands to reduce the number of layers in the final image.
  • Example of combining commands:
RUN apk add --no-cache curl \
&& npm install --production \
&& npm cache clean --force

4. Use Multi-Stage Builds

  • Multi-stage builds allow you to separate build-time and runtime dependencies, which reduces the final image size.
  • Example:
# Build stage
FROM node:18.16.0 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

# Runtime stage
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json ./
RUN npm install --production
CMD ["node", "dist/index.js"]

5. Avoid Adding Unnecessary Files

  • Use .dockerignore to exclude files that are not needed in the build context.
  • Example .dockerignore:
node_modules
*.log
*.tmp
dist

6. Install Only Required Packages

  • Be precise about the tools and libraries you install to avoid unnecessary bloat.
  • Example:
RUN npm install express dotenv

7. Clean Up Temporary Files

  • Remove cache files and temporary artifacts created during the build.
  • Example:
RUN npm cache clean --force

8. Use COPY Instead of ADD

  • Prefer COPY over ADD unless you need to extract a tar file or download from a URL.
  • Example:
COPY src/ /app/src/

9. Set Metadata Using Labels

  • Add labels for documentation, versioning, and maintainership.
  • Example:
LABEL maintainer="you@example.com" \
version="1.0" \
description="Node.js application"

10. Leverage Caching

  • Order commands in your Dockerfile to maximize caching benefits.
  • Place frequently changing commands (e.g., COPY) towards the end.
  • Example:
RUN npm install
COPY . /app

11. Set a Non-Root User

  • Avoid running your application as root for security reasons.
  • Example:
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
USER appuser

12. Use ENTRYPOINT Over CMD

  • Use ENTRYPOINT for the main application command and CMD for default arguments.
  • Example:
ENTRYPOINT ["node", "dist/index.js"]
CMD ["--help"]

13. Optimize Image Layers

  • Use --squash (experimental feature) to merge layers and reduce image size.
  • Example:
docker build --squash -t myapp:optimized .

14. Scan for Vulnerabilities

  • Use tools like Trivy or Docker’s built-in docker scan to identify security vulnerabilities in your image.
  • Example:
trivy image myapp:latest

Example Dockerfile

Here’s a complete Dockerfile incorporating best practices for a Node.js application:

# Stage 1: Build stage
FROM node:18.16.0 AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

# Stage 2: Runtime stage
FROM node:alpine
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY package.json ./
RUN npm install --production
USER node
CMD ["node", "dist/index.js"]

Reducing Image Size Checklist

  • Use lightweight base images.
  • Remove unnecessary files and cache.
  • Leverage multi-stage builds.
  • Use .dockerignore effectively.
  • Combine commands and minimize layers.

Conclusion

By following these best practices, you can create Docker images that are efficient, secure, and optimized for production environments. These principles not only improve performance but also enhance maintainability and security. Remember, building small and robust images is an essential step in ensuring your containerized applications run smoothly in any environment.

Comments