BackstageのDockerイメージをビルドしてみる

2024.04.10
2024.04.10
コンテナ
BackstageDockerfileDocker Compose

はじめに

BackstageのDockerイメージをビルドする方法について紹介していきたいと思います。

Backstageについては、以下の記事を参照してください。

【CNCF】Backstage触ってみた(Software Catalog)

【CNCF】Backstage触ってみた(Software Catalog)

はじめに CNCF プロジェクトの一つである、Backstage についてざっくり紹介して、メイ

BackstageのDockerイメージ

BackendのDockerイメージのビルド方法は大きく分けて以下の3つが用意されています。

  • Docker外でアプリをビルドしてからイメージをビルド
  • Dockerでマルチステージビルド
  • フロントエンドとバックエンドを分けてビルド
Building a Docker image | Backstage Software Catalog and Developer Platform

Building a Docker image | Backstage Software Catalog and Developer Platform

How to build a Backstage Docker image for deployment

@backstage/create-appで作成したプロジェクトの場合、デフォルトでは@backstage/plugin-app-backendというプラグインによってフロントエンドがバックエンドに統合されているため、生成されるイメージは1つになり、1つのコンテナでフロントエンドとバックエンドが動作するようになっています。

紹介する3つの方法は、@backstage/create-appで作成したプロジェクトを想定しています。

1npx @backstage/create-app@latest

Docker外でアプリをビルドしてからイメージをビルド

まずは、Docker外でアプリをビルドしてからイメージをビルドする方法です。

app-config.production.yamlを更新

app-config.production.yamlを更新します。今回は、とりあえず動かしたいだけなので、下記のようにしています。

app-config.production.yaml
1app:
2  baseUrl: http://localhost:7007
3
4backend:
5  baseUrl: http://localhost:7007
6  listen:
7    port: 7007
8  auth:
9    keys:
10      - secret: xxxxxxx
11
12auth:
13  providers:
14    guest:
15      dangerouslyAllowOutsideDevelopment: true

アプリをビルド

下記のコマンドで、最初にアプリをビルドします。

1yarn install --frozen-lockfile
2yarn tsc
3yarn build:backend --config ../../app-config.yaml --config ../../app-config.production.yaml

Dockerイメージをビルド

packages/backend/Dockerfileを作成します。

packages/backend/Dockerfile
1FROM node:18-bookworm-slim
2
3# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
4RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
5    --mount=type=cache,target=/var/lib/apt,sharing=locked \
6    apt-get update && \
7    apt-get install -y --no-install-recommends python3 g++ build-essential && \
8    yarn config set python /usr/bin/python3
9
10# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
11# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
12RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
13    --mount=type=cache,target=/var/lib/apt,sharing=locked \
14    apt-get update && \
15    apt-get install -y --no-install-recommends libsqlite3-dev
16
17# From here on we use the least-privileged `node` user to run the backend.
18USER node
19
20# This should create the app dir as `node`.
21# If it is instead created as `root` then the `tar` command below will
22# fail: `can't create directory 'packages/': Permission denied`.
23# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
24# so the app dir is correctly created as `node`.
25WORKDIR /app
26
27# This switches many Node.js dependencies to production mode.
28ENV NODE_ENV production
29
30# Copy repo skeleton first, to avoid unnecessary docker cache invalidation.
31# The skeleton contains the package.json of each package in the monorepo,
32# and along with yarn.lock and the root package.json, that's enough to run yarn install.
33COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./
34RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz
35
36RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
37    yarn install --frozen-lockfile --production --network-timeout 300000
38
39# Then copy the rest of the backend bundle, along with any other files we might want.
40COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./
41RUN tar xzf bundle.tar.gz && rm bundle.tar.gz
42
43CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]

.dockerignoreも作成しておきます。

.dockerignore
1.git
2.yarn/cache
3.yarn/install-state.gz
4node_modules
5packages/*/src
6packages/*/node_modules
7plugins
8*.local.yaml

必要なファイルが揃ったので、イメージをビルドします。

1docker image build . -f packages/backend/Dockerfile -t backstage:hostbuild

Dockerで起動

イメージが作成できたら、起動します。

1docker run -it -p 7007:7007 backstage:hostbuild

Dockerでマルチステージビルド

次に、Dockerでマルチステージビルドする方法です。

app-config.production.yamlを更新

app-config.production.yamlを更新します。中身については、上記のやり方と同じ内容になっています。

app-config.production.yaml
1app:
2  baseUrl: http://localhost:7007
3
4backend:
5  baseUrl: http://localhost:7007
6  listen:
7    port: 7007
8  auth:
9    keys:
10      - secret: xxxxxxx
11
12auth:
13  providers:
14    guest:
15      dangerouslyAllowOutsideDevelopment: true

Dockerイメージをビルド

packages/backend/Dockerfileを作成します。上記のやり方と異なり、マルチステージでアプリのビルドからイメージの作成までを行います。

packages/backend/Dockerfile
1# Stage 1 - Create yarn install skeleton layer
2FROM node:18-bookworm-slim AS packages
3
4WORKDIR /app
5COPY package.json yarn.lock ./
6
7COPY packages packages
8
9# Comment this out if you don't have any internal plugins
10COPY plugins plugins
11
12RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+
13
14# Stage 2 - Install dependencies and build packages
15FROM node:18-bookworm-slim AS build
16
17# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
18RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
19    --mount=type=cache,target=/var/lib/apt,sharing=locked \
20    apt-get update && \
21    apt-get install -y --no-install-recommends python3 g++ build-essential && \
22    yarn config set python /usr/bin/python3
23
24# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
25# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
26RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
27    --mount=type=cache,target=/var/lib/apt,sharing=locked \
28    apt-get update && \
29    apt-get install -y --no-install-recommends libsqlite3-dev
30
31USER node
32WORKDIR /app
33
34COPY --from=packages --chown=node:node /app .
35
36RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
37    yarn install --frozen-lockfile --network-timeout 600000
38
39COPY --chown=node:node . .
40
41RUN yarn tsc
42RUN yarn --cwd packages/backend build
43# If you have not yet migrated to package roles, use the following command instead:
44# RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies
45
46RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \
47    && tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \
48    && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle
49
50# Stage 3 - Build the actual backend image and install production dependencies
51FROM node:18-bookworm-slim
52
53# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
54RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
55    --mount=type=cache,target=/var/lib/apt,sharing=locked \
56    apt-get update && \
57    apt-get install -y --no-install-recommends python3 g++ build-essential && \
58    yarn config set python /usr/bin/python3
59
60# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
61# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
62RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
63    --mount=type=cache,target=/var/lib/apt,sharing=locked \
64    apt-get update && \
65    apt-get install -y --no-install-recommends libsqlite3-dev
66
67# From here on we use the least-privileged `node` user to run the backend.
68USER node
69
70# This should create the app dir as `node`.
71# If it is instead created as `root` then the `tar` command below will
72# fail: `can't create directory 'packages/': Permission denied`.
73# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
74# so the app dir is correctly created as `node`.
75WORKDIR /app
76
77# Copy the install dependencies from the build stage and context
78COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./
79
80RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
81    yarn install --frozen-lockfile --production --network-timeout 600000
82
83# Copy the built packages from the build stage
84COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./
85
86# Copy any other files that we need at runtime
87COPY --chown=node:node app-config.yaml ./
88COPY --chown=node:node app-config.production.yaml ./
89
90# This switches many Node.js dependencies to production mode.
91ENV NODE_ENV production
92
93CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]

.dockerignoreも作成しておきます。

.dockerignore
1dist-types
2node_modules
3packages/*/dist
4packages/*/node_modules
5plugins/*/dist
6plugins/*/node_modules

イメージをビルドします。

1docker image build . -f packages/backend/Dockerfile -t backstage:multistage

Dockerで起動

イメージが作成できたら、起動します。

1docker run -it -p 7007:7007 backstage:multistage

フロントエンドとバックエンドを分けてビルド

最後に、フロントエンドとバックエンドを分けてビルドする方法です。

バックエンドのプラグインの修正

まずは、@backstage/plugin-app-backendの設定を削除します。

packages/backend/src/index.tsから@backstage/plugin-app-backendの設定を削除します。

packages/backend/src/index.ts
1import { createBackend } from '@backstage/backend-defaults';
2
3const backend = createBackend();
4
5// backend.add(import('@backstage/plugin-app-backend/alpha')); ここを削除します
6backend.add(import('@backstage/plugin-proxy-backend/alpha'));
7backend.add(import('@backstage/plugin-scaffolder-backend/alpha'));
8backend.add(import('@backstage/plugin-techdocs-backend/alpha'));
9
10// auth plugin
11backend.add(import('@backstage/plugin-auth-backend'));
12// See https://backstage.io/docs/backend-system/building-backends/migrating#the-auth-plugin
13backend.add(import('@backstage/plugin-auth-backend-module-guest-provider'));
14// See https://github.com/backstage/backstage/blob/master/docs/auth/guest/provider.md
15
16// catalog plugin
17backend.add(import('@backstage/plugin-catalog-backend/alpha'));
18backend.add(
19  import('@backstage/plugin-catalog-backend-module-scaffolder-entity-model'),
20);
21
22// permission plugin
23backend.add(import('@backstage/plugin-permission-backend/alpha'));
24backend.add(
25  import('@backstage/plugin-permission-backend-module-allow-all-policy'),
26);
27
28// search plugin
29backend.add(import('@backstage/plugin-search-backend/alpha'));
30backend.add(import('@backstage/plugin-search-backend-module-catalog/alpha'));
31backend.add(import('@backstage/plugin-search-backend-module-techdocs/alpha'));
32
33backend.start();

プラグインはもう必要ないので、packages/backend/package.jsonからも@backstage/plugin-app-backendを削除します。

バックエンドのDockerfileを作成

バックエンド用のpackages/backend/Dockerfileを作成します。内容としては、マルチステージビルドと同じになりますが、@backstage/plugin-app-backendの設定を削除しているので、フロントエンドは統合されてないようになります。

packages/backend/Dockerfile
1# Stage 1 - Create yarn install skeleton layer
2FROM node:18-bookworm-slim AS packages
3
4WORKDIR /app
5COPY package.json yarn.lock ./
6
7COPY packages packages
8
9# Comment this out if you don't have any internal plugins
10COPY plugins plugins
11
12RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf {} \+
13
14# Stage 2 - Install dependencies and build packages
15FROM node:18-bookworm-slim AS build
16
17# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
18RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
19    --mount=type=cache,target=/var/lib/apt,sharing=locked \
20    apt-get update && \
21    apt-get install -y --no-install-recommends python3 g++ build-essential && \
22    yarn config set python /usr/bin/python3
23
24# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
25# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
26RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
27    --mount=type=cache,target=/var/lib/apt,sharing=locked \
28    apt-get update && \
29    apt-get install -y --no-install-recommends libsqlite3-dev
30
31USER node
32WORKDIR /app
33
34COPY --from=packages --chown=node:node /app .
35
36RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
37    yarn install --frozen-lockfile --network-timeout 600000
38
39COPY --chown=node:node . .
40
41RUN yarn tsc
42RUN yarn --cwd packages/backend build
43# If you have not yet migrated to package roles, use the following command instead:
44# RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies
45
46RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \
47    && tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \
48    && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle
49
50# Stage 3 - Build the actual backend image and install production dependencies
51FROM node:18-bookworm-slim
52
53# Install isolate-vm dependencies, these are needed by the @backstage/plugin-scaffolder-backend.
54RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
55    --mount=type=cache,target=/var/lib/apt,sharing=locked \
56    apt-get update && \
57    apt-get install -y --no-install-recommends python3 g++ build-essential && \
58    yarn config set python /usr/bin/python3
59
60# Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image,
61# in which case you should also move better-sqlite3 to "devDependencies" in package.json.
62RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
63    --mount=type=cache,target=/var/lib/apt,sharing=locked \
64    apt-get update && \
65    apt-get install -y --no-install-recommends libsqlite3-dev
66
67# From here on we use the least-privileged `node` user to run the backend.
68USER node
69
70# This should create the app dir as `node`.
71# If it is instead created as `root` then the `tar` command below will
72# fail: `can't create directory 'packages/': Permission denied`.
73# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`)
74# so the app dir is correctly created as `node`.
75WORKDIR /app
76
77# Copy the install dependencies from the build stage and context
78COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./
79
80RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \
81    yarn install --frozen-lockfile --production --network-timeout 600000
82
83# Copy the built packages from the build stage
84COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./
85
86# Copy any other files that we need at runtime
87COPY --chown=node:node app-config.yaml ./
88COPY --chown=node:node app-config.production.yaml ./
89
90# This switches many Node.js dependencies to production mode.
91ENV NODE_ENV production
92
93CMD ["node", "packages/backend", "--config", "app-config.yaml", "--config", "app-config.production.yaml"]

フロントエンドのDockerfileを作成

フロントエンド用のpackages/app/Dockerfileを作成します。内容としては、アプリのビルドをしてから、それをNginxで配信するようになっています。

packages/app/Dockerfile
1FROM node:18-bookworm-slim AS build
2
3RUN mkdir /app
4COPY . /app
5WORKDIR /app
6
7RUN yarn install
8COPY app-config.yaml ./
9COPY app-config.production.yaml ./
10RUN yarn workspace app build --config ../../app-config.yaml --config ../../app-config.production.yaml
11
12
13FROM nginx:mainline
14
15RUN apt-get update && apt-get -y install jq && rm -rf /var/lib/apt/lists/*
16
17COPY --from=build /app/packages/app/dist /usr/share/nginx/html
18COPY packages/app/docker/default.conf.template /etc/nginx/templates/default.conf.template
19
20COPY packages/app/docker/inject-config.sh /docker-entrypoint.d/40-inject-config.sh
21
22ENV PORT 80

app-config.production.yamlを更新

最後に、app-config.production.yamlを更新します。

フロントエンドとバックエンドは別々に動かすため、異なるbaseUrlを設定します。また、CORSの設定も必要になります。ここでは、Docker Composeで動かす場合にポートフォーワードするlocalhost:8080を許可しています。

app-config.production.yaml
1app:
2  baseUrl: http://localhost:7001
3
4backend:
5  baseUrl: http://backstage-sample-backstage-backend-1:7002
6  listen:
7    port: 7002
8  auth:
9    keys:
10      - secret: xxxxxxx
11  cors:
12    origin: http://localhost:8080
13    methods: [GET, HEAD, PATCH, POST, PUT, DELETE]
14    credentials: true
15
16auth:
17  providers:
18    guest:
19      dangerouslyAllowOutsideDevelopment: true

イメージのビルド

必要なファイルが用意できたら、イメージをビルドします。

1docker image build . -f packages/backend/Dockerfile -t backstage:backend
2docker image build . -f packages/app/Dockerfile -t backstage:frontend

Docker composeで起動

イメージが作成できたら、下記のようなdocker-compose.ymlを作成して、起動します。

docker-compose.yml
1version: '3'
2services:
3  backstage-frontend:
4    image: backstage/frontend:latest
5    ports:
6      - 8080:80
7
8  backstage-backend:
9    image: backstage/backend:latest
10    ports:
11      - 7002:7002
1docker-compose up -d

まとめ

BackstageのDockerイメージをビルドする方法について紹介しました。

簡単に試すのであれば、フロントエンドとバックエンドを一緒にしたコンテナでも良さそうですが、本番環境などで運用する場合は、フロントエンドとバックエンドを分けた方が良いかもしれません。

下記のリポジトリで、Backstageの実装をいろいろ試していきたいと思います。

GitHub - monda00/backstage-sample

GitHub - monda00/backstage-sample

Contribute to monda00/backstage-sample development by creating an account on GitHub.

参考

Support

\ この記事が役に立ったと思ったら、サポートお願いします! /

buy me a coffee
Share

Profile

author

Masa

都内のIT企業で働くエンジニア
自分が学んだことをブログでわかりやすく発信していきながらスキルアップを目指していきます!

buy me a coffee