What is a multi-stage Docker image?

introduce

Let's start with the basics

Dockers ! It's a great tool that lets you create, deploy, and run applications in these neat little containers. Think of them as tiny virtual machines, but with just enough resources to run your application. This is great because it means you can run your application on any platform, from your laptop to the cloud!

The Power of Docker Images

Docker images are one of the key elements that make Docker so powerful. These are prebuilt, preconfigured packages that include everything your application needs to execute - code, libraries, and dependencies. They're like the ultimate Lego set for developing and distributing apps!

But what if Docker images got better?

This is where multi-stage Docker images come in. They're like the superheroes of the Docker world - able to create smaller, faster, more efficient images by separating build and runtime environments. With multi-stage Docker images, you can save disk space, reduce build times, and even improve security!

The magic of multi-stage Docker images

What is a multi-stage Docker image?

Are you tired of creating bloated Docker images that take forever to build and deploy? Multi-stage Docker images to the rescue! These superheroes of the Docker world let you create smaller, faster, and more secure images by separating build and runtime environments.

Let's say you're building a TypeScript application that uses Node.js as the runtime environment. With traditional Docker images, you have to include everything in the same image, resulting in larger image sizes. However, with multi-stage Docker images, you can create a lean and simple image that contains only what is necessary.

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)"># First stage: build environment</span>
FROM node:14-alpine as build
WORKDIR /app
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install
</span>COPY <span style="color:var(--syntax-text-color)">.</span> <span style="color:var(--syntax-text-color)">.</span>
RUN npm run build

<span style="color:var(--syntax-comment-color)"># Second stage: runtime environment</span>
FROM node:14-alpine
WORKDIR /app
COPY <span style="color:var(--syntax-error-color)">--from</span><span style="color:var(--syntax-error-color)">=</span>build /app/dist ./dist
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install</span> <span style="color:var(--syntax-error-color)">--only</span><span style="color:var(--syntax-error-color)">=</span>production
CMD <span style="color:var(--syntax-error-color)">[</span><span style="color:var(--syntax-string-color)">"npm"</span>, <span style="color:var(--syntax-string-color)">"start"</span><span style="color:var(--syntax-error-color)">]</span>
</code></span></span>

Traditional and multi-stage Docker images

Traditional Docker images use a single stage to build and package an application, meaning that an image includes both a build and a runtime environment. This makes the image larger and less efficient.

On the other hand, multi-stage Docker images allow you to create smaller, more efficient images by separating the build and runtime environments. Each stage in a multi-stage Docker image can have its own base image, dependencies, and build instructions. You can use the COPY command to copy files between stages, the last stage being used to create the final image that will be deployed.

Let's consider the Typescript application example above , which you want to deploy to a serverless platform like AWS Lambda. With a traditional Docker image, you have both the build environment and the runtime environment in a single image, which results in a larger image size. This slows down deployment times and makes the application less responsive. Here is an example Dockerfile for a TypeScript application with a legacy image:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)"># Tradional Docker image</span>
FROM node:14-alpine
WORKDIR /app
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install 
</span>COPY <span style="color:var(--syntax-text-color)">.</span> <span style="color:var(--syntax-text-color)">.</span>
RUN npm run build
CMD <span style="color:var(--syntax-error-color)">[</span><span style="color:var(--syntax-string-color)">"npm"</span>, <span style="color:var(--syntax-string-color)">"start"</span><span style="color:var(--syntax-error-color)">]</span>
</code></span></span>

As you can see, not only do we have all dependencies installed, whether or not they are required for production, but we also have TypeScript code and transpiled JavaScript, resulting in two copies of the same code.

Whereas with multi-stage Docker images, you can keep the size of the Docker image small by separating the build and runtime environments into different stages. Here is an example Dockerfile for a TypeScript application using a multi-stage Docker image:

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code><span style="color:var(--syntax-comment-color)"># Build stage</span>
FROM node:14 AS build
WORKDIR /app
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install
</span>COPY <span style="color:var(--syntax-text-color)">.</span> <span style="color:var(--syntax-text-color)">.</span>
RUN npm run build

<span style="color:var(--syntax-comment-color)"># Runtime stage</span>
FROM node:14-alpine
WORKDIR /app
COPY <span style="color:var(--syntax-error-color)">--from</span><span style="color:var(--syntax-error-color)">=</span>build /app/dist ./dist
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install</span> <span style="color:var(--syntax-error-color)">--only</span><span style="color:var(--syntax-error-color)">=</span>production
CMD <span style="color:var(--syntax-error-color)">[</span> <span style="color:var(--syntax-string-color)">"npm"</span>, <span style="color:var(--syntax-string-color)">"run"</span>, <span style="color:var(--syntax-string-color)">"start"</span> <span style="color:var(--syntax-error-color)">]</span>
</code></span></span>

Demystifying Multi-Stage Docker Images

Multi-stage Docker images work like a well-coordinated team, with each stage performing a specific task. In the first stage, you can use the TypeScript base image and include your source code and build tools. This phase compiles your TypeScript code and creates build artifacts.

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>FROM node:14 AS build
WORKDIR /app
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install
</span>COPY <span style="color:var(--syntax-text-color)">.</span> <span style="color:var(--syntax-text-color)">.</span>
RUN npm run build
</code></span></span>

In the next stage, you can use the Node.js base image and copy only the runtime dependencies needed to run the application from the previous stage. By separating the runtime environments, you can create smaller, more efficient Docker images.

<span style="color:var(--syntax-text-color)"><span style="color:var(--syntax-text-color)"><code>FROM node:14-alpine
WORKDIR /app
COPY <span style="color:var(--syntax-error-color)">--from</span><span style="color:var(--syntax-error-color)">=</span>build /app/dist ./dist
COPY package<span style="color:var(--syntax-declaration-color)">*</span>.json ./
RUN npm <span style="color:var(--syntax-text-color)">install</span> <span style="color:var(--syntax-error-color)">--only</span><span style="color:var(--syntax-error-color)">=</span>production
CMD <span style="color:var(--syntax-error-color)">[</span> <span style="color:var(--syntax-string-color)">"npm"</span>, <span style="color:var(--syntax-string-color)">"run"</span>, <span style="color:var(--syntax-string-color)">"start"</span> <span style="color:var(--syntax-error-color)">]</span>
</code></span></span>

The final stage creates a final image ready for deployment. The best part? With this approach, you can significantly reduce the size of your Docker images, making it faster and easier to deploy TypeScript applications.

in conclusion

Benefits of multi-stage Docker images

They have many advantages, including smaller image sizes, faster builds, reduced attack surface, and improved maintainability. By separating your build and runtime environments, you can create leaner, more efficient Docker images that are ideal for containerizing your applications.

final thoughts

Multi-stage Docker images are especially useful for applications with complex build processes or a large number of dependencies, such as TypeScript applications. They also excel when deployed to serverless platforms like AWS Lambda or Kubernetes clusters for easy and fast management.

Guess you like

Origin blog.csdn.net/jascl/article/details/131325637