Harness Open Source

harness/harness — 1 つの Go バイナリに git ホスティング・CI・アーティファクトレジストリ・開発環境を同居させる

1 — 概要

GitHub プロジェクト harness/harness(Go モジュール名は github.com/harness/gitness、ビルド成果物も gitness)は、開発プラットフォームの主要機能を 1 つのサーバープロセスにまとめたオープンソース実装である。扱う領域は 4 つ。

  • コードホスティング — ベアリポジトリ、smart HTTP と SSH、プルリクエスト、ブランチ保護、コードオーナー、LFS。
  • パイプライン — Drone 由来の実行エンジンで、コミットやプルリクエストからコンテナベースの CI を回す。
  • アーティファクトレジストリ — OCI/Docker を含む 11 種のパッケージ形式と、上流ミラーリング。
  • Gitspaces — devcontainer.json を読んでコンテナ開発環境を起動し、VS Code や JetBrains Gateway から接続させる。
1 バイナリgitness server
約 40 万行Go 2,792 ファイル
11 形式レジストリ対応
Apache-2.0ライセンス

Drone との関係は README に明記されている。Harness Open Source は Drone の次世代版という位置づけで、Drone 時点のコードは drone ブランチにスナップショットとして残され、開発は main で続いている。

言語構成

バックエンドは Go(go 1.26.6)。フロントエンドは web ディレクトリの React 17 + TypeScript 4.7 + webpack 5 で、ビルド結果は Go バイナリに埋め込まれる。

依存の少なさ

既定の永続化は SQLite、イベントバス・ロック・pub/sub は既定でインメモリ。Redis・PostgreSQL・S3・GCS はいずれも差し替え可能な選択肢として用意されている。

設定方法

設定は環境変数 1 本槍で、プレフィックスは GITNESS_。gitness server <envfile> のように env ファイルを渡せる。

2 — リクエストの入口とルーター

エントリポイントは cmd/gitness/main.go。kingpin ベースの CLI(cli/)が server / migrate / user / account / hooks / swagger を登録する。server は env を読み込んでから Google Wire が生成した巨大なインジェクタ(cmd/gitness/wire_gen.go)を呼び、ストア・git サービス・イベント・全コントローラ・レジストリ・パイプライン・Gitspaces を 1 つの System に組み立てる。

組み立て後は初回ブートストラップ(管理者ユーザーと内部呼び出し用のサービスプリンシパル作成)を行い、errgroup で HTTP サーバー、SSH サーバー、ジョブスケジューラ、CI ポーラー、メトリクス収集を並行起動する。停止は SIGINT/SIGTERM でグレースフルに行う。

main→ CLI: server→ env → Config→ Wire injector→ bootstrap→ errgroup で常駐開始

HTTP の入口は 1 つだが、app/router の Router.ServeHTTP が登録順にサブルーターへ IsEligibleTraffic を尋ね、最初に引き受けたものへ渡す。内部はいずれも go-chi。

flowchart TD
  REQ["HTTP リクエスト :3000"] --> R{"Router.ServeHTTP
登録順に IsEligibleTraffic"} R -->|"git ホスト / git パス"| GIT["git ルーター
info/refs, git-upload-pack,
git-receive-pack, info/lfs"] R -->|"レジストリ URL"| REG["registry ルーター
/v2/* OCI, /pkg/*"] R -->|"/api/ 前置"| API["API ルーター
/api/v1/* + openapi.yaml"] R -->|"それ以外"| WEB["web ルーター
埋め込み SPA + /swagger"] SSH["SSH :3022"] --> CTRL["repo / lfs コントローラ"] GIT --> CTRL API --> CTRL CTRL --> GITSVC["git サービス"]
順サブルーター受け持つもの
0gitsmart HTTP(info/refs、git-upload-pack、git-receive-pack)、LFS API、go get 用メタ。ホスト名でもパスでも判定できる。
1registryOCI distribution API(/v2/*)とパッケージ別 API(/pkg/*)、レジストリ用 Swagger。
2api/api/ 前置を剥がして /api/v1 の REST API へ。認証ミドルウェアはここで適用される。
3web埋め込み SPA の配信、/openapi.yaml、Swagger UI。未知パスは / にフォールバックする。
dumb git protocol は意図的に未実装。 該当パスにはスタブハンドラが割り当てられ、502 を返す。クライアントには smart HTTP か SSH を使わせる設計。
3 — Git サービスとフック

Git 操作は libgit2 や go-git ではなく、OS の git コマンドを exec する方式で実装されている。層は 3 段。

層パッケージ役割
実行git/commandCommand{Globals, Name, Action, Flags, Args, PostSepArgs, Envs} という型で git 起動を組み立てる。ユーザー入力は -- 以降に置く PostSepArgs へ回し、フラグ注入を防ぐ。終了コードと stderr は型付きエラーへ変換する。
アダプタgit/apigit の概念ごとに 1 ファイル(commit.go、tree.go、diff.go、merge.go、blame.go、cat-file.go、pack-objects.go、gc.go ほか)。foreachref や parser で出力を構造体に落とす。
サービスgit(interface.go / service.go)ベアリポジトリの配置場所、共有一時ディレクトリ、削除待ちの墓場ディレクトリを管理し、フッククライアントを持つ。Wire で in-process に注入されるため、ネットワーク境界はない。
命名の名残に注意。 CreateRPCGitPushWriteParams のような RPC 由来の型名が残っているが、このツリーでは git サービスは同一プロセス内の関数呼び出しである。別マイクロサービスとして説明しないこと。

サーバーサイドフックは同じバイナリ

リポジトリ作成時に、hooks/pre-receive と hooks/post-receive が gitness 実行ファイル自身へのシンボリックリンクとして張られる(update は性能上コメントアウト)。push のたびに次の往復が起きる。

git push→ receive-pack→ hook = gitness 実行ファイル→ GIT_HOOK_PAYLOAD を復号→ REST で /v1/internal/git-hooks→ 保護ルール・シークレット走査・マージキュー判定

ペイロード(リポジトリ ID、プリンシパル ID、リクエスト ID、操作種別、コールバック先 URL)は gob エンコード+base64 で環境変数 GIT_HOOK_PAYLOAD 1 本に詰められる。cli/cli.go は起動時に argv を見てフック呼び出しだと判定すると hooks サブコマンドへ経路を切り替える。フック処理のタイムアウトは 3 分。

SSH 側

ssh/server.go は gliderlabs/ssh のサーバーで、許可コマンドを git-upload-pack / git-receive-pack / git-lfs-authenticate / git-lfs-transfer の 4 つに限定する。公開鍵認証は app/services/publickey が担い、その先は HTTP と同じリポジトリコントローラへ合流する。

4 — パイプライン / CI

CI は Drone のコードベースを依存ライブラリとして取り込む形で実装されている。ランナーは同一プロセス内で動き、ポーラーも HTTP ではなく in-process クライアント経由でジョブを受け取る。

依存モジュール用途
drone-runners/drone-runner-dockerDocker 実行エンジン。レガシー Drone YAML 用の engine と v1 spec 用の engine2 の両方を使う。
drone/runner-goランナーの骨格(クライアント、ポーラー、ランタイム、レポーター、アップローダ、シークレット)。
drone/specv1 パイプライン spec のパース・正規化・解決。
drone/drone-yaml, drone/drone-goレガシー YAML のパースと lint、API 型。
drone/go-convert, go-generate, funcmap変換とテンプレート展開。

2 つの YAML 方言

方言の判定は単純で、パイプラインファイルが ^spec: にマッチすれば v1 spec、しなければレガシー Drone YAML として扱う(app/pipeline/triggerer/trigger.go の isV1Yaml)。レガシー側は jsonnet / starlark のコンバータと lint を通り、DAG を組んでからステージ化される。

flowchart TD
  EV["git / pullreq イベント
(内部イベントバス)"] --> TS["trigger サービス
app/services/trigger"] TS -->|"有効トリガを突合"| TR["triggerer
app/pipeline/triggerer"] TR --> FS["file サービス
指定 ref の YAML を取得"] FS --> DIA{"^spec: か"} DIA -->|"はい"| V1["drone/spec で parse
→ ステージ生成"] DIA -->|"いいえ"| LEG["converter → lint
→ DAG → ステージ生成"] V1 --> EXE["Execution + Stage を
1 トランザクションで保存"] LEG --> EXE EXE --> SCH["scheduler
ステージキュー"] SCH --> POLL["poller(in-process)"] POLL --> MGR["ExecutionManager
Accept / Details / Watch / Write"] MGR --> RUN["Docker エンジンで step 実行"] RUN --> LOG["livelog でライブログ配信"] RUN --> CHK["コミットチェックに結果書き戻し"]
コンポーネント場所役割
trigger サービスapp/services/triggerブランチ・タグ・プルリクの各イベントを購読し、リポジトリの有効トリガと突き合わせる。
triggererapp/pipeline/triggererHook を Execution と Stage 群に変換。skip.go がブランチ・イベント・アクション・ref・cron の除外条件を判定する。
fileapp/pipeline/file内部 git サービスから、指定 ref のパイプライン YAML を読む。
converterapp/pipeline/converterjsonnet / starlark テンプレートを Drone YAML へ展開する。
managerapp/pipeline/managerランナー側から見た API。Details がリポジトリ・実行・ステージ・シークレット・netrc をまとめて渡す。
schedulerapp/pipeline/schedulerステージのキューイングとキャンセル通知。分散ロックを併用する。
runnerapp/pipeline/runnerDocker エンジンとポーラーの組み立て。API バージョンをデーモンと自動ネゴシエートする。
resolverapp/pipeline/resolverv1 spec のプラグイン・テンプレート解決。実行 → リポジトリ → スペースの順にスコープを広げて探す。
liveloglivelog実行中の step ログをブラウザへ流す。実装はインメモリのみ。
特権コンテナは許可リスト方式。 plugins/docker と ACR / ECR / GCR / Heroku 系プラグインだけが privileged で動く。Docker ソケットの位置は GITNESS_DOCKER_HOST で変更でき、Rancher Desktop や Colima も README に手順がある。

同じ git イベントバスに、目的の異なる 2 つの購読者がぶら下がっている。app/services/webhook は外向きで、ユーザーが登録した HTTP エンドポイントを叩く。app/services/trigger は内向きで、イベントをパイプライン実行に変える。レジストリ側にも独立した webhook 実装がある。

5 — Artifact Registry

レジストリは registry ディレクトリ配下の別 Go モジュールで、ルート側の go.mod の replace でローカル参照されている。OCI distribution spec の適合テストがリポジトリに入っており、Makefile から ./gitness server を立てて走らせられる。

対応パッケージ形式

DOCKER HELM MAVEN NPM NUGET PYTHON RPM CARGO GO HUGGINGFACE GENERIC

実装は registry/app/pkg の形式別ディレクトリに分かれる。ただし HELM は専用ディレクトリを持たず、Docker/OCI の経路で扱われる(docker のローカル・リモート実装がいずれも DOCKER と HELM を宣言する)。

レジストリの種類とディスパッチ

種類意味
VIRTUALローカル保管に加え、順序付けした上流レジストリへフォールバックする。取得したアーティファクトはキャッシュされる。
UPSTREAM外部レジストリを指す定義。Docker Hub、npmjs、PyPI、NuGet、Maven Central、crates.io、Go proxy、RPM、AWS ECR などのアダプタがある。

ハンドラの選択は (パッケージ形式, レジストリ種類) をキーにしたファクトリ(registry/app/pkg/base)で行われ、プロキシ有無とチェック有無のラッパーが被せられる。

router→ handler→ controller→ pkg/<形式>→ base + filemanager→ storage→ driver
ストレージドライバ登録名備考
ファイルシステムfilesystem既定。Docker イメージでは /data/registry に置かれる。
S3 互換s3 と R2 名同一ファクトリを 2 つの名前で登録している。
Google Cloud Storagegcs—

ドライバのファクトリ構造は distribution/distribution を踏襲しており、OCI 側ハンドラは blob の resumable upload、manifest、tag 一覧、catalog、referrers(OCI 1.1)、トークン発行までカバーする。ガベージコレクタ(registry/gc)と隔離機構(quarantine)も別立てで用意されている。

隔離判定の出どころは外部。 quarantine パッケージはアーティファクトの隔離状態を問い合わせて配布をブロックする仕組みで、スキャナ本体はこのリポジトリには含まれない。
6 — Gitspaces(コンテナ開発環境)

Gitspaces は app/gitspace(orchestrator / infrastructure / scm / secret / logutil)と app/services/gitspace*、そしてインフラ抽象の infraprovider で構成される。API は app/api/controller/gitspace と infraprovider。

インフラプロバイダ

抽象インターフェイスは Provision / Find / Stop / CleanupInstanceResources / Deprovision / パラメータ検証などを備え、列挙型には docker、harness_gcp、harness_cloud、hybrid_vm_gcp、hybrid_vm_aws が並ぶ。ただしこの OSS ツリーで実装されているのは Docker だけで、ファクトリにも docker しか登録されない。Kubernetes プロバイダは含まれない。

イベント駆動で中断・再開する起動シーケンス

flowchart TD
  API["API: start gitspace"] --> ACT["app/services/gitspace
action_start"] ACT --> SCM["SCM 詳細取得
clone 認証 + devcontainer.json 解析"] SCM --> PORT["必要ポート算出
forwardPorts + IDE ポート"] PORT --> PROV["infra provisioner に
Provision イベントを投げて返る"] PROV -.->|"完了イベント"| RES["ResumeStartGitspace"] RES --> CONT["コンテナ orchestrator
作成・起動・features 解決"] CONT --> LC["lifecycle コマンド実行
postCreate / postStart"] LC --> SSHD["コンテナ内 sshd と IDE 準備"] SSHD -.->|"操作完了イベント"| FIN["FinishResumeStartGitspace"]

起動は同期的に完走せず、プロビジョニング完了とコンテナ操作完了がそれぞれイベントとして戻り、ResumeStartGitspace と FinishResumeStartGitspace が続きを引き受ける。停止・削除・クリーンアップも同じ形。状態は設定レベル(running / stopped / starting / stopping / cleaning / error / uninitialized)とインスタンスレベル(resetting、pending_cleanup などを含む 12 種)で二重管理される。

接続できる IDE

vs_codevs_code_webcursorwindsurf intellijpycharmgolandwebstorm clionphpstormrubyminerider

各 IDE は app/gitspace/orchestrator/ide の実装をファクトリで選ぶ。VS Code Web はアセットを同梱し、デスクトップ IDE と JetBrains Gateway はコンテナ内で起動する sshd に接続する。

2 つの SSH を混同しない。 トップレベルの ssh パッケージは git 用の SSH サーバーで、Gitspaces の接続先はコンテナ内で動く sshd である。後者にはプロバイダのポートマッピング経由で到達する。
7 — 認証と認可

主体(プリンシパル)は user / serviceaccount / service の 3 種。Admin フラグを持つプリンシパルは検査をバイパスする。内部処理は起動時に作られるシステム・パイプライン・Gitspace のサービスプリンシパルとして自分自身の API を呼ぶ。

リソースはネストしたスペース(パスで表現)の下に並ぶ。認可はリソースをスペースパスに写し、祖先方向にメンバーシップを辿って判定する。公開アクセス設定が先に評価され、次に管理者バイパス、最後にメンバーシップという順序。

リソース種別(13)
SPACEREPOSITORYPIPELINEREGISTRY GITSPACEINFRAPROVIDERSECRETCONNECTOR TEMPLATEINTEGRATIONUSERSERVICEACCOUNTSERVICE
ロール(4)
readerexecutorcontributorspace_owner

reader の権限集合を土台に、上位ロールが積み増す形で静的に定義されている。

権限は 56 個の文字列

権限は <リソース>_<動詞> 形式の文字列で、リポジトリ系だけでも repo_view / repo_create / repo_edit / repo_push / repo_review / repo_delete がある。レジストリ系は registry_* とアーティファクト操作系(artifacts_upload / artifacts_download / artifacts_delete / artifacts_quarantine)に分かれている。

検査の位置

認証(JWT の抽出と検証)はミドルウェアで常に試行されるが、強制はコントローラ側で行う。app/api/auth の CheckRepo、CheckSpace、CheckPipeline、CheckRegistry、CheckGitspace といったヘルパーを各コントローラが呼ぶ。トークンはヘッダの Bearer、HTTP Basic のパスワード、Cookie などから取り出す。git ルートではブラウザのセッショントークンをミドルウェアで弾く。

権限判定の実例。 smart HTTP のサービス名から権限へ写す箇所が分かりやすい。git-receive-pack は repo_push、git-upload-pack は repo_view を要求する。
8 — データと基盤コンポーネント

永続化は app/store/database.go に並ぶ約 60 のストアインターフェイスと、その実装(app/store/database、sqlx + squirrel)で構成される。プレースホルダは PostgreSQL 形式($1)に固定されており、SQLite でもそのまま通す方針。

項目内容
対応 DBsqlite3(既定)と postgres。GITNESS_DATABASE_DRIVER / GITNESS_DATABASE_DATASOURCE で切り替える。
マイグレーションgoose ではなく、埋め込み SQL ディレクトリを走らせる方式。PostgreSQL 用と SQLite 用の SQL を go:embed で持ち、番号順に適用する。SQLite では外部キーを一時的に無効化して走らせる。
SQL の規模PostgreSQL 用は up/down 合わせて約 390 ファイル、番号は 0189 付近まで。SQLite 用も同数規模。
Go 製の補助データ変換が必要な移行は Go コードとして併存する(アーティファクト移行や RPM 移行など)。
CLIgitness migrate current / gitness migrate to で確認と巻き戻しができる。

単体起動をやりやすくするため、分散化が必要な部品はすべて「インメモリ既定 + 外部バックエンド任意」の形で抽象化されている。Redis を入れれば同じインターフェイスのまま複数インスタンス構成に寄せられる。

パッケージ用途バックエンド
streamストリームの生産者・消費者プリミティブインメモリ / Redis Streams
events型付きドメインイベント(at-least-once)。git・repo・pullreq・rule・pipeline・check・gitspace 系のイベントを定義インメモリ / Redis
pubsub投げっぱなしの pub/sub。SSE 配信の土台でもあるインメモリ / Redis
lock分散ミューテックスインメモリ / Redis
jobDB に永続化するバックグラウンドジョブ。ロックと pub/sub で協調するDB のみ
blob添付・アップロード用オブジェクトストアファイルシステム / GCS
cache型付きキャッシュ(LRU / TTL / Redis / no-op)。git の last-commit キャッシュや権限キャッシュに使う複数
livelogCI のライブログインメモリのみ
app/sseブラウザへの Server-Sent Eventspubsub に追従
9 — Web フロントエンド

フロントエンドは web ディレクトリの単一 yarn パッケージ(パッケージ名 codeui)。React 17 + webpack 5 で、Vite は使っていない。

ライブラリ宣言バージョン役割
react / react-dom^17.0.2UI ランタイム
webpack^5.58.0バンドラ。Module Federation も担う
react-router-dom^5.2.1ルーティング(v5 API の Switch 系)
restful-react15.6.0データ取得と OpenAPI からのクライアント生成
@tanstack/react-query4.20.4レジストリ側モジュールのクエリキャッシュ
@blueprintjs/core3.26.1基礎ウィジェット
@harnessio/uicore / design-system / icons^4.3.4 / ^2.1.1 / 2.1.22自社コンポーネントとトークン
monaco-editor + monaco-yaml^0.45.0 / ^4.0.4パイプライン YAML とファイル編集
@codemirror/*^6.xMarkdown 入力(コメント・PR 本文)
diff2html3.4.22差分表示
sass + typed-scss-modules^1.32.8 / ^7.1.4Sass CSS Modules と .d.ts 生成
jotai^2.6.3アトム状態(現在ユーザー、リポジトリメタ等)
highcharts9.2.0グラフ

i18n に i18next は使わず、YAML の文言ファイル(strings.en.yaml / strings.es.yaml)を独自フレームワークが読み、useStrings() と <String/> で参照する。型はビルド時に自動生成される。

API 型定義の正は Go コードである。TypeScript クライアントはそこから 3 段で生成される。

app/api/openapi の Go コード→ gitness swagger で YAML 出力→ restful-react import→ 生成 TSX(フックと型)
  1. app/api/openapi の 26 ファイルが openapi3.Reflector にオペレーションを登録する。サーバーは /api/v1、認証スキームは bearer。
  2. 隠しコマンド gitness swagger が仕様を YAML で吐く。README の手順どおり、出力をフロントエンドの swagger.yaml(約 1.75 万行)へ流し込む。実行中のサーバーは同じ仕様を /openapi.yaml で配信し、/swagger に UI を置く。
  3. yarn services が restful-react import を走らせ、useGet* / useMutate* フックと型を含む約 1.13 万行の TSX を生成する。ベース URL はランタイム設定関数から解決する。
レジストリ API は逆向き。 こちらは手書きの OpenAPI YAML(約 3,600 行)が正で、そこから oapi-codegen で Go サーバー側のコードを生成する。TypeScript クライアントはリポジトリ内で生成せず、公開済み npm パッケージを取り込む。

1 つの webpack プロジェクトから 3 つのリモートを出力し、react / react-dom / react-router-dom / formik をバージョン固定の singleton として共有する。

リモート中身主な expose
codeRemoteSCM 本体./App、./Repositories、./PullRequests、./Compare、./Webhooks ほか
harRemoteアーティファクトレジストリ UI./HAREnterpriseApp、./HARCreateRegistryButton
cdeRemoteGitspaces UI./Gitspaces、./GitspaceInfra、./UsageDashboard ほか

レジストリ UI の合成方法は 2 通り

  • OSS バイナリで動くモード: メインアプリのルーティングが lazy(() => import(...)) でレジストリアプリを読み込む。コード分割はされるがネットワーク越しではなく、同じ dist に同梱される。アダプタが matchPath、401 ハンドラ、トークン生成、ルート定義、ホスト側コンポーネントを注入する。
  • プラットフォーム埋め込みモード: 同じコードを ./HAREnterpriseApp として federated module で公開し、親シェルがコンテキストを与える。API クライアントはインターセプタでヘッダと base URL を差し替えるため、レジストリ UI 自体は接続先を知らないまま動く。
10 — ビルドと起動

Go バイナリは web/dist.go の go:embed dist/* でフロントエンドのビルド成果物を取り込む。dist は git 管理外なので、クリーンなチェックアウトでは make web-build(または Docker の web ステージ)が先に必要。make build は web-build に依存していない点に注意する。

# 手元でひととおり動かす
make dep && make tools          # wire, dbmate, goimports, protoc 系
cd web && yarn install && yarn build && cd ..
make build                      # wire 生成 → go build -o ./gitness
./gitness server .local.env     # http://localhost:3000
Makefile ターゲット内容
dep / toolswire・dbmate の導入、goimports・govulncheck・protoc プラグイン・gci の導入
generateGoogle Wire で DI コードを再生成する
web-buildyarn install && yarn build
build / runバイナリ生成と、./gitness server .local.env による起動
test一部レジストリ結合テストを除外して go test + カバレッジ HTML
lint / lint-fullgolangci-lint。設定はスキーマ v2 で、既定オフの上に約 50 個を明示有効化している
ar-api-updateレジストリ OpenAPI から Go サーバーコードを生成
ar-conformance-testサーバーを起動して OCI distribution spec 適合テストを走らせる

ビルドツールは実行時依存と分離され、go.tool.mod 側のモジュールとして go tool -modfile=go.tool.mod で呼ばれる。

公式イメージは 4 ステージ構成。node で web をビルド → golang-alpine で make dep・make tools の後にビルド成果物を web/dist へコピーして静的リンク(CGO_ENABLED=1、arm64 はクロスコンパイラを取得)→ CA 証明書ステージ → alpine/git ベースの最終イメージにバイナリだけを置く。バージョン情報は ldflags で埋め込まれる。

docker run -d \
  -p 3000:3000 -p 3022:3022 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v /tmp/harness:/data \
  --name harness --restart always \
  harness/harness
イメージ既定値値
GITNESS_GIT_ROOT/data
GITNESS_DATABASE_DRIVER / DATASOURCEsqlite3 / /data/database.sqlite
GITNESS_REGISTRY_FILESYSTEM_ROOT_DIRECTORY/data/registry
GITNESS_SSH_ENABLE / GITNESS_GITSPACE_ENABLEいずれも true
公開ポート3000(HTTP)と 3022(SSH)

Docker ソケットをマウントするのは、パイプラインの step と Gitspaces のコンテナをホストの Docker で動かすため。/data にはリポジトリ・SQLite・レジストリのブロブが入るので、名前付きボリュームかバインドマウントを当てないとコンテナ停止でデータが消える。

UI を差し替えるための別ルート。 Dockerfile.uiv2 は web ステージを「公開済み UI イメージからファイルをコピーする」だけに置き換える。Go 側は go:embed のままなので、次世代 UI を web に手を入れずに載せられる。開発中は GITNESS_DEVELOPMENT_UI_SOURCE_OVERRIDE で埋め込み FS をローカルディレクトリに差し替えられる。

Kubernetes 向けには charts/gitness に Helm チャート(deployment / service / ingress / pvc / serviceaccount)がある。