Вы когда-нибудь задумывались, как можно удалить лишние зависимости сборки из вашего образа docker?
Знаете ли вы, что ваш образ можно легко оптимизировать?
В этой статье мы не так много говорим о языке, и чтение всех примеров может быть полезным.
Пример TS
Например, возможно, вы всегда использовали такой Dockerfile для сборки и запуска вашего Typescript проекта:
FROM node:18-alpine3.15
WORKDIR /usr/app
COPY package.json .
RUN npm install --include=dev
COPY tsconfig.json .
COPY src src
RUN ["npm", "run", "build"]
CMD ["npm", "run", "start"]
Просто, но его можно уменьшить, не потеряв ничего важного,
Потому что, в строке
RUN ["npm", "run", "build"]
Мы конвертируем некоторые TS-коды в JS-коды с помощью некоторых инструментов (dev dependencies).
Что произойдет, если мы удалим эти инструменты после преобразования? Ничего!
Посмотрите на картинку ниже, мы построили наше изображение в три этапа, вместо одного, и сэкономили 35% места!
С помощью кода ниже:
FROM node:18-alpine3.15 as ts-compiler
WORKDIR /usr/app
COPY package*.json ./
COPY tsconfig.json ./
RUN npm install --include=dev
COPY src src
# Build the project (TS to JS conversion)
RUN ["npm", "run", "build"]
FROM node:18-alpine3.15 as ts-remover
WORKDIR /usr/app
# We need package.json again
COPY --from=ts-compiler /usr/app/package*.json ./
# Move built codes from last stage here
COPY --from=ts-compiler /usr/app/build ./
# We don't need dev dependencies anymore
RUN npm install --omit=dev
# Using google optimized containers can make it even smaller
FROM gcr.io/distroless/nodejs:18
WORKDIR /usr/app
COPY --from=ts-remover /usr/app ./
USER 1000
CMD ["index.js"]
Будьте осторожны с переменными окружения
Представьте, что вы используете специальный пакет, например, Puppeteer.
При установке Puppeteer загружает браузер chrome, но вы можете отключить его, если хотите, задав ENV.
Взгляните на изменение, которое мы сделали на первом этапе последнего кода:
FROM node:18-alpine3.15 as ts-compiler
WORKDIR /usr/app
COPY package.json .
# This line is added 👇🏻
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN npm install --include=dev
COPY tsconfig.json .
COPY src src
RUN ["npm", "run", "build"]
Если вы снова запустите многоэтапную сборку, вы увидите огромный прирост 😳.
Что происходит?
Дело в том, что вы должны использовать команды ENV и ARG для каждого этапа, мы устанавливаем зависимости дважды за два этапа, поэтому мы должны пропустить установку хрома дважды!
Ничто не говорит больше, чем кусок кода 😄:
FROM node:18-alpine3.15 as ts-compiler
WORKDIR /usr/app
COPY package.json .
# Define ENV once here
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN npm install --include=dev
COPY tsconfig.json .
COPY src src
RUN ["npm", "run", "build"]
FROM node:18-alpine3.15 as ts-remover
WORKDIR /usr/app
COPY --from=ts-compiler /usr/app/package.json .
COPY --from=ts-compiler /usr/app/build .
# Define ENV once again!
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true
RUN npm install --omit=dev
# We don't need that ENV in this stage,
# because we're not installing anything more.
FROM gcr.io/distroless/nodejs:18
WORKDIR /usr/app
COPY --from=ts-remover /usr/app ./
USER 1000
CMD ["index.js"]
Пример Go
Контейнеры Go (и любого другого компилируемого языка) могут быть оптимизированы еще больше. (гораздо больше 😄)
Поскольку время выполнения привязано к ним, вам не нужно устанавливать что-то вроде Nodejs, и вы можете отказаться от самого компилятора на втором этапе и просто использовать бинарный исполняемый файл.
FROM golang:1.18-alpine3.15 AS builder
WORKDIR /app
# Copy go.mod and go.sum first, because of caching reasons.
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
# Compile project
RUN CGO_ENABLED=0 GOOS=linux go build -a -o main .
# Use another clean image without Golang compiler.
FROM alpine:3.15 AS production
COPY --from=builder /app/main .
CMD ["./main"]
Все верно! Мы сэкономили около 96% пространства!
Первый результат — без использования многоступенчатой техники,
Второй — при использовании alpine-3.15 в качестве production.
Третий — с использованием gcr.io/distroless/static-debian11, который является самым маленьким образом из всех существующих.
Включение пользовательского интерфейса
Вы даже можете добавить пользовательский интерфейс в ваш Dockerfile на другом этапе!
В данном случае у меня есть проект Svelte SPA рядом с моим проектом (в папке ui), и я обслуживаю его из Go Backend.
FROM golang:1.18-alpine3.15 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . ./
RUN CGO_ENABLED=0 GOOS=linux go build -a -o main .
# This stage convert svelte project to vanilla HTML, CSS, JS.
FROM node:18-alpine3.15 AS frontend
WORKDIR /ui
COPY ui .
RUN npm install
RUN npm run build
FROM alpine:3.15 AS production
COPY --from=builder /app/main .
COPY --from=frontend /ui/public /ui
CMD ["./main"]
Этот код является частью проекта Blogo.
https://github.com/arshamalh/blogo
Надеюсь, вы узнали что-то новое от меня. 😃✌🏻
Любые предложения или исправления приветствуются.