Skip to main content

构建 Docker 映像

本节介绍如何将后端应用程序构建为可部署的 Docker 镜像。 本节分为三部分,首先介绍主机构建方法,推荐使用这种方法,因为它速度快,缓存更高效,通常也更简单。 第二部分介绍完整的多阶段 Docker 构建,最后一部分介绍如何将前端和后端作为单独的镜像进行部署。

所有这些 docker 部署策略都是无状态的,因此在生产部署中,你需要设置并连接到一个外部 PostgreSQL 实例,让后端插件可以存储它们的状态,而不是使用 SQLite。

本节假定应用已经用@backstage/create-app其中,前端由后端(bundle and served from the backend)提供。@backstage/plugin-app-backend这意味着在最小化的Backstage设置中,您只需构建和部署一个容器。 如果您希望将前端服务与后端分开,请参阅独立前端下面的主题。

主机构建

本节介绍如何从 backstage repo 构建 Docker 镜像,其中大部分构建工作都是在 Docker 外部完成的。 这几乎总是更快的方法,因为构建步骤的执行速度往往更快,而且可以在主机上更有效地缓存依赖项,单个更改不会破坏整个缓存。

主机构建的必要步骤是使用yarn install生成类型定义yarn tsc,并用以下命令构建后端软件包yarn build:backend.

在 CI 工作流程中,从根本上看可能是这样的:

yarn install --frozen-lockfile

# tsc outputs type definitions to dist-types/ in the repo root, which are then consumed by the build
yarn tsc

# Build the backend, which bundles it all up into the packages/backend/dist folder.
# The configuration files here should match the one you use inside the Dockerfile below.
yarn build:backend --config ../../app-config.yaml

主机构建完成后,我们就可以构建映像了。 下面是Dockerfile创建新应用程序时,会包含@backstage/create-app:

FROM node:18-bookworm-slim

# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends python3 g++ build-essential && \
yarn config set python /usr/bin/python3

# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends libsqlite3-dev

# From here on we use the least-privileged `node` user to run the backend.
USER node

# This should create the app dir as `node`.
# If it is instead created as `root` then the `tar` command below will
# fail: `can't create directory 'packages/': Permission denied`.
# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
# so the app dir is correctly created as `node`.
WORKDIR /app

# This switches many Node.js dependencies to production mode.
ENV NODE_ENV production

# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
# The skeleton contains the package.json of each package in the monorepo,
# and along with yarn.lock and the root package.json, that's enough to run yarn install.
COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz

RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
yarn install --frozen-lockfile --production --network-timeout 300000

# Then copy the rest of the backend bundle, along with any other files we might want.
COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
RUN tar xzf bundle.tar.gz && rm bundle.tar.gz

CMD ["node", "packages/backend", "--config", "app-config.yaml"]

欲了解更多有关backend:bundle命令和skeleton.tar.gz文件的工作原理,请参见后端:bundle.

Dockerfile位于packages/backend/Dockerfile但需要以软件源的根作为构建上下文来执行,这样才能访问根目录yarn.lockpackage.json以及其他可能需要的文件,例如.npmrc.

@backstage/create-app命令添加以下内容.dockerignore在版本库的根目录中,以减少构建上下文的大小,从而加快构建速度:

.git
.yarn/cache
.yarn/install-state.gz
node_modules
packages/*/src
packages/*/node_modules
plugins
*.local.yaml

项目建成后.dockerignoreDockerfile现在,我们可以构建最终镜像了。 从 repo 的根目录执行构建:

docker image build . -f packages/backend/Dockerfile --tag backstage

要在本地试用图像,可以运行以下程序:

docker run -it -p 7007:7007 backstage

然后,您的终端应该会开始收到日志,然后您就可以在以下位置打开浏览器http://localhost:7007

多阶段构建

注意:在这个设置中,.dockerignore 是不同的,详情请继续阅读。

本节介绍如何设置多阶段 Docker 构建,在 Docker 中构建整个项目。 这通常比主机构建慢,但有时因为 Docker 中的 Docker 无法在构建环境中使用,或由于其他要求,也需要这样做。

构建过程分为三个不同阶段,第一阶段是查找所有的package.json与初始安装步骤相关的文件,使我们能够缓存初始yarn install第二阶段执行构建本身,类似于我们在主机上执行主机构建的步骤。 第三阶段,也是最后一个阶段,将所有内容打包成最终镜像,类似于Dockerfile的主机构建。

以下是Dockerfile执行多阶段构建,并应添加到 repo 根目录:

# Stage 1 - Create yarn install skeleton layer
FROM node:18-bookworm-slim AS packages

WORKDIR /app
COPY package.json yarn.lock ./

COPY packages packages

# Comment this out if you don't have any internal plugins
COPY plugins plugins

RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+

# Stage 2 - Install dependencies and build packages
FROM node:18-bookworm-slim AS build

# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends python3 g++ build-essential && \
yarn config set python /usr/bin/python3

# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends libsqlite3-dev

USER node
WORKDIR /app

COPY --from=packages --chown=node:node /app .

RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
yarn install --frozen-lockfile --network-timeout 600000

COPY --chown=node:node . .

RUN yarn tsc
RUN yarn --cwd packages/backend build
# If you have not yet migrated to package roles, use the following command instead:
# RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies

RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \
&& tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \
&& tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle

# Stage 3 - Build the actual backend image and install production dependencies
FROM node:18-bookworm-slim

# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends python3 g++ build-essential && \
yarn config set python /usr/bin/python3

# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
--mount=type=cache,target=/var/lib/apt,sharing=locked \
apt-get update && \
apt-get install -y --no-install-recommends libsqlite3-dev

# From here on we use the least-privileged `node` user to run the backend.
USER node

# This should create the app dir as `node`.
# If it is instead created as `root` then the `tar` command below will
# fail: `can't create directory 'packages/': Permission denied`.
# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
# so the app dir is correctly created as `node`.
WORKDIR /app

# Copy the install dependencies from the build stage and context
COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./

RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
yarn install --frozen-lockfile --production --network-timeout 600000

# Copy the built packages from the build stage
COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./

# Copy any other files that we need at runtime
COPY --chown=node:node app-config.yaml ./

# This switches many Node.js dependencies to production mode.
ENV NODE_ENV production

CMD ["node", "packages/backend", "--config", "app-config.yaml"]

请注意,新创建的 Backstage 应用程序通常没有plugins/文件夹,所以你需要注释掉这一行。 这种构建方法在主软件包中也不起作用,因为backstage-cli最终没有正确安装。

为加快不在新克隆版本中运行时的编译速度,你应该设置一个.dockerignore这与主机构建不同,因为我们希望获取所有软件包的源代码用于构建。 不过,我们可以忽略主机上任何现有的构建输出或依赖关系。 对于我们新的.dockerignore用以下内容替换现有内容:

dist-types
node_modules
packages/*/dist
packages/*/node_modules
plugins/*/dist
plugins/*/node_modules

同时添加Dockerfile.dockerignore到项目根目录,然后运行以下命令在指定标记下构建容器。

docker image build -t backstage .

要在本地试用图像,可以运行以下程序:

docker run -it -p 7007:7007 backstage

然后,您的终端应该会开始收到日志,然后您就可以在以下位置打开浏览器http://localhost:7007

独立前台

注意:这是一个可选步骤,你将失去 @backstage/plugin-app-backend插件的功能。 最值得注意的是,前端配置 > 将不再由后端(backend)注入,你需要在构建前端捆绑包时使用 > 正确的配置。

有时需要将前端与后端分开,或者使用单独的镜像,或者使用静态文件服务提供商。 这样做的第一步是移除app-backend后端软件包中的插件,具体操作如下:

  1. Delete packages/backend/src/plugins/app.ts 2. Remove the following lines from packages/backend/src/index.ts: tsx import app from './plugins/app'; // ... const appEnv = useHotMemoize(module, () => createEnv('app')); // ... .addRouter('', await app(appEnv)); 3. Remove the @backstage/plugin-app-backend and the app package dependency (e.g. app) from packages/backend/package.json. If you don't remove the app package dependency the app will still be built and bundled with the backend.

一旦app-backend从后端移除后,就可以使用自己喜欢的静态文件服务方法为前端提供服务了。 有关如何设置 NGINX 映像的示例,请参见主软件仓库中的

请注意,如果你正在为前端构建一个单独的 docker 版本,你可能需要调整.dockerignore很可能是通过确保packages/app/dist不会被忽略。

故障排除技巧

在构建 Docker 镜像时,你可能时不时会遇到一些问题,这时有两个方便的标记可以帮助你:

  • --progress=plain: 这将提供更详细的输出,并且不会将日志折叠成不同的部分。 当出现错误时,这将非常有用,但它只会显示最后一条命令和可能的退出代码。 使用此标志,你更有可能看到错误的实际位置。 --no-cache: 这将每次重建所有层。 当你想确保它是从头开始构建时,这将非常有用。

下面是这些标记的使用示例:

docker image build . -f packages/backend/Dockerfile --tag backstage --progress=plain --no-cache