Multi-Stage Docker Build

Multi-Stage Docker Build

Day 3 of #40DaysOfKubernetes

Introduction: Welcome to Day 3 of #40DaysOfKubernetes! Today, we dive deep into Docker multistage builds, exploring their benefits, best practices for Dockerfile writing, and essential Docker commands. Let’s walk through the process of dockerizing an application using multistage builds and understand why it’s crucial for efficient container management.

Cloning a GitHub Repository

To kickstart our exploration into Docker multistage builds, we began by cloning a GitHub repository containing an application ideal for showcasing this technique. For our demonstration, we used the repository named todoapp-docker:

git clone https://github.com/piyushsachdeva/todoapp-docker.git
cd todoapp-docker/

Docker Multistage Build

Docker multistage builds are a powerful feature designed to produce smaller and more efficient Docker images by using multiple FROM statements within a single Dockerfile. Here’s how we set up our Dockerfile using multistage builds for our example application:

# Stage 1: Build environment
FROM node:18-alpine AS installer
WORKDIR /app
COPY package*.json ./
RUN npm install 
COPY . .
RUN npm run build

# Stage 2: Runtime environment
FROM nginx:latest AS deployer
COPY --from=installer /app/build /usr/share/nginx/html

Explanation:

  • Stage 1 (Build):

    • Uses node:18-alpine as the base image.

    • Sets the working directory to /app.

    • Copies package.json and package-lock.json files and installs dependencies using npm install.

    • Copies the application code.

    • Executes the build command (npm run build) to compile the application.

  • Stage 2 (Nginx):

    • Utilizes nginx:latest as the base image, a lightweight version of Nginx.

    • Copies the built files from Stage 1 (/app/build) into the Nginx web server’s default document root (/usr/share/nginx/html).

    • Exposes port 80, the default port for HTTP traffic.

    • Sets the command to start Nginx in the foreground (nginx -g 'daemon off;').

This approach separates the build environment (Node.js) from the runtime environment (Nginx), resulting in a compact and secure final image.

Benefits of Docker Multistage Builds

Docker multistage builds offer several advantages:

  1. Reduced Image Size: By using separate build stages, you eliminate the need to include build-time dependencies and artifacts in the final runtime image. This results in smaller Docker images, reducing storage and network transfer requirements.

  2. Improved Security: Separating build and runtime environments minimizes the attack surface of the final image. Build dependencies that are necessary for compilation or testing are not present in the runtime image, reducing potential vulnerabilities.

  3. Enhanced Build Speed: Docker leverages caching between build stages. This means that if a previous build stage has already been executed and its output hasn't changed, Docker can reuse the cached intermediate image, speeding up subsequent builds.

Reviewing Dockerfile Best Practices

To ensure we followed best practices for Docker image creation, we reviewed the Docker documentation for guidance on writing efficient Dockerfiles. You can explore Docker's best practices documentation https://docs.docker.com/build/building/best-practices/

Key best practices include:

  • Avoiding Unnecessary Packages: Installing only essential packages minimizes image size and potential vulnerabilities.

  • Sorting Multi-line Arguments: Structuring Dockerfile commands optimizes build times and readability.

  • Pinning Base Image Versions: Specifying exact versions for base images ensures consistency across environments.

  • Regularly Rebuilding Images: Keeping Docker images updated with security patches enhances security and reliability.

Implementing these practices enhances efficiency and security while simplifying development and deployment workflows.

Exploring Docker Commands: During the dockerization process, we utilized essential Docker commands:

  • Removing Images: Clean up disk space by removing unused Docker images:

      docker image rm <image_id>
    
  • Viewing Logs: Monitor container activity and troubleshoot issues using Docker logs:

      docker logs <container_id>
    

  • Inspecting Docker Objects: Gain detailed information about Docker objects such as containers using docker inspect:

      docker inspect <container_or_image_id>
    

  • Executing Commands: Access running containers and execute commands within them using docker exec:

      docker exec -it <container_id> sh
    

Conclusion: Mastering Docker Multistage Builds

In conclusion, Day 3 of #40DaysOfKubernetes has empowered us with essential skills in Docker multistage builds, crucial Docker commands, and best practices for optimizing Dockerfiles. These learnings have not only streamlined our application deployments but also fortified their security and efficiency. As we continue our journey through Kubernetes, we are poised to excel in cloud-native environments by leveraging these powerful tools and practices.

Stay tuned for more insights and advancements in our Kubernetes mastery!

For fourther reference: https://youtu.be/ajetvJmBvFo?si=LpsH5lbNVMqOfdVR