DISABLE_REGISTRATION = true。アカウントを作るのはあなただけであり、誰もセルフサービスでは作れない。時々サインアップが必要になるなら、門戸を開くのではなく招待フローを使うこと。

あなた自身の Git サーバーを。
リポジトリは組織図である。
プライベートリポジトリは、あなたが何を作っているか、誰と作っているか、どこまで進んでいるかを記録した日付入りの記録であり——プラットフォーム上では、その記録は他人の法域にある一企業が保持している。それを匿名で借りたサーバーへ移す6つのステップ——Caddy の背後の Forgejo、その下の PostgreSQL、専用ポートでの SSH プッシュ、本番のシークレットを一切目にしない Actions ランナー、そして実際に復元したことのあるバックアップ。サーバーは月額$3.90。Debian 12 で検証済み。
- 01
選ぶ
ベアリポジトリ、Forgejo、GitLab
- 02
プロビジョニング
起動前に決めるポート
- 03
デプロイ
Forgejo、Postgres、Caddy
- 04
プッシュ
SSH、デプロイキー、署名
- 05
ビルド
距離を置いたランナー
- 06
Keep
ミラー、バックアップ、アップグレード
コードは最も興味を引かない部分だ。資産はメタデータである。
セルフホスト型 Git をめぐる議論のほとんどはソースコードについての議論だが、ソースコードこそ最も重要度の低い部分である。その大半は取り立てて言うほどのものではなく、多くはオープンソースライブラリの薄い層にすぎず、明日漏洩したとしても実際の被害はたいてい実害というより気まずさで済む。だからこそ「自分のコードは秘密ではない」は完全な答えのように感じられ、そして完全な答えではないのだ。
コードを取り巻くものこそが興味深い部分である。リポジトリとは、日付が入り、帰属が明らかで、高解像度な仕事の記録だ——いつ眠っているかを物語るコミットのタイムスタンプ、誰と働いているかを物語る共著者の行、誰も発表していない製品を物語るブランチ名、何がどれほど壊れているかを物語るイシュートラッカー、そしてプロジェクト全体の開始時期を刻む最初のコミット。そういう目で百のリポジトリを読めば、もはやソフトウェアを読んでいるのではない——一つの企業と、そのロードマップと、その人員規模と、その苦境を読んでいるのだ。
プラットフォーム上では、その記録は意味のある形であなたのものではない。それはあなたが制御していないインフラの上に、検証済みの身元に紐づけられたアカウントの下に、法的手続きが及ぶ法域の中に置かれ、その範囲が改定されうる文書によって定められたシステムによってスキャンされる。そのいずれも誰かの悪意を必要としない。トラッカーを借りるとはそういうことだというだけだ。4つの構成と、それぞれが実際に明け渡しているものを見てみよう。
| リポジトリがどこにあるか | 誰が履歴を保持しているか | 法的身元との紐付け | どう失敗するか |
|---|---|---|---|
| プラットフォーム、個人アカウント | プラットフォーム、自らの法域にて | あり——メール、請求情報、しばしば電話番号も | 1回の停止処分ですべてのリモートが道連れになる |
| プラットフォーム、組織アカウント | プラットフォーム、それに組織を管理する者 | あり、しかも雇用主にまで | オフボーディングで自分自身の成果物へのアクセスを失う |
| セルフホスト、ハイパースケーラー上 | あなた自身、アプリケーション層において | あり——請求アカウントそのものが身元になる | スナップショットは、閉鎖されうるアカウントの中にある |
| セルフホスト、KYC 不要の VPS 上 | あなた、あなただけ | なし——身分証もカードも不要、暗号通貨で決済 | あなたが今やシステム管理者だ。不良なバックアップは取り返しがつかない |
3行目は、すでにこれを解決したと思っているチームが引っかかる行である。法人カードで、検証済みの企業アカウントの下に借りたマシンに自分でインストールしたフォージは、アプリケーションをプラットフォームの外に移しただけで、身元はそっくりそのまま元の場所に残している。それは制御という点では本物の前進だが、露出という点では何一つ前進していない。
そして4行目は、自らの代償を率直に述べている。このガイドの残りの大部分は、その代償を払うことについてのものだからだ。セルフホスティングの失敗モードは監視ではなく、喪失である。プラットフォームは、あなたが考えたこともないバックアップを保持している。あなたのサーバーは、あなたが設定したバックアップしか保持しない。ステップ06 は最後に添える任意の仕上げではない——他の5つのステップを安全にするためのステップなのだ。
どこまでのフォージが必要か?正直な答えは「不要」であることも多い。
問題を解決する最小のものから始めること。その上に積むレイヤーはどれも、自分で運用しなければならないサービスになるからだ。SSH 経由のベアリポジトリは、すでに持っているサーバー上のコマンド一つで済み、認証、履歴、プッシュ権限を備えた本物の Git リモートを与えてくれる。Web アプリケーションもデータベースもアップグレードの踏み車もなく、sshd 以外にパッチを当てるものもない。ノート PC とプラットフォームからコミットを解放したいだけの一人であれば、これで完全な答えになり、ステップ02 の後は読むのをやめてよい。
# On the server, as an unprivileged user:
mkdir -p ~/git/project.git && cd ~/git/project.git
git init --bare
# On your laptop:
git remote add origin ssh://user@git.example.com/~/git/project.git
git push -u origin main
フォージが必要になるのは、二人目が関わってくるとき、あるいはベアリポジトリのディレクトリではできない4つのこと——ブラウザで履歴を閲覧すること、変更をプルリクエストとしてレビューすること、コードのそばでイシューを追跡すること、そして一人の人物に一つのリポジトリへのアクセスを許可すること——のいずれかを望むときだ。それは能力における本物の飛躍であり、運用上の重さにおいても本物の飛躍である。慎重に選ぶこと。
| 項目 | SSH 経由のベアリポジトリ | Forgejo | Gitea | GitLab CE |
|---|---|---|---|---|
| Web UI、プルリクエスト、イシュー | なし | はい | はい | あり、それもはるかに多く |
| アイドル時のメモリ | 実質ゼロ | 数百 MB | 数百 MB | CI を除いても数 GB |
| 組み込み CI | 自分で書けば git hooks | Forgejo Actions | Gitea Actions | GitLab CI、成熟した方 |
| 誰がプロジェクトを舵取りするか | Git プロジェクト | Codeberg e.V.、非営利団体 | 営利企業、オープンコア | 上場企業、オープンコア |
| ライセンス | GPLv2 | v9.0 以降は GPLv3+(それ以前は MIT) | MIT、プロプライエタリなアドオン付き | MIT コア、プロプライエタリなティア |
| 選ぶ場面 | 一人で、メンテナンスをゼロにしたい | 小さなサーバーで本物のフォージが欲しい | すでに運用していて、うまくいっている | エンタープライズ機能一式が必要 |
Forgejo と Gitea は祖先を共有しており、日常の使用では今も見分けがつかないほどよく似ている。Forgejo は2024年初頭にハードフォークとなり、ベルリンに登録された非営利団体 Codeberg e.V. が保持している。Gitea は、自らの商標を所有しコントリビューターに著作権の譲渡を求める営利企業によって開発されている。どちらも良いソフトウェアだ。ただし、その運営者のインセンティブがあなたがこのページを読んでいる理由と一致するのは、片方だけである。
このガイドの以降ではForgejoを使用する。ステップ02 から06 までの内容は、ほぼそのまま Gitea にも当てはまる——変わるのはコンテナ名といくつかの環境変数だけで、形そのものは変わらない。
フォージに代わりに決められてしまう前に、ポートは自分で決めておく。そのうち2つは SSH だ。
まずサーバーを注文する。パネルではOrder → VPS → Sentinel、イメージは Debian 12、履歴を置きたい拠点はどこでもよい——Helsinki、Stockholm、Oslo、Reykjavík。アカウント開設にメールアドレスは不要で、請求は Monero、Bitcoin、Lightning、その他対応する任意の資産で決済される。これはたいていのワークロード以上に、ここでは重要な意味を持つ——フォージとは、あなたの仕事の記録が名前に紐づいていないことそのものが価値提案であるサービスなのだから。
次に最初の1時間のハードニングチェックリストを実行する——移動したポートでの鍵のみの SSH、自動アップデート、デフォルト拒否のファイアウォール。以下のすべては、まだ他の誰のものにもなっていないサーバーであることを前提にしているからだ。
フォージはログインの付いた静的サイトではない。コード実行エンジンが取り付けられた公開 Web アプリケーションである。放っておくと自由に使われてしまう2つのデフォルト設定がある——証明書が透明性ログに現れてから数日のうちにインスタンスをスパムアカウントで満たす開かれた登録と、プルリクエストを開ける誰からのワークフローでも実行してしまう CI で、これがセルフホストのランナーが他人のために暗号通貨をマイニングする羽目になる経緯だ。どちらも巧妙な攻撃ではなく、どちらも珍しくもない。両方とも、ステップ03 とステップ05 でそれぞれ1つの設定によって閉じられる——ただし今のうちに閉じると決めておくこと。「動いている」と「利用されている」の間の窓は、日単位で測られるのだから。
サーバーにホスト名を向ける——A レコード、そして IPv6 経由で到達可能にしたいなら AAAA レコードも——そして始める前に伝播を待つこと。リバースプロキシは初回起動時にその名前の証明書を取得しようとするため、チャレンジの失敗はつまずきの元になる。そのあと、まだ何も待ち受けていないうちにファイアウォールルールを書く。
二つの SSH 問題。 フォージは Git のために SSH を話し、あなたのサーバーはすでに管理のために SSH を話している。両者が同時にポート22を占有することはできず、それらを混在させると、Git まで理解しなければならない管理用デーモンができあがってしまう。分離しておくこと——管理はハードニングガイドで移動したポートで、Git は専用の公開ポートでコンテナへマッピングする。慣例的には2222が選ばれるが、それに神聖な意味はない。
ufw default deny incoming
ufw allow 47222/tcp # administrative SSH, moved
ufw allow 80/tcp # ACME HTTP-01 challenge
ufw allow 443/tcp # the web interface
ufw allow 2222/tcp # git over SSH, into the container
ufw enable
ufw status numbered
# Nothing else is open. In particular: PostgreSQL stays on the Docker
# network and is never published to the host, let alone the internet.
Docker はディストリビューションのパッケージではなく公式リポジトリからインストールし、Compose v2 が入っていて、かつバージョンが最新であるようにする。そのあとスタック用のディレクトリを作成し、サーバー上で状態を保持する唯一の場所にする——それがステップ06 でバックアップするディレクトリである。
curl -fsSL https://get.docker.com | sh
docker compose version
install -d -m 0750 /srv/forgejo
cd /srv/forgejo
3つのコンテナ。そのうち1つは、絶対に到達可能であってはならない。
Forgejo、PostgreSQL、Caddy。Forgejo はリポジトリを保持しインターフェースを提供し、PostgreSQL は Git オブジェクトでないものすべてを保持し、Caddy は TLS を終端し、二度と尋ねられることなく証明書を取得する。PostgreSQL は意図的にホストへ公開されていない——それを必要とするただ1つのコンテナから、名前を介して Compose ネットワーク経由でのみ到達可能である。
/srv/forgejo/.env
FORGEJO_DOMAIN=git.example.com
FORGEJO_VERSION=15 # the LTS line; 16 is current stable
POSTGRES_PASSWORD=$(openssl rand -base64 32) # paste the value, do not leave the $( )
/srv/forgejo/docker-compose.yml
services:
forgejo:
image: codeberg.org/forgejo/forgejo:${FORGEJO_VERSION}
container_name: forgejo
environment:
USER_UID: 1000
USER_GID: 1000
FORGEJO__database__DB_TYPE: postgres
FORGEJO__database__HOST: db:5432
FORGEJO__database__NAME: forgejo
FORGEJO__database__USER: forgejo
FORGEJO__database__PASSWD: ${POSTGRES_PASSWORD}
FORGEJO__server__DOMAIN: ${FORGEJO_DOMAIN}
FORGEJO__server__ROOT_URL: https://${FORGEJO_DOMAIN}/
FORGEJO__server__SSH_DOMAIN: ${FORGEJO_DOMAIN}
FORGEJO__server__SSH_PORT: "2222" # what clones will say
FORGEJO__server__SSH_LISTEN_PORT: "22" # what the container listens on
FORGEJO__service__DISABLE_REGISTRATION: "true"
FORGEJO__repository__DEFAULT_PRIVATE: private
FORGEJO__security__INSTALL_LOCK: "true"
restart: unless-stopped
volumes:
- ./data:/data
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
ports:
- "127.0.0.1:3000:3000" # web, reached only by Caddy
- "2222:22" # git over SSH, public
depends_on:
- db
db:
image: postgres:16-alpine
container_name: forgejo-db
environment:
POSTGRES_USER: forgejo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: forgejo
restart: unless-stopped
volumes:
- ./postgres:/var/lib/postgresql/data
# No ports: block. This is the point.
caddy:
image: caddy:2-alpine
container_name: forgejo-caddy
restart: unless-stopped
network_mode: host
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile:ro
- ./caddy-data:/data
- ./caddy-config:/config
/srv/forgejo/Caddyfile
git.example.com {
encode zstd gzip
request_body {
max_size 512MB # raise it if you push large repositories
}
reverse_proxy 127.0.0.1:3000
}
この2つの SSH の行は、みなが間違えるところなので、二度読む価値がある。SSH_LISTEN_PORTはコンテナの内側で、Forgejo 組み込みの SSH サーバーがバインドするポートだ。SSH_PORTは Forgejo が表示するクローン URL に印字するポートで——ポートマッピングの公開側と一致していなければならない。この2つが食い違うと、ユーザーがコピー&ペーストする、まさにその一点を除いてすべてが動いてしまう。
起動し、それからコマンドラインで管理者を作成する。 Web インストーラーは使わないこと。まっさらなインスタンスでは、ホスト名を見つけた誰からでも到達可能であり、それを通じて最初に作成されたアカウントが管理者になってしまう——参加する必要のない競争だ。上のファイルにあるINSTALL_LOCKが、コンテナが起動する前にそれを閉じてくれる。
docker compose up -d
docker compose logs -f forgejo # wait for "Starting new Web server"
docker compose exec -u git forgejo forgejo admin user create \
--admin --username you --email you@example.com --random-password
ブラウザでホスト名を開き、表示されたパスワードでサインインしてから変更する。次に、動作するインスタンスを閉じたインスタンスへと変える設定を一通り確認する——ほとんどは上の環境変数ブロックですでに設定済みだが、思い込みではなく一つひとつ確認する価値がある。
DEFAULT_PRIVATE = private。新規リポジトリは非公開の状態で始まる。公開はそれによって、デフォルトの結果ではなく意図的な行為になる。
REQUIRE_SIGNIN_VIEW = trueは、ログアウトした訪問者からは公開リポジトリすら隠す。非公開のチーム用フォージには適切だが、何かを公開したいなら不適切だ。
何よりも先に、管理者アカウントでこれを有効にすること。フォージは今や、あなたの持つすべてのブランチを書き換えられるマシンなのだから。
覚えておく価値のあるパスが一つ——実運用中の設定はホスト上の./data/gitea/conf/app.iniにある。Forgejo は既存のインストールとの互換性のためにgiteaというディレクトリ名を維持しており、これは誰もが一度だけ驚く点だ。FORGEJO__section__KEYという形式の環境変数は起動時にそのファイルへ書き込まれるため、Compose ファイルから設定全体を管理できる——ただしapp.iniを手で編集した場合、同じキーの環境変数は次回の再起動時にその編集を上書きしてしまう。どちらか一方を選び、一貫させること。
SSH 経由でプッシュする。そしてそれに署名し、履歴がサーバーに依存しないようにする。
プロフィール設定に公開鍵を貼り付け、それからクライアントにフォージの場所を教える。Git は標準でないポートにあるため、SSH 設定に1つエントリを加える30秒は価値がある——それによって、今後入力するすべての URL からポート番号を排除でき、リモートの持ち運びもしやすくなる。
# ~/.ssh/config
Host git.example.com
Port 2222
User git
IdentityFile ~/.ssh/id_ed25519
# Then, in any repository:
git remote set-url origin git@git.example.com:you/project.git
git push -u origin main
ssh -T git@git.example.com # expect a greeting, not a shell
最後のそのコマンドこそが、ポートマッピング、コンテナの SSH サーバー、そしてあなたの鍵がすべて噛み合っているかどうかを教えてくれるものだ。Forgejo からの挨拶が返ってくれば、チェーン全体が機能している証拠だ。パスワードの入力を求められた場合は、代わりにホストの管理用 sshd に到達してしまっているということであり、ポートマッピングが誤っている。
次は、コミットに署名する番だ。 これはたいていのセルフホスティングガイドが省略するステップであり、セルフホストしたフォージでは、プラットフォーム上よりも重要度が増す。コミットの author フィールドは自由記述のテキストであり、プッシュできる者なら誰でもそこにあなたの名前を書き込める。署名とは、特定の鍵が特定のコミットを作ったという証明であり、プッシュ先のサーバーが侵害されようと、移行されようと、廃棄されようと、証明であり続ける。Git はすでに持っている SSH 鍵で署名できるので、もう PGP へ寄り道する必要はない。
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true
git config --global tag.gpgsign true
git commit --allow-empty -m "signed"
git log --show-signature -1
そして同じ公開鍵を、認証鍵としてではなく署名鍵として、フォージにもう一度追加する。Forgejo はサーバー側で署名を検証し、コミットを検証済みとして表示する。この2回目の登録がなければ、署名をあなたのアカウントに結びつける手段がなく、正しく署名されていてもすべてのコミットが未検証と表示されてしまう。
読み取り専用のマシンには、デプロイキーを。 本番サーバー、ビルドマシン、ミラーには、フォージ上のユーザーアカウントは不要だ。代わりにデプロイキーを与える——1つのリポジトリに追加される鍵ペアで、明示的に書き込みを許可しない限り読み取り専用であり、インスタンス上の他の何にもアクセスを与えない。取り消しは1件を削除するだけだ。これは、セルフホストしたフォージを整然と保つための、単独で最も効果的な習慣である。ユーザーアカウントは権限を蓄積していくが、デプロイキーはそうならないからだ。
プラットフォームからの移行は、これらすべてを正しく行う良い機会だ。Forgejo のNew Migrationフローは、リポジトリの URL とアクセストークンを受け取り、ブランチ、タグ、イシュー、コメント、ラベル、マイルストーン、リリース、プルリクエストを引き寄せる。まず1つのリポジトリで結果の形を確認し、大きなバッチは API レート制限で遅くなることを見込んでおき、移行元はその日のうちに削除するのではなく数週間は読み取り専用のままにしておくこと。
CI ランナーとは、見知らぬ他人のコードを実行するマシンである。それにふさわしい置き場所を与えること。
Forgejo Actions は、GitHub Actions と同じ形式のワークフローファイルを.forgejo/workflows/から読み込み、Forgejo Runnerという別のプログラムに渡す。ランナーはフォージの一部ではなく、同じマシン上に置く必要もない——それがこの設計のすべてであり、このステップに肩をすくめるだけでなく警告が添えられている理由である。
インスタンス上で Actions を有効にし、それから管理パネルで登録トークンを取得する——インスタンス全体に対して、あるいは1つの組織か1つのリポジトリに限定して。確信が持てないときは後者がより良いデフォルトだ。ランナーはフォージへ向けて外向きに接続するため、受信ポートはまったく不要であり、公開アドレスを持たないマシンも含め、好きなファイアウォールの内側に置くことができる。
# In the Compose environment of the forge, or app.ini:
# FORGEJO__actions__ENABLED: "true"
# Then: Site administration -> Actions -> Runners -> Create new runner
# On the runner machine:
forgejo-runner generate-config > config.yml
forgejo-runner register --no-interactive \
--instance https://git.example.com \
--token <REGISTRATION_TOKEN> \
--name runner-01 \
--labels docker:docker://node:22-bookworm,debian:docker://debian:12
forgejo-runner --config config.yml daemon
ラベルはワークフローとランナーの間の契約である——runs-on: docker を要求するジョブは、右辺のイメージからビルドされたコンテナを得る。実際に提供する準備があるラベルだけを宣言すること。誰も提供していないラベルを要求するジョブは、失敗するのではなく永遠に待ち続けてしまい、長い一日の終わりにデバッグするには紛らわしいものだからだ。
.forgejo/workflows/ci.yml
on: [push, pull_request]
jobs:
test:
runs-on: docker
container:
image: node:22-bookworm
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
ランナーはワークフローファイルに書かれていることを何でも実行し、そのワークフローファイルはプルリクエストとしてやってくる。チームの外の誰かがプルリクエストを開けるのであれば、ランナーはフォージとは別のマシンに置くべきだ——月額$3.90の2台目の Sentinel に、フォージのデータディレクトリへのアクセスもデータベースの認証情報もなく、ビルドログに映って困るようなものが環境に何もない状態で。フォージ自体では、初めての貢献者に対してはワークフロー実行前に承認を必須にすること。標準的な2つの失敗は、どちらも地味でどちらも高くつく——プルリクエストに埋め込まれたマイニング作業と、誰かがわざと加えたステップによって出力されるシークレットだ。
シークレットも同じ考え方でスコープを絞る。Forgejo はそれらをリポジトリ、組織、インスタンスの各レベルで保管する。1つのリポジトリだけが必要とするデプロイキーは、そのリポジトリだけに存在させるべきであり、無関係なプロジェクトの侵害されたワークフローがそれを読み取れないようにする。対象がサポートしているならどこでも、長命なトークンより短命なトークンを優先し、ワークフローファイルには決してシークレットを置かないこと——ワークフローファイルはリポジトリの中にあり、リポジトリには履歴があるのだから。
ビルドが毎回同じベースイメージを取得するなら、キャッシュしておくこと。ローカルのレジストリミラーや永続的なランナーキャッシュは、2分かかるイメージ取得を2秒に変えてくれるし、酷使しているアップストリームのレジストリに対する礼儀でもある。そしてこの設計全体が誘っているパターンに注目してほしい。それがこのガイドがエージェントランタイムガイドの隣にある理由でもある——ランナーはあなたのフォージからジョブを取得する汎用ワーカーであり、定期メンテナンス、データパイプライン、エージェントのワークロードもまた、そこに属するものなのだ。
クローンはどれもコードのバックアップである。会話のバックアップになるものは何もない。
Git はここで異例なほど寛容であり、その理由を正確に理解しておく価値がある。クローンはどれも履歴の完全な複製なので、一人の開発者が最新の作業コピーを持っている限り、コードはサーバーを生き延びる。これは正真正銘、安心できる話であり、フォージが保持する2つのもののうち、ちょうど片方をカバーしている。もう一方——イシュー、プルリクエストの議論、レビュースレッド、リリース、Wiki ページ、ユーザー、チーム、権限、CI の履歴——はデータベースの中にしか存在せず、誰もそのクローンを持っておらず、たいていは3年かけて積み上がった部分である。
まずはプッシュミラーから。無料で、勝手に動いてくれる。 リポジトリ内で、Settings → Mirror settings → プッシュミラーを追加し、送信先の URL とアクセストークンを入力する。以降 Forgejo は、プッシュのたびにすべてのブランチとタグを、第二の Forgejo へ、Codeberg へ、あるいはホームではなくショーウィンドウとして残すと決めたプラットフォームへ複製する。はっきり述べておくべき注意点が一つ——プッシュミラーは履歴を忠実に再公開するため、削除したはずのシークレットも含まれてしまう。
次に本物のバックアップを——組み込みのアーカイブではないものを。 Forgejo にはforgejo dumpコマンドが付属しており、設定、リポジトリ、データ、データベースダンプを1つの zip にまとめてくれる。バグを報告する際にメンテナーへ渡すには、正真正銘役に立つものだ。しかし、そこから蘇ることを頼みにすべきものではない——生成される SQL には再インポート時の長年知られた問題があり、そのアーカイブは実行のたびにリポジトリ一式全体を書き直すため、実運用中のインスタンスでは毎晩ゼロから大きなファイルを書き出すことを意味する。代わりに、地味だが検証可能な道を使うこと——データベース自身のツールでダンプを取り、データディレクトリはファイルとしてバックアップする。
#!/bin/sh
# /usr/local/bin/forgejo-backup — run from a systemd timer, nightly
set -eu
cd /srv/forgejo
# 1. Consistent database dump, written next to the data.
docker compose exec -T db pg_dump -U forgejo -Fc forgejo > ./db.dump
# 2. Everything that matters, to an encrypted offsite repository.
restic backup ./data ./db.dump ./docker-compose.yml ./.env ./Caddyfile
# 3. Retention, then prove the repository is still readable.
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic check
フォージとは異なる法域にあるマシンへ、追記専用のリポジトリ経由で送る。フォージが侵害されても自らの履歴を消せないようにするためだ——暗号化バックアップガイドは、systemd タイマーや追記専用の鍵制限を含む、上の3行の詳細版である。
そして一度、意図的に復元してみる。 検証用の VPS を注文し、そこへスナップショットを復元し、復元したデータディレクトリに対して同じ Compose ファイルを起動し、データベースダンプを読み込み、hosts ファイルのエントリをそこに向け、サインインする。全体の所要時間を計ること。バックアップジョブが存在するという事実ではなく、その数字こそが、あなたの実際の復旧態勢である。そしてこの演習を行った誰もが、最初の一回では include リストから何かが漏れていたことに気づいている。
# restic keeps absolute paths, so the stack reappears under the target.
restic restore latest --target /srv/restore
cd /srv/restore/srv/forgejo
docker compose up -d db # empty volume: the entrypoint creates the database
docker compose exec -T db pg_restore -U forgejo -d forgejo \
--clean --if-exists --no-owner < db.dump
docker compose up -d # then sign in and walk the issue tracker
アップグレードについて、手短に。 イメージは:latestではなくメジャーバージョンに固定し、寝ている間に無人のプルが破壊的変更の向こう側へあなたを連れて行かないようにする。メジャーバージョンをまたぐ前にリリースノートを読み、まずスナップショットを取り、新バージョンの初回起動時にデータベースのマイグレーションが走ることを忘れないこと——それは一方通行の扉だ。Watchtower のような自動更新は、静的サイトには良い考えだが、あなたの持つすべてのブランチを保持するマシンには不向きである。
完了と呼ぶ前の4つの確認。 サインアウトし、インスタンスがアカウントを作成する手段を提供していないことを確認する。セッションのないブラウザから、非公開リポジトリが単に禁止されているのではなく見えなくなっていることを確認する。ランナーがフォージのデータディレクトリを読み取れないことを確認する。そして直近の restic チェックが成功していることを確認する——静かに失敗するバックアップジョブは、必要になるその日まで、誰もが抱えているものなのだから。
あなたは一つの当事者を取り除いた。建物を取り除いたわけではない。
フォージをセルフホストすることは、あなたの成果物を誰が握るかという点での本物の変化であり、不可視になることではない。上のすべてを終えたあとも真実であり続ける4つのことがあり、それを知っているかどうかが、防御と自分に言い聞かせる物語との違いになる。
ディスクは誰かの建物の中にある。 レンタルサーバーは、扉とスタッフのいるデータセンターのハードウェア上で動いている。NordBastion は顧客のワークロードを検査しないし、透明性レポートとワラントカナリアが、もし何かを要求された場合に何が起こるかを説明している——しかし、正直なのはアーキテクチャ上の言明のほうだ。変わるのは、あなたのリポジトリの製品レベルのインデックスも、あなたのコミットに対する分析パイプラインも、改定されうるプライバシーポリシーも存在しないということ。変わらないのは、マシンが物理的であり、あなたの家の中にはないということだ。
証明書が発行された瞬間、ホスト名は公開される。 信頼された証明書はすべて、誰でも検索できる証明書透明性ログに公開されるため、一度もリンクしたことがなくても git.yourproject.com は発見可能だ。それはコードの漏洩ではないが、プロジェクトが存在し、おおよそいつ始まったかという事実の漏洩ではある。すでに公開している名前のサブドメインを使うか、公開証明書を完全に省略して WireGuard かオニオンサービス経由でフォージに到達すること。
あなたは今や、Web アプリケーションにパッチを当てる立場にある。 フォージは、ログインページ、信頼できないマークアップのレンダリングパイプライン、SSH サーバー、CI エンジンであり、そのすべてがインターネットから到達可能だ。それはステップ01 のベアリポジトリよりもかなり大きな表面積であり、常に最新の状態を保たなければならないものでもある。リリースアナウンスを購読し、セキュリティリリースは四半期単位ではなく週単位の仕事として扱うこと。
フェデレーションはまだここにはない。 Forgejo はフォージ間フェデレーションについて実際に作業を進めており、その進捗も公開しているが、今日の時点では、共有プラットフォームでできるように自分のインスタンスから他人のインスタンスへプルリクエストを開くことはできない。実務上それは、外部の貢献者には依然としてあなたのフォージ上のアカウントが必要であるか、パッチをメールで受け取るか、ふらりと現れる貢献のために公開ミラーを維持するか、のいずれかを意味する。驚かされるのではなく、この3つのうち自分のプロジェクトに合うものを計画しておくこと。
実際に手に入れたものを正確に言えば——あなたが何を、誰と、いつ作っているかという日付入りの記録は、それを分析することがビジネスモデルに含まれる企業にはもはや握られていない。それは限定的な主張であり、同時に本物の主張でもある。それがその代償に見合うのは、ステップ06 が完了している場合に限られる——検証済みの復元を持たないセルフホストのフォージは、プライバシーの改善ではなく、ブランディングだけが優れた単一障害点である。
フォージは小さい。CI はそうではない。
| ティア | 月額 | リソース | 無理なく動かせるもの |
|---|---|---|---|
| Sentinel | $3.90 | 2 vCPU · 4 GB · 120 GB | 一人、あるいは小規模チーム向けの Forgejo + PostgreSQL。正しい出発点。 |
| 2台目の Sentinel | $3.90 | ランナーは、専用のサーバーに | コーヒー1杯分の値段で手に入る、本当に意味のある分離 |
| Garrison | $7.90 | 4 vCPU · 8 GB · 240 GB | 10人規模のフォージ、コンテナレジストリとアーティファクト |
| Ravelin | $16.90 | 8 vCPU · 16 GB · 480 GB | 多忙な CI、大きなリポジトリ、複数の並列ジョブ |
この表のどれも、Git のためのサイズではない。Forgejo と PostgreSQL は合わせてもアイドル時に数百メガバイトしか使わない。サーバーを埋め尽くすのは CI——コンテナのレイヤー、ビルドキャッシュ、アーティファクト——であり、ソースコードではないリポジトリの内容だ。まずディスク、次にコア数、最後にメモリの順でサイジングすること。ここでは小さなサーバー2台が大きな1台に勝る。2台目は容量のためではなく、セキュリティ境界のためのものだからだ。
これが失敗する6つの経路。そのうち2つはお金がかかる。
開かれた登録、そして見知らぬ他人だらけのフォージ
証明書が透明性ログに現れ、スキャナーが数日のうちに追随し、サインアップを有効にしたインスタンスはスパムアカウントとスパムリポジトリで満たされる。DISABLE_REGISTRATION は最初のインシデントの後ではなく、最初の起動の前に設定すること。
誰かのために暗号通貨をマイニングするランナー
どんなプルリクエストからのワークフローでも実行してしまう CI は、それを見つけた誰にとっても無料の計算資源だ。初めての貢献者には承認を必須にし、ランナーは専用のサーバーに置き、ジョブの実行時間をたまに確認すること。
ROOT_URL の誤り、そして中途半端に動くすべて
クローン URL が誤ったホストを指し、Webhook が誤ったアドレスへ発火し、OAuth のコールバックは失敗し、アバターは壊れる。インターフェースは問題なく見えるので、誰も1行の設定を疑わない。DOMAIN、ROOT_URL、SSH_DOMAIN、SSH_PORT はまとめて設定し、UI でクローン URL を確認すること。
2つの SSH サーバー、1つのポート
コンテナは22番を欲しがるが、ホストはすでにそれを使っており、マッピングは静かに予期しない場所へ着地し、git push はパスワードを求めてくる。まず管理用の SSH を移動し、コンテナは専用のポートで公開し、ssh -T で確認すること。
一度も復元されたことのないダンプ
組み込みのアーカイブはサポート用の成果物であり、検証済みの復旧経路ではない。その SQL には再インポート時の既知の問題がある。pg_dump でデータベースをダンプし、データディレクトリはファイルとしてバックアップし、全体を一度、検証用のサーバーへ復元してみること。
ディスクを埋めるのはコードではなくビルドのレイヤー
ソースコードはごくわずかだが、コンテナのレイヤー、アーティファクト、CI キャッシュはそうではない。意欲的なパイプラインを持つ120 GB のサーバーは、数週間で満杯になる。イメージはタイマーで剪定し、アーティファクトの保持期間には上限を設け、CPU よりもディスクを見張ること。
質問、回答済み。
これを構築する過程で出てくる11の質問——そもそも構築すべきかどうかを左右する2つから始める。
Forgejo か Gitea か——実際にはどちらをインストールすべきか?
大半の人には Forgejo であり、理由は機能ではなくガバナンスにある。両プロジェクトは祖先を共有しており、使い勝手は今も驚くほど似ている——同じ Go バイナリ、同じ軽量なフットプリント、同じ Web インターフェース、遜色ない CI。異なるのは誰がそれを舵取りしているかだ。Forgejo はベルリンに登録された非営利団体Codeberg e.V.が保持しており、2024年初頭にハードフォークとなった。すべてをフリーソフトウェアとして公開し、コントリビューターに著作権の譲渡を求めない。Gitea は商標とドメインを所有する営利企業によって開発されており、コントリビューターに著作権の譲渡を求め、無料の基盤の周りに有料機能を配置するオープンコアモデルで運営されている。これらのどちらも、ソフトウェアとしての Gitea を損なうものではない。しかし、リポジトリをプラットフォームから移す目的の核心は、一企業のロードマップに依存するのをやめることにある。だからこそ、非営利団体が管理するフォークを選ぶことが、あなたがここに至った理由に合致する選択なのだ。後で乗り換える場合の実務上の注意を一つ——Forgejo は既存の Gitea のデータディレクトリの上にインストールできるが、逆方向はサポートされていない。
そもそもフォージは必要なのか?SSH 経由のベアリポジトリでは足りないのか?
ノート PC 一台と良いコミットメッセージを書く習慣を持つ一人の人間にとって、ベアリポジトリは正真正銘十分であり、このガイドの中で最も頑丈なものでもある。サーバー上での git init --bare、普通の SSH アカウント、それだけだ——パッチを当てるべき Web アプリケーションもなく、バックアップすべきデータベースもなく、sshd 以外の攻撃対象領域もない。この構成が EOL(サポート終了)を迎えることは決してない。フォージが必要になるのは、二人目の人間が関わってきた瞬間、あるいはベアリポジトリがまったく関知しない4つのもの——ブラウザで閲覧できる履歴、レビューを伴うプルリクエスト、コードのそばにあるイシュートラッカー、リポジトリ単位のアクセス制御——のいずれかを望んだ瞬間だ。それはささやかな一歩の先ではない。保存と協働の違いなのだ。自分がどちらを解決しようとしているのかに正直であること。フォージは今後あなたが運用することになるサービスであり、ベアリポジトリはただのディレクトリなのだから。
GitHub のリポジトリを、イシューやプルリクエストも含めて移行できるのか?
できる。しかもセルフホスティングの中では、比較的良好な部類の移行体験だ。Forgejo 組み込みの移行ツールはリポジトリの URL と個人アクセストークンを受け取り、コード以上のものを引き寄せる——ブランチとタグ、コメント付きのイシュー、プルリクエスト、ラベル、マイルストーン、リリース。読み取りスコープのトークンを与え、まず1つのリポジトリで結果がどう見えるか確認してから、残りに取り掛かるとよい。想定しておくべきことが3つある。大規模あるいは大量の移行は移行元プラットフォームの API レート制限に突き当たり、単純に時間がかかる——開始したら放っておけばよい。イシューの添付ファイルはネットワーク経由で取得されるため、送信を制限するファイアウォールの内側にあるインスタンスでは画像が壊れたイシューができあがる。そしてレビュースレッドは、2つのデータモデルが同一ではないため、必ずしも元の場所にきれいに収まるとは限らない。移行元のリポジトリはその日の午後に削除するのではなく、数週間は読み取り専用のまま残しておく計画を立てておくこと。
セルフホストしたフォージは本当により秘匿性が高いのか、それとも単に違うだけなのか?
一点、具体的かつ重要な意味で、本当に違うものになる——あなた以外の誰もインデックスを保持していないという点だ。プラットフォーム上では、プライベートリポジトリは他のユーザーに対して非公開であるにすぎない。プラットフォーム自身に対しては非公開ではなく、プラットフォームはそれを保存し、スキャンし、そこから依存関係グラフを算出し、自らの法域の裁判所によって開示を強制されうる。そのインデックスは、コードそのものが平凡であっても、驚くほど多くを語る——コミットのタイムスタンプはあなたの作業時間を、共著者はあなたのチームを、ブランチ名は未発表の製品を、そして最初のコミットは発表されていないプロジェクトの開始時期を物語る。自分のサーバーに移すことで、この構図からプラットフォームは完全に姿を消す。しかし、それでもマシンそのものが消えてなくなるわけではない。あなたは依然としてデータセンターのハードウェアを借りており、ディスクはその建物を所有する人々にとって物理的に手の届く場所にあり、選んだホスト名は今も証明書透明性ログの中に残っている。正直に言えば——契約上のプライバシーポリシーを持つ一企業を、あなたの設定だけがポリシーであるサーバーに置き換えたにすぎない。ただしその法域は自分で意図的に選んだものであり、支払いを行うアカウントにあなたの名前を載せる必要もない。
VPS はどのくらいの規模が必要ですか?
GitLab のドキュメントがみなに刷り込んできた予想よりも小さい。Forgejo と Gitea は単一の Go バイナリで、アイドル時の RAM 使用量は数百メガバイト程度。PostgreSQL とリバースプロキシを加えても、個人開発者のフォージは Sentinel——2 vCPU、4 GB、120 GB NVMe、月額$3.90——の中に余裕を持って収まる。負荷がかかるのは他の3つの場所からで、それぞれ意識してサイジングする価値がある。最大のものは CI だ——コンテナをビルドするランナーは与えられたコアをすべて使い切り、レイヤーでディスクを埋め尽くす。2つ目はリポジトリのサイズ——ゲームアセット、ノートブック、ベンダリングされた依存関係はソースコードよりもはるかに速く肥大化する。3つ目はアーティファクトとコンテナレジストリだ。実践で効果が確かめられているルールがある——フォージは Sentinel に置き、ランナーは別の2台目の Sentinel に置き、ティアを上げるのは CPU ではなくディスクがそう告げてきたときだけにする。
Forgejo Actions は本当に GitHub Actions のワークフローと互換性があるのか?
たいていの素直なワークフローが無変更で動く程度には互換性があるが、複雑なパイプラインを中身も読まずに移行してよいほど互換性が高いわけではない。ワークフローファイルの形式は同じで、.forgejo/workflows/に置かれ(.github/workflows/も読み込まれる)、チェックアウト、言語のセットアップ、キャッシュ、マトリクスビルドといった共通の構成要素は、ランナーが同じようにコンテナ内で実行するため期待どおりに動作する。壊れるのは、Actions というフォーマットではなく GitHub というプラットフォームを前提にしているものだ——github.comの URL に固定されたマーケットプレイスのアクション(インスタンスがそれを取得できるよう許可しなければならない)、リリースや PR のメタデータを取得するために GitHub の REST API を呼び出すアクション、非常に特定のツールチェーンがプリインストールされた GitHub ホストのランナーイメージ、そして OIDC ベースのクラウド認証だ。うまくいく移行パターンはこうだ——まずビルドとテストだけを行うワークフローから始め、それをグリーンにしてから、デプロイのステップを1つずつ追加し直す。
VPS が死んだら、コードはどうなるのか?
コードそのものは無事な部分であり、これは Git が他のほぼすべてのセルフホスト型サービスに対して持つ、目立たない優位性である。クローンはどれも履歴の完全な複製なので、一人の開発者が最新の作業コピーを持っている限り、リポジトリが失われることはない。サーバーとともに失われるのは、コードの周りにあるすべて——イシュー、プルリクエストの議論、レビューコメント、リリース、Wiki、ユーザー、アクセスルール、CI の履歴だ。この材料はデータベースの中にしか存在せず、誰もそのクローンを持っておらず、たいていは何年もかけて積み上がった部分である。だから両者は別物として扱うべきだ。コードについては、第二の場所へのプッシュミラーを設定すれば、何の手間もなく継続的かつ自動的なオフサイト保護が手に入る。メタデータについては本物のバックアップが必要になる——PostgreSQL のダンプとデータディレクトリを暗号化し、タイマーで別の法域にあるマシンへ送るのだ。ステップ06 は両方を扱っており、最後にある復元訓練こそが、それらを単なる意図から保証へと変える部分である。
GitHub を完全に離れるのではなく、ミラーとして残しておくことはできるのか?
できる。しかも多くのプロジェクトにとって、それは妥協ではなく正しい答えである。Forgejo はプッシュミラーに対応しており、あなたのインスタンスをチームが作業する原点(origin)としながら、すべてのプッシュが GitHub、Codeberg、あるいは第二の Forgejo 上のリポジトリへ自動的に複製される。公開の可視性、ふらりと現れる貢献者、GitHub の URL を前提としたパッケージレジストリ、古い README にあるイシュートラッカーへのリンク——これらはすべて動き続ける一方で、真実の源——非公開のリポジトリも、CI のログも、公開していないもののイシュー履歴も——はあなたのサーバーの上にとどまる。逆方向にも機能する——プルミラーは依存しているアップストリームプロジェクトのローカルかつ常に最新のコピーを保っておくもので、明日削除されたら困るようなものに対しては行う価値がある。1つだけ慎重になるべきことがある。何をミラーしているかだ——削除したはずのシークレットが履歴に含まれるリポジトリのプッシュミラーは、そのシークレットを即座かつ自動的に再公開してしまう。
契約社員に、他は渡さずに1つのリポジトリへのアクセスだけを与えるにはどうすればよいか?
アクセスがどれだけ続くべきかに見合った層を使う。読み取り、コミット、プルリクエストのオープンを行う人物には、ユーザーアカウントを作成し、必要なリポジトリだけにコラボレーターとして追加する——より多くの権限を静かに付与してしまう組織チームに入れるのではなく、読み取り・書き込み・管理者という明示的な権限レベルを添えて。取得するだけの自動化システム——デプロイ先、ビルドマシン、ミラー——には、ユーザーアカウントの代わりに読み取り専用のデプロイキーを使う。1つのリポジトリに限定され、他には何も許可されず、取り消しは1行を削除するだけで済む。API と話すスクリプトには、パスワードを渡すのではなくスコープを絞ったアクセストークンを発行する。この仕組み全体を支える習慣は2つ——登録を閉じたままにし、存在するアカウントが自分で作成したものだけになるようにすること、そして一時的なアクセスを許可したらカレンダーに日付を書き込んでおくこと。契約が終わったことを、どのフォージも決して思い出させてはくれないのだから。
フォージにはドメイン名が必要なのか、そしてそれは身元の漏洩につながるのか?
Web インターフェースのためにホスト名は必要になる。TLS 証明書は名前に対して発行されるものであり、ここで IP アドレスを信頼するブラウザがあってはならないからだ。しかしその名前があなたに紐づけて追跡できる必要はない。慎重になるべき点は3つ。ドメインは、他の場所で気を配ってきた身元情報を要求しない場所で登録し、WHOIS プライバシーは有効にしておくこと——レジストラはホストとは別個の信頼判断であり、人々が忘れがちな点だ。証明書が発行された瞬間にホスト名が公開されることは覚悟しておく。信頼された証明書はすべて、誰でも検索できる証明書透明性ログに公開されるからだ。すでに使っているドメインのワイルドカードを使えば、新たに検索可能な名前を増やさずに済む。そしてもし公開の名前をまったく持ちたくないのであれば、代替手段は現実的にある——フォージを Tor のオニオンサービスに置くか WireGuard 経由でのみ到達可能にし、公開証明書を完全に省略する。その代わり、どこからでもブラウザを開ける利便性は手放すことになる。
VPS 上で Git フォージや CI ランナーを動かしても、NordBastion は気にしないのか?
まったく気にしない——フォージとそのランナーはごく普通のワークロードであり、顧客がよく構築するものの一つだ。許容利用ポリシーには、ソースコードのホスティングやそのビルドについて何ら定めがない。ポリシーというより運用上の注意点が2つある。第一に、見知らぬ相手からのワークフローを受け入れる CI ランナーは、それを見つけた誰にとっても無料の計算サービスになる——典型的な悪用はプルリクエストに仕込まれた暗号通貨マイニングであり、公開してから数日のうちに見つかる。登録は閉じたままにし、すでにコラボレーターでない者のワークフロー実行には承認を必須にすること。第二に、ビルドが毎回大きなコンテナイメージを取得するとしても、それは通常のトラフィックであり含まれている——とはいえローカルにキャッシュしておく価値はある。パイプラインが速くなるし、依存しているアップストリームのレジストリに対する礼儀でもあるからだ。
あなた自身の名前ではないアカウントの上に、あなた自身のフォージを。KYC 不要、暗号通貨払い。
Sentinel——2 vCPU と 4 GB が月額$3.90、Helsinki、Stockholm、Oslo、Reykjavík のいずれかで。Forgejo、PostgreSQL、リバースプロキシには十分で、移そうと思っていたリポジトリの余地もある。ランナー用にもう一台追加すること。サインアップにメールは不要、身分証明書も不要、プッシュしたものの製品レベルのインデックスも存在しない。
最終レビュー · 2026-10-12 · 出典 · Forgejo 管理者ドキュメント(インストール、Actions、コマンドライン)、Forgejo と Gitea の比較、Codeberg e.V. のガバナンスページ、PostgreSQL と Caddy のドキュメント、git-config(1) · 頻度 · 年次
Anonymous VPS hosting in 2026 — the cluster.
This guide is one spoke of a larger series. The pillar walks the three privacy layers end to end — the sibling spokes below dive into the specifics.
Three independent layers — signup, payment, network — explained, legal context included, common mistakes flagged.
Bitwarden-compatible password vault under your own control.
Files, calendar, contacts, photos — owned, not rented.
Postfix, Dovecot, SPF/DKIM/DMARC — deliverability included.
A meta search engine that does not log you — because you own it.