😩

【失敗談】Gitea + Cloud Run + Litestream構成でデプロイする

に公開

概要

あまりやる人はいないかと思いますが、軽量のセルフホスト版のGithub/Bitbucket/GitLabライクのGiteaをCloud Runにデプロイし、GiteaのビルドインSQLiteをLitestreamを使ってCloud Storageへレプリケーションできるのか?を試した記事になります。

結果的には上手くいかなかったのですが、何かの役に立てば。。

Giteaとは?

https://about.gitea.com/products/gitea/

Giteaは、Gitリポジトリのホスティングからコードレビュー、課題管理、パッケージレジストリ、CI/CDまでを一つにまとめたMITライセンスのオープンソースDevOpsプラットフォームです。

Go言語で書かれているため動作が軽く、Raspberry Piでも動かせるほどの省リソースで、Linux・macOS・Windowsといった主要OSに簡単にセルフホストできます。

プロジェクトは2016年にGogsからフォークして生まれ、コミュニティドリブンで機能拡張を続けており、GitHub Actions互換の「Gitea Actions」などモダンな開発ワークフローも標準サポートしています。

“Git with a cup of tea”――お茶のように気楽に使えるGitサービスを目指すその思想は、個人開発者から大規模組織まで多様なユーザーに支持されています。

Litestreamとは?

https://litestream.io/

Litestreamは、SQLiteのWALをリアルタイムにS3などへ転送し、単一サーバーでも秒単位RPOを実現するGo製ストリーミングレプリケーション&バックアップツールです。

YAML設定+ワンコマンドで導入でき、復旧はlitestream restoreでスナップショットと増分を自動再構成するだけ。

MITライセンスのオープンソースとして2021年に公開され、BoltDB作者Ben Johnson氏が中心となりコミュニティ開発が継続中です。

Fly.ioなどの“サーバーサイドSQLite”事例を後押しし、Raspberry Piからクラウドまで軽量にスケールする点が支持されています。

ローカルでGiteaを動かす

まずはローカル環境でGiteaを動かしてみたいと思います。👇を参考に進めていきます。

https://docs.gitea.com/installation/install-with-docker-rootless

まずはディレクトリを作成し、必要なディレクトリを作成します。

$ mkdir selfhost-gitea
$ cd selfhost-gitea

次に以下内容で compose.yml を作成します。

volumes:
  gitea-data:
  gitea-config:

services:
  gitea:
    image: docker.gitea.com/gitea:1.24.0-rootless
    restart: always
    working_dir: /usr/src
    volumes:
      - .:/usr/src
      - gitea-data:/var/lib/gitea
      - gitea-config:/etc/gitea
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "2222:2222"

早速起動して確認してみます。

$ docker compose up -d

起動後、http://localhost:3000/ にアクセスして以下の画面が表示されればOKです。

image1.png
ひとまずデフォルトの設定のまま「Giteaをインストール」を選択します。

その後アカウントを作成しログインすると👇の画面が表示されます。

image2.png

ひとまずローカルでGiteaを動かすまで実施できました。簡単ですね ✨

この時の SQLite のファイルパスは先ほどの設定項目を見ると /var/lib/gitea/data/gitea.db にデフォルトでなっている事が分かります。

MinIOを使ってローカルでLitestreamを動かす

次にローカルで試せるMinIOを使ってLitestreamのレプリケーション先として設定して先ほどのSQLiteのdbファイルをレプリケーションさせてみたいと思います。

https://min.io/

先ほどの compose.yml にMinIOの環境を追加します。

volumes:
  gitea-data:
  gitea-config:
  minio_data: # 追加

services:
  gitea:
    # ...
    # 👇 depends_on 追加
    depends_on:
      - minio
    # 👇 service 追加
  minio:
    image: minio/minio:RELEASE.2025-02-18T16-25-55Z
    volumes:
      - minio_data:/minio/data
    command: server --console-address ':9001' /minio/data
    ports:
      - 9000:9000
      - 9001:9001

また services:gitea 内で Litestream とデバッグ用に SQliteコマンド を使いたいので👇の Dockerfile.local を用意してそちらを使うようにします。

    build:
      context: .
      dockerfile: Dockerfile.local
FROM keinos/sqlite3:3.50.1 AS sqlite3-builder

FROM alpine AS litestream-builer

ARG LITESTREAM_VERSION=0.3.13

ENV LITESTREAM_VERSION=${LITESTREAM_VERSION}
ADD "https://github.com/benbjohnson/litestream/releases/download/v${LITESTREAM_VERSION}/litestream-v${LITESTREAM_VERSION}-linux-amd64.tar.gz" /tmp/litestream.tar.gz
RUN tar -C /usr/local/bin -xzf /tmp/litestream.tar.gz

FROM docker.gitea.com/gitea:1.24.0-rootless

COPY --from=sqlite3-builder /usr/bin/sqlite3 /usr/bin/sqlite3
COPY --from=litestream-builer /usr/local/bin/litestream /usr/local/bin/litestream

compose.yml

services:
  gitea:
    # 👇に変更
    build:
      context: .
      dockerfile: Dockerfile.local
    image: selfhost-gitea
    container_name: "selfhost-gitea"
    # ....

ここまできたら docker compose up -d で起動して、MinIOコンソール画面 http://localhost:9001 にアクセスします。

デフォルトだとユーザー/パスワード共に minioadmin なのでログインしときます。

まずはレプリケーション先のBucketを作成しときます。「Create Bucket」から「gitea-bucket」の名前でBucketを作成します。

image3.png

次にサイドメニューの「Access Keys」>「Create access key +」から新しいAccessKeyを作成しときます。

image4.png

次に litestream.yml を以下内容で作成します。 access-key-idsecret-access-key は先ほど作成したものを設定しときます。

dbs:
  - path: /var/lib/gitea/data/gitea.db
    replicas:
      - type: s3
        bucket: gitea-bucket
        path: gitea.db
        endpoint: http://minio:9000
        region: us-east-1
        access-key-id: xxxxxxxx
        secret-access-key: xxxxxxx
        force-path-style: true

早速Litestreamを動かしてみます。

litestream replicate -config ./litestream.yml

MinIOの先ほど作ったBucket内にオブジェクトが作成されています。

image5.png

試しに何かリポジトリを作成しときます。

image6.png

litestreamCtrl + C で止めて以下を実施し gitea.db を確認してみます。

$ sqlite3 /var/lib/gitea/data/gitea.db
SQLite version 3.50.1 2025-06-06 14:52:32
Enter ".help" for usage hints.
sqlite> select * from repository;
2|1|slowhand|test|test|||0||main|main|...

先ほど作成したリポジトリが登録されています。これらを一旦削除してMinIOのBucketからリストアしてみます。

$ rm /var/lib/gitea/data/gitea.db
$ rm /var/lib/gitea/data/gitea.db-*
$ litestream restore -if-replica-exists -o /var/lib/gitea/data/gitea.db -config ./litestream.yml /var/lib/gitea/data/gitea.db
$ sqlite3 /var/lib/gitea/data/gitea.db
SQLite version 3.50.1 2025-06-06 14:52:32
Enter ".help" for usage hints.
sqlite> select * from repository;
2|1|slowhand|test|test|||0||main|main|...

ちゃんとリストアできてそうな雰囲気です。この状態でコンテナ再起動して http://localhost:3000 にアクセスして前回と変わらず表示できとけばOKです。

ローカルでは volumes で永続化が必要な箇所をまるっと実施している為、コンテナ再起動しても普通に使えます。それが Cloud Run だと状況が変わってきます。次は実際にうまくいかなかったCloud Runへのデプロイを試していきます。

デプロイ準備

Cloud Run へデプロイする為に、Cloud StorageのBucket作成やArtifact Registryにリポジトリを作成します。

Cloud StorageのBucket作成

今回は selfhost-gitea という名前でBucketを作成していきます。Bucket名は適宜設定して下さい。

gcloud storage buckets create selfhost-gitea \
    --uniform-bucket-level-access \
    --location=asia-northeast1 \
    --project={PROJECT_ID} # プロジェクトIDを指定

またGiteaの設定ファイル app.ini をCloud Storageにボリュームマウントさせる為のBucketも作成しときます。理由は後述します。Bucket名は selfhost-gitea-config で作成しました。

    --add-volume=name=gitea-config,type=cloud-storage,bucket=selfhost-gitea-config \
    --add-volume-mount=volume=gitea-config,mount-path=/etc/gitea
gcloud storage buckets create selfhost-gitea-config \
    --uniform-bucket-level-access \
    --location=asia-northeast1 \
    --project={PROJECT_ID} # プロジェクトIDを指定

Artifact Registryリポジトリ作成

今回は selfhost-gitea-repository という名前でリポジトリを作成しました。

gcloud artifacts repositories create selfhost-gitea-repository \
    --repository-format=docker \
    --location=asia-northeast1 \
    --description=selfhost-gitea \
    --async \
    --disable-vulnerability-scanning \
    --project={PROJECT_ID} # プロジェクトIDを指定

デプロイ用のDockerfile作成

次に実際にselfhost-gitea-repository へpushするDockerイメージ用のDockerfileを作成します。最終的に以下のようなDockerfileを作成しました。

FROM alpine AS litestream-builer

ARG LITESTREAM_VERSION=0.3.13

ENV LITESTREAM_VERSION=${LITESTREAM_VERSION}
ADD "https://github.com/benbjohnson/litestream/releases/download/v${LITESTREAM_VERSION}/litestream-v${LITESTREAM_VERSION}-linux-amd64.tar.gz" /tmp/litestream.tar.gz
RUN tar -C /usr/local/bin -xzf /tmp/litestream.tar.gz

FROM docker.gitea.com/gitea:1.24.0-rootless

ENV GCS_BUCKET_URL=gcs://selfhost-gitea/gitea.db

COPY --from=litestream-builer /usr/local/bin/litestream /usr/local/bin/litestream
COPY docker-entrypoint.sh /
COPY litestream.yml /etc/litestream.yml

ENTRYPOINT ["/docker-entrypoint.sh"]

やっている事としては gitea のDockerイメージに litestream-builer のフェーズで準備したLitestreamのバイナリをコピーして使えるようにしてます。

docker-entrypoint.sh は以下になります。

#!/bin/bash
set -e

echo "starting restore"

echo "gcs url >> ${GCS_BUCKET_URL}"

if [ -f /var/lib/gitea/data/gitea.db ]; then
  echo "Database already exists"
else
  echo "Database does not exist. Creating..."
  litestream restore -if-replica-exists -o /var/lib/gitea/data/gitea.db "${GCS_BUCKET_URL}" 
fi

echo "done with restore. starting litestream"

exec litestream replicate \
     -exec "/usr/local/bin/gitea -c ${GITEA_APP_INI:-/etc/gitea/app.ini} web --port ${PORT}"

最後のコマンドでは直接 gitea web コマンドを実行し、その際にportを指定するようにしています。

またCloud Storageへ向けた設定の litestream.yml は以下になります。

dbs:
  - path: /var/lib/gitea/data/gitea.db
    replicas:
      - url: ${GCS_BUCKET_URL}

DockerイメージをPush

準備ができたので早速DockerイメージをビルドしてArtifact RegistryリポジトリへPushしていきます。 {PROJECT_ID} は適宜置き換えて下さい。

# ビルド
$ docker build -t asia-northeast1-docker.pkg.dev/{PROJECT_ID}/selfhost-gitea-repository/gitea --platform amd64 .
# 認証とPush
$ gcloud auth configure-docker asia-northeast1-docker.pkg.dev --project={PROJECT_ID}
$ docker push asia-northeast1-docker.pkg.dev/{PROJECT_ID}/selfhost-gitea-repository/gitea

Cloud Run デプロイ

最後にCloud Runへデプロイしていきます。

gcloud run deploy selfhost-gitea \
    --image=asia-northeast1-docker.pkg.dev/{PROJECT_ID}/selfhost-gitea-repository/gitea \
    --region=asia-northeast1 \
    --allow-unauthenticated \
    --project={PROJECT_ID} \
    --port=8080 \
    --add-volume=name=gitea-config,type=cloud-storage,bucket=selfhost-gitea-config \
    --add-volume-mount=volume=gitea-config,mount-path=/etc/gitea
    --max-instances=1 \
    --min-instances=1

デプロイが無事完了したら発行されたURLへアクセスします。するとローカルでも設定した初期設定画面が表示されるかと思います。データベースのタイプを SQLite3 にして、必要あればサイトタイトルも変更しときます。

image7.png

他はデフォルトのまま「Giteaをインストール」をクリックします。

「インストール中です、お待ちください…」から進まない!?

image8.png

ここで何故か app.ini をCloud Storageにボリュームマウントしとかないと先に進まず /user/login404 でずっとアクセスできないという現象が起きました… 調べても原因が分からずだったのでご存じの方がいれば共有してもらえると助かります!

無事先に進むと以下の画面が表示され、あとは普通にGiteaを使えます。。。が、再デプロイを行うと永続化しとかないといけない箇所が足りてなくて、使える状態にはなりませんでした。。

image9.png


試しにCloud Storage のボリューム マウントを/var/lib/giteaと/etc/giteaで使ってみる

ローカルで /var/lib/gitea/etc/gitea をボリュームマウントしてた様に、Cloud Storageのボリュームマウントで同じようにやったらどうなるか? を試してみました。

selfhost-gitea-configselfhost-gitea-data というBucketを作成し、Cloud Run デプロイ時にボリュームマウントを指定してみます。

gcloud beta run deploy selfhost-gitea \
    --image=asia-northeast1-docker.pkg.dev/{PROJECT_ID}/selfhost-gitea-repository/gitea \
    --region=asia-northeast1 \
    --allow-unauthenticated \
    --project={PROJECT_ID} \
    --port=8080 \
    --set-env-vars=DISABLE_SSH=true \
    --set-env-vars=START_SSH_SERVER=false \
    --max-instances=1 \
    --min-instances=1 \
    --add-volume=name=gitea-config,type=cloud-storage,bucket=selfhost-gitea-config \
    --add-volume-mount=volume=gitea-config,mount-path=/etc/gitea \
    --add-volume=name=gitea-data,type=cloud-storage,bucket=selfhost-gitea-data,mount-options="dir-mode=777;file-mode=666" \
    --add-volume-mount=volume=gitea-data,mount-path=/var/lib/gitea

selfhost-gitea-data の方には mount-options で一応パーミッションを変更しています。その為コマンドが gcloud beta run になっています。

実際にデプロイしてみるとエラーになって落ちてしまいます。ログには以下のエラーが出力されていました。

code.gitea.io/gitea/modules/git.InitFull(ctx) failed: failed to set git global config gc.reflogexpire, err: exit status 4 - error: chmod on /var/lib/gitea/data/home/.gitconfig.lock failed: Operation not permitted

chmod を実行しようとして Operation not permitted で落ちているようです 👀

これは Cloud Storage FUSE がPOSIX に準拠しておらず以下の制限事項がある為失敗しているようです。

https://cloud.google.com/storage/docs/gcs-fuse?hl=ja#expandable-1

Cloud Storage FUSE は、ファイルのロックやファイルへのパッチ適用をサポートしていません。バージョン管理システムはファイルのロックとパッチ適用に依存しているため、バージョン管理システムのリポジトリを Cloud Storage FUSE のマウント ポイントに保存しないでください

という事で今回のユースケースだとボリュームマウントは使えない事が分かりました。

まとめ

自分のやり方がマズかっただけで、工夫すれば Cloud Run で Gitea使える様になるのかもしれませんが、色々やらないといけない事を考えると素直に別の方法でGitea運用するのが一番良さそうですw

Discussion