Multi-stage Dockerfile for React + Vite + TS
Problems
เราได้ build react เรียบร้อย มี docker image แล้ว สามารถรัน docker image เป็น docker container ได้แล้ว
เราจะมาลอง sh เข้าไปดูภายใน container กัน
รันก่อน
❯ docker run -p 3001:3001 --name react -d react
7fbf3951b9f39f9ff72695bc1f9e48d14575bae4bee7434054ccb6cce9792b38แล้วสั่ง exec เข้าไปที่ container
docker exec -it react /bin/bashแล้วสั่ง ls -al เพื่อดูรายการไฟล์ที่มี
❯ docker exec -it react /bin/bash
root@7fbf3951b9f3:/app# ls -al
total 156
drwxr-xr-x 1 root root 32 Jun 30 05:15 .
drwxr-xr-x 1 root root 14 Jun 30 05:27 ..
drwxr-xr-x 1 root root 66 Jun 30 05:15 .direnv
-rw-r--r-- 1 root root 253 Jun 30 05:12 .dockerignore
-rw-r--r-- 1 root root 13 Jun 30 03:25 .envrc
-rw-r--r-- 1 root root 436 Jun 30 03:25 .eslintrc.cjs
-rw-r--r-- 1 root root 253 Jun 30 03:25 .gitignore
-rw-r--r-- 1 root root 221 Jun 30 04:43 Dockerfile
-rw-r--r-- 1 root root 1300 Jun 30 03:25 README.md
-rwxrwxrwx 1 root root 94823 Jun 30 03:33 bun.lockb
drwxr-xr-x 1 root root 48 Jun 30 05:15 dist
-rw-r--r-- 1 root root 1058 Jun 30 03:25 flake.lock
-rw-r--r-- 1 root root 1249 Jun 30 03:25 flake.nix
-rw-r--r-- 1 root root 138 Jun 30 03:25 global.d.ts
-rw-r--r-- 1 root root 366 Jun 30 03:25 index.html
drwxr-xr-x 1 root root 8 Jun 30 05:15 node_modules
-rw-r--r-- 1 root root 743 Jun 30 03:25 package.json
drwxr-xr-x 1 root root 16 Jun 30 03:25 public
drwxr-xr-x 1 root root 100 Jun 30 03:25 src
-rw-r--r-- 1 root root 677 Jun 30 03:25 tsconfig.app.json
-rw-r--r-- 1 root root 139 Jun 30 03:25 tsconfig.json
-rw-r--r-- 1 root root 325 Jun 30 03:25 tsconfig.node.json
-rw-r--r-- 1 root root 163 Jun 30 03:25 vite.config.tsจะเห็นว่ามี ไฟล์ที่เราไม่ได้ใช้ใน production เต็มไปหมดเลย ไฟล์ที่เกินมาเหล่านี้ทำให้ docker image ของเรามีขนาดใหญ่ด้วย
เราจะแก้ปัญหานี้ด้วยการทำ Multi-stage นั่นเอง
Multi-stage Dockerfile
มาดูตัวอย่างกันเลย
# Build
FROM oven/bun:alpine as build
WORKDIR /app
COPY package.json .
COPY bun.lockb .
RUN bun install
COPY . .
RUN bun run build
# Final
FROM oven/bun:alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY ./public ./public
RUN bun install --global serve
ENV NODE_ENV production
CMD ["bun","x","serve","-l","3001","./dist"]
EXPOSE 3001จากตัวอย่างจะแบ่งเป็น 2 stages
- build
- final
1. Build stage
FROM oven/bun:alpine as buildอันนี้คือเปลี่ยนมาใช้ bun image ที่มีขนาดเล็กลง ถ้าจะให้เล็กกว่านี้ก็ต้องใช้ distroless ละ ส่วนas buildคือกำหนดชื่อของ stage นี้ ให้ชื่อว่าbuildส่วนนี้จะจบแค่
RUN bun run buildสุดท้ายเราจะได้ folderdistมา และจบ stage แค่นี้เลย
2. Final stage
ที่ stage นี้เราจะ copy file ที่จำเป็นในการรัน React app มาใช้
COPY --from=build /app/dist ./distจะเห็นว่ามีการเพิ่ม flag--fromมาด้วย เป็นการบอกว่าจะให้ copy file จาก stagebuildมาใช้ที่ stage นี้ ซึ่งจะไป copy ที่/app/distก็คือไปที่ WORKDIR ใน stage build แล้ว copy folderdistมาไว้ที่./สุดท้ายเราจะได้./distเหมือนเดิม
build docker image
docker build -t react .❯ docker build -t react .
[+] Building 7.1s (16/16) FINISHED docker:orbstack
=> [internal] load build definition from Dockerfile 0.1s
=> => transferring dockerfile: 394B 0.0s
=> [internal] load metadata for docker.io/oven/bun:alpin 2.0s
=> [auth] oven/bun:pull token for registry-1.docker.io 0.0s
=> [internal] load .dockerignore 0.1s
=> => transferring context: 295B 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 1.39kB 0.0s
=> [build 1/7] FROM docker.io/oven/bun:alpine@sha256:fb5 0.0s
=> CACHED [build 2/7] WORKDIR /app 0.0s
=> CACHED [build 3/7] COPY package.json . 0.0s
=> CACHED [build 4/7] COPY bun.lockb . 0.0s
=> CACHED [build 5/7] RUN bun install 0.0s
=> [build 6/7] COPY . . 0.2s
=> [build 7/7] RUN bun run build 1.7s
=> CACHED [stage-1 3/5] COPY --from=build /app/dist ./di 0.0s
=> [stage-1 4/5] COPY ./public ./public 0.1s
=> [stage-1 5/5] RUN bun install --global serve 2.4s
=> exporting to image 0.2s
=> => exporting layers 0.1s
=> => writing image sha256:d5ac5bab7d45f1b1fa5a74d44adb9 0.0s
=> => naming to docker.io/library/react 0.0sRun container
docker run -p 3001:3001 --name react -d react❯ docker run -p 3001:3001 --name react -d react
1a2731d0fcc7bf4dbb41956577c6db0b3874f55582091b60e4f380e210e72aaaExec
ลอง sh เข้ามาดูก็จะเห็นว่า มี ไฟล์นิดเดียว เท่าที่จำเป็นต้องใช้
docker exec -it react /bin/sh❯ docker exec -it react /bin/sh
/app # ls
dist public
/app # ls -al
total 0
drwxr-xr-x 1 root root 12 Jun 30 07:53 .
drwxr-xr-x 1 root root 14 Jun 30 07:55 ..
drwxr-xr-x 1 root root 48 Jun 30 07:46 dist
drwxr-xr-x 1 root root 16 Jun 30 03:25 public