Swiftorial Logo
Home
Swift Lessons
Tutorials
Learn More
Career
Resources

Docker Multi-stage Builds

Docker multi-stage builds allow you to optimize the Docker build process by reducing the final image size and improving build performance. This guide covers key concepts, steps to use multi-stage builds, examples, and best practices for deploying Dockerized Express.js applications with multi-stage builds.

Key Concepts of Docker Multi-stage Builds

  • Stages: A Dockerfile can have multiple stages, each with its own FROM instruction.
  • Build Artifacts: Intermediate build artifacts can be created in one stage and used in subsequent stages.
  • Final Image: The final Docker image only contains the necessary files and dependencies from the last stage.

Setting Up the Project

Initialize a new Express.js project and create a Dockerfile:

// Initialize a new project
// npm init -y

// Install Express
// npm install express

// Create the project structure
// mkdir src
// touch src/index.js Dockerfile .dockerignore .gitignore

// .gitignore
node_modules
.env

// .dockerignore
node_modules
npm-debug.log

Creating an Express Application

Create a simple Express application:

Example: index.js

// src/index.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Hello, Docker Multi-stage Builds!');
});

app.listen(port, () => {
    console.log(`Server running at http://localhost:${port}/`);
});

Creating a Dockerfile with Multi-stage Builds

Create a Dockerfile that uses multi-stage builds to optimize the Docker build process:

Example: Dockerfile

// Dockerfile

# Stage 1: Build
FROM node:14-alpine AS builder

# Set working directory
WORKDIR /usr/src/app

# Copy package.json and package-lock.json
COPY package*.json ./

# Install app dependencies
RUN npm install

# Copy the entire source code
COPY . .

# Build the application (if needed)
# RUN npm run build

# Stage 2: Run
FROM node:14-alpine

# Set working directory
WORKDIR /usr/src/app

# Copy only the necessary files from the builder stage
COPY --from=builder /usr/src/app .

# Expose port 3000 to the outside world
EXPOSE 3000

# Run app when the container launches
CMD ["node", "src/index.js"]

Building and Running the Docker Container

Build and run the Docker container for your Express application using the optimized multi-stage build Dockerfile:

// Build the Docker image
docker build -t my-express-app .

// Run the Docker container
docker run -d -p 3000:3000 --name my-express-app my-express-app

// Open http://localhost:3000 in your browser to see the application running

Best Practices for Docker Multi-stage Builds

  • Minimize Layers: Combine multiple RUN instructions into a single instruction to minimize layers.
  • Use Specific Tags: Use specific tags (e.g., node:14-alpine) instead of latest to ensure consistent builds.
  • Optimize Dependencies: Only install necessary dependencies in the final stage to reduce the image size.
  • Clean Up After Installation: Remove unnecessary files and dependencies after installation to reduce the image size.
  • Separate Build and Runtime Dependencies: Use multi-stage builds to separate build dependencies from runtime dependencies.

Testing Docker Multi-stage Builds

Test your Dockerized application built with multi-stage builds to ensure it works correctly:

Example: Testing with Mocha and Chai

// Install Mocha and Chai
// npm install --save-dev mocha chai

// test/app.test.js
const chai = require('chai');
const expect = chai.expect;
const axios = require('axios');

describe('Express App', () => {
    it('should return Hello, Docker Multi-stage Builds!', async () => {
        const response = await axios.get('http://localhost:3000');
        expect(response.data).to.equal('Hello, Docker Multi-stage Builds!');
    });
});

// Add test script to package.json
// "scripts": {
//   "test": "mocha"
// }

// Run tests
// npm test

Key Points

  • Stages: A Dockerfile can have multiple stages, each with its own FROM instruction.
  • Build Artifacts: Intermediate build artifacts can be created in one stage and used in subsequent stages.
  • Final Image: The final Docker image only contains the necessary files and dependencies from the last stage.
  • Follow best practices for Docker multi-stage builds, such as minimizing layers, using specific tags, optimizing dependencies, cleaning up after installation, and separating build and runtime dependencies.

Conclusion

Docker multi-stage builds allow you to optimize the Docker build process by reducing the final image size and improving build performance. By understanding and implementing the key concepts, steps, examples, and best practices covered in this guide, you can effectively deploy Dockerized Express.js applications with multi-stage builds. Happy coding!