Multi-stage build với Docker
Trong quá trình build một image với Docker. Việc phải giữ cho image luôn nhỏ nhất có thể là điều hết sức quan trọng. Bởi vì image sau đó sẽ được dùng để deploy, nên image càng nhẹ thì việc deploy sẽ càng diễn ra nhanh chóng, ít phải đợi chờ hơn.
1. Multi-stage build là gì:
Multi-stage build là một tính năng mới được thêm vào Docker (từ version 17.05 trở đi). Nó là phương thức để chia quá trình build image thành nhiều giai đoạn nhằm giảm kích thước image, giúp việc quản lý CICD dễ dàng hơn.
2. Ví dụ cụ thể:
Giả sử như ta có 1 trang web chạy bằng golang. Bên trong template sẽ kết hợp thêm các module vuejs.
Để làm được việc đó ta sẽ cần đến hai nhiệm vụ thực hiện bởi hai Docker container.
- Build vuejs bằng webpack với một base image từ node
// Dockerfile for vuejs
FROM node:14.0.0-alpine3.10
COPY webpack /webpack
WORKDIR /webpack
RUN npm install
RUN npm run build
- Build app golang với một base image từ golang, bên cạnh đó cũng copy luôn file được build từ webpack ở phía trên.
# Dockerfile for golang
FROM golang:1.13
COPY beego /go/src/goapp
COPY vuejs/build.js /go/src/backend/static/js/build.js
WORKDIR /go/src/goapp
RUN go mod vendor
RUN go build goapp.go
CMD ./goapp
Với cách làm như trên thì image được build từ node sẽ trở nên không thật sự cần thiết do cuối cùng thì chúng ta cũng chỉ cần đến file build.js để đem vào image của app golang thôi. Bên cạnh đó, nó còn mang bên mình khối node_modules nặng nề nữa, nên sau khi build xong thì tốt nhất là phải xóa image này đi.
3. Cách giải quyết:
Nếu sử dụng multi-stage build thì mọi việc sẽ dễ dàng hơn.
# Giai đoạn 1: Build vuejs bằng webpack
FROM node:14.0.0-alpine3.10 AS webpack-builder
COPY webpack /webpack
WORKDIR /webpack
RUN npm install
RUN npm run build
# Giai đoạn 2: Build application
FROM golang:1.13
RUN apt-get -q update && apt-get -qy install netcat
COPY beego /go/src/goapp
COPY --from=webpack-builder /webpack/dist/build.js /go/src/goapp/static/js/build.js
COPY ops/bin/wait-for.sh /usr/bin/wait-for.sh
RUN chmod +x /usr/bin/wait-for.sh
WORKDIR /go/src/goapp
RUN go mod vendor
RUN go build main.go
CMD /usr/bin/wait-for.sh backend-mysql:3306 -- ./goapp
Quá trình build image sẽ được chia làm hai giai đoạn. Giai đoạn đầu sẽ được đặt tên là webpack-builder :FROM node:14.0.0-alpine3.10 AS webpack-builder
.
Ở giai đoạn 2 ta có đoạn code sau:
COPY --from=webpack-builder /webpack/dist/build.js /go/src/goapp/static/js/build.js
Đoạn code này sẽ giúp copy file build được từ webpack-builder
(ở đây mình đặt tên là build.js
, được setting trong webpack.config.js
) vào folder /go/src/goapp/static/js
của app golang.
4. Tổng kết:
Với multi-stage build, ta chỉ cần một Dockerfile duy nhất. Tất cả những thứ không còn cần thiết được sinh ra trong quá trình build webpack (như node_modules, package-lock v.v...) sẽ được xóa đi, chỉ giữ lại những gì cần thiết cho image cuối, như ở ví dụ trên là file build.js
.
Bên cạnh đó, quá trình build cũng được chia làm nhiều stage, giúp việc quản lý dễ dàng hơn, code sẽ dễ đọc và dễ bảo trì.