Skip to content

環境変数 ​

4 つの層を後勝ちで解決します。さらにその下に、Kobune が自動的に注入する変数が あります。

4 つの層 ​

層保存先コミット対象
global~/.kobune/env対象外。マシン固有の設定
project.kobune/env対象
service[services.<name>] の env対象
workspace.kobune/env.local対象外。gitignore に追加する

後に定義された層が優先されます。workspace が service より、service が project より、project が global より優先されます。

console
$ kobune env ls
│ KEY           SCOPE      VALUE
│ DATABASE_URL  project    po••••••••••••••••••••
│ GITHUB_TOKEN  global     gh••••••••••••••••••
│ …
│ LOG_LEVEL     workspace  de•••

定義元の層は常に併記されます。 層が 4 つあるため、意図しない層の値が 優先されている状況が最も特定しにくいためです。

値のほうは、指定しない限り伏せられます。 自分で設定した値は先頭 2 文字を 残してマスクされるため、画面を横から見られても、そのまま issue に貼っても 問題ありません。全体を表示するには kobune env ls --reveal を使います。 Kobune が注入する変数は対象外です。Kobune 自身の値で秘密を含まず、URL の確認は 頻度が高いため、伏せても邪魔になるだけだからです。

service はサービスを指定したときにだけ現れます。 サービスを指定しない 一覧は、すべてのサービスが共有する値です。特定のサービスだけが持つ変数をそこに 混ぜると、全体の値として見えてしまいます。そのため kobune env ls --service web のときにだけ現れます。project と分けてあるのは、 project と表示すると .kobune/env を編集しに行かせてしまうからです。その値は サービス側で上書きされており、しかも一覧が「正しい場所を見ている」と伝えた 直後になります。kobune env set からも書けません。この層は kobune.toml で 編集します。

値を設定する ​

console
$ kobune env set LOG_LEVEL=debug                    # 既定は workspace
$ kobune env set DATABASE_URL=… --scope project     # コミット対象、全体で共有
$ kobune env set GITHUB_TOKEN=… --scope global      # 全プロジェクト共通
$ kobune env unset LOG_LEVEL

ファイルを直接編集せず、kobune env から設定してください。指定した層に確実に 書き込まれ、書式も統一されます。

反映には再起動が必要です

すでに稼働しているコンテナは、新しい値を読み込みません。 kobune down && kobune up を実行してください。

自動的に注入される変数 ​

すべてのサービスに次の変数が渡されます。利用者が設定する値より下の層に位置する ため、いずれも上書きできます。

KOBUNE_PROJECT      = myapp
KOBUNE_WORKSPACE    = feature-user-auth
KOBUNE_SERVICE      = web
KOBUNE_CACHE_DIR    = /var/cache/kobune
KOBUNE_CA_FILE      = /etc/kobune/ca.crt
NODE_EXTRA_CA_CERTS = /etc/kobune/ca.crt
KOBUNE_URL_WEB      = https://web.feature-user-auth.myapp.localhost
KOBUNE_URL_API      = https://api.feature-user-auth.myapp.localhost
KOBUNE_HOSTNAME_WEB = web.feature-user-auth.myapp.localhost
KOBUNE_HOSTNAME_API = api.feature-user-auth.myapp.localhost

KOBUNE_CA_FILE ​

Kobune 自身の CA 証明書です。すべてのサービスに読み取り専用でマウントされ、 KOBUNE_URL_<SERVICE> への HTTPS 通信を検証付きで通せるようにします。

ブラウザがこの証明書を信頼しているのは kobune setup がホストのキーチェーンに 入れたからで、コンテナは自前のトラストストアを持つため何も知りません。これが 無いと URL には到達するのに証明書で落ち、結局プロセス全体の検証を切ることに なります。

Node は何も書かなくて済みます。 同じファイルが NODE_EXTRA_CA_CERTS にも 設定されるため、Server Component や API Route からの fetch は検証付きで 通ります。利用者の値より下の層なので、企業の CA バンドルを指しているイメージは そう書けばそちらが残ります。

toml
[services.web.env]
NODE_EXTRA_CA_CERTS = "/etc/ssl/corporate-and-kobune.pem"

この変数はファイルを 1 つしか取れないので、両方を信頼したいなら 1 つの ファイルにまとめてください。

Kobune が設定するのは Node の分だけです。 NODE_EXTRA_CA_CERTS は追加ですが SSL_CERT_FILE / CURL_CA_BUNDLE / REQUESTS_CA_BUNDLE は置き換えで、後者で Kobune を信頼させたコンテナは他のどこも信頼しなくなり、Kobune 以外への HTTPS 通信が止まります。自分で作ったバンドルを指すか、下のシステムトラストストアを 使ってください。

NODE_EXTRA_CA_CERTS は、注入される値の中で唯一 env_file に書き出されません。あちらはホストで読まれる ファイルで、/etc/kobune/ca.crt は存在せず Node が起動のたびに警告を出すため です。パスを自分で使いたい場合の KOBUNE_CA_FILE は書き出されます。

証明書がプロセスまで届かないとき ​

コンテナとサーバの間に挟まるタスクランナーが落とすことがあります。Turborepo 2 の既定である strict environment mode は設定に書かれた変数しか通さないため、 NODE_EXTRA_CA_CERTS は turbo までは届いてもその先の next dev には渡らず、 この節が防ごうとしているエラーがそのまま出ます。

[cause]: Error: self-signed certificate in certificate chain {
  code: 'SELF_SIGNED_CERT_IN_CHAIN'
}

turbo.json に名前を書けば通ります。

json
{ "globalPassThroughEnv": ["NODE_EXTRA_CA_CERTS"] }

Node は追加の証明書を環境変数からしか受け取らないため、環境を濾すツールの 向こう側へは Kobune の注入は届きません。コンテナには設定されているのに プロセスには無い、というときは間に挟まっているものを疑ってください。

システムのトラストストアを読むスタックでは、起動時に追加してください。

toml
[services.api]
command = "sh -c 'cp $KOBUNE_CA_FILE /usr/local/share/ca-certificates/ && update-ca-certificates && ./serve'"

検証すべき HTTPS が無いとき(443 を保持できていないなど)は設定されません。 他の値と同じく、すでに稼働中のコンテナには反映されません (kobune down && kobune up)。

/etc/kobune/ca.crt は Kobune 自身のマウント先なので、そのパスちょうどへの volumes は /var/cache/kobune と同様に拒否されます。

KOBUNE_CACHE_DIR ​

残す価値はあるがコミットする必要はないものの置き場所です。Kobune が管理する ボリュームで、すべてのサービスにマウントされます。

toml
[services.web.env]
npm_config_store_dir = "${KOBUNE_CACHE_DIR}/pnpm"
CARGO_HOME = "${KOBUNE_CACHE_DIR}/cargo"
TURBO_CACHE_DIR = "${KOBUNE_CACHE_DIR}/turbo"

波括弧は省略できません

${KOBUNE_CACHE_DIR} は参照であり、Kobune が展開します。 波括弧の無い $KOBUNE_CACHE_DIR は書いたまま渡され、Docker も展開しません。 npm_config_store_dir = "$KOBUNE_CACHE_DIR/pnpm" と書くと、workdir すなわち worktree からの相対パスとして $KOBUNE_CACHE_DIR という名前のディレクトリが 作られます。これはまさに、この仕組みが防ごうとしている「リポジトリ内に数 GB」 そのものです。この書き方をした値には kobune up が警告します。

シェルが展開する場所——command や起動スクリプト——では波括弧は不要です。

toml
command = "sh -c 'pnpm config set store-dir $KOBUNE_CACHE_DIR/pnpm && pnpm dev'"

パッケージマネージャの参照先をここに向けてください。 既定のままでは多くが 作業ディレクトリ配下にキャッシュを作りますが、そこは worktree、つまりホストから バインドマウントされた領域です。結果としてキャッシュがリポジトリの中に生まれ、 pnpm の store であれば数 GB の追跡対象外ファイルがチェックアウトに残ります。

プロジェクト内のすべての worktree で共有されます。パッケージの取得を 1 回で 済ませるのが目的だからです。ブランチによって内容が変わるもの(ブランチごとに lockfile が異なる node_modules など)には @workspace ボリューム を使ってください。

Turborepo のキャッシュもここに置きます。 既定の保存先は .turbo/cache、 つまり worktree の中、リポジトリの中です。新しく作った worktree はそれを 1 つも持たない状態から始まります。TURBO_CACHE_DIR を共有ボリュームに 向ければ、明日作る worktree でも他の worktree がビルドしたものをそのまま 使えます。コンテナがホストより遅く感じるかどうかは、ここで決まります。

root 以外で動作するコンテナの場合

ボリュームは空かつ root 所有で作成されるため、別のユーザで動作するサービスは 自分が所有するディレクトリが作られるまで書き込めません。インストール処理だけ USER root にするか、起動スクリプトで mkdir -p "$KOBUNE_CACHE_DIR/x" && chown してください。

コンテナは作成時のマウント構成を保持するため、アップグレード時にすでに起動して いたサービスには kobune down && kobune up するまで反映されません。また /var/cache/kobune に自前のボリュームをマウントすることはできません。1 つの パスへの二重マウントはコンテナエンジンのエラーになり、原因となった記述から 遠い場所で表面化するためです。

kobune env ls が表示するのは全サービスに共通する内容だけです。 KOBUNE_SERVICE とサービス固有の env は kobune env ls --service <name> で確認してください。

KOBUNE_URL_<SERVICE> ​

とくに重要な変数です。 URL はブランチごとに異なるため、フロントエンドは API の URL をハードコードできません。worktree ごとの環境が成立するのは、この 変数があるためです。

js
const api = process.env.KOBUNE_URL_API ?? 'http://localhost:8080'

サービス名に含まれる - は _ に変換されます。api-server であれば KOBUNE_URL_API_SERVER になります。

プロキシが停止している場合

プロキシが待ち受けていないときは、空文字ではなく変数自体が設定されません。 空文字を設定すると「値はあるのに接続できない」状態になり、変数が存在しない 場合よりも原因の特定が困難になるためです。

コンテナ側では KOBUNE_URL_WEB: parameter not set として現れますが、この メッセージからここに辿り着く手がかりはありません。プロキシが無い状態で サービスを起動した場合は kobune up が警告し、対処方法は kobune doctor が示します。

KOBUNE_HOSTNAME_<SERVICE> ​

同じホストを、周りに何も付けずに渡します。スキームもポートも末尾のスラッシュも ありません。

toml
[services.web.env]
NEXT_ALLOWED_DEV_ORIGIN = "${KOBUNE_HOSTNAME_WEB}"

[services.api.env]
COOKIE_DOMAIN = "${KOBUNE_HOSTNAME_API}"

CORS の origin、allowedDevOrigins、cookie の domain はいずれも URL ではなく これを要求します。 この変数が無いと、KOBUNE_URL_<SERVICE> から sed で スキームを削ぎ落とす処理がプロジェクト側に生まれます。

注入される条件は URL と同じです。プロキシが待ち受けている間、かつ URL を公開 しているサービスに限ります。応答しないホスト名を渡すのは、URL 側で避けている 「値はあるのに繋がらない」と同じ状態だからです。

KOBUNE_HOST_<SERVICE> とは別物です

そちらは Apple Container のもので、他サービスの IP アドレスを保持します。 ランタイム を参照してください。

他の変数を参照する ​

値の中の ${NAME} は、NAME の解決結果に置き換えられます。

toml
[services.web.env]
NEXT_PUBLIC_WEB_URL = "${KOBUNE_URL_WEB}"
NEXT_PUBLIC_API_URL = "${KOBUNE_URL_API}"
FILE_BASE_URL       = "${KOBUNE_URL_API}/dev/r2"

worktree ごとに変わる URL を、アプリケーションが既に読んでいる名前で渡すため の仕組みです。 KOBUNE_URL_API は Kobune の名前で届くため、これを書けないと、 変数を別の変数に写すためだけの起動スクリプトがどのプロジェクトにも生まれます。

参照が解決するのは、どの層が優先されたかを問わず、コンテナに実際に渡る値です。 したがって .kobune/env.local で KOBUNE_URL_API を上書きすれば、そこから 組み立てられる値もまとめて変わります。参照は連鎖できます。展開後の値は kobune env ls にも表示されます。展開前の値の一覧は、どこでも動いていない ものの一覧だからです。

  • 波括弧の無い $NAME は展開されません。 これらの値はこれまで書いたまま 渡されてきたため、いま展開を始めると既存の設定の意味が変わってしまいます。 存在する変数名がこの形で書かれている場合は kobune up が警告するので、 症状から探し当てる必要はありません。
  • $$ は $ そのものです。 $${A} は ${A} のまま渡ります。
  • 変数名でないものは参照ではありません。 ${PORT:-3000} はシェルの記法 として、そのままシェルに届きます。
  • どこにも定義の無い名前はエラーです。 空文字にはしません。プロキシが無い ときに KOBUNE_URL_<SERVICE> を未設定のままにするのと同じ理由です。その ため ${KOBUNE_URL_API} を参照していると、プロキシが動いていない間はその 変数が欠けたまま起動するのではなく、サービスの起動自体が止まります。復旧の 手順は kobune doctor が示します。

解決できない値があっても kobune env ls は一覧を表示します。書かれたまま 表示されるのは原因の値だけで、理由は一覧の下に添えられます。原因の値は値を 眺めてしか見つけられないためです。解決できた値はそのまま解決済みで表示される ので、両者は区別できます。

サービスを指定しない一覧には KOBUNE_SERVICE とサービス固有の env が含まれ ません。そのため、それらから組み立てた値はこの一覧では解決できません。 サービス自体は問題なく起動します。その場合はその旨と、解決できる一覧を持つ サービス名を表示します。解決できるのはそのサービスだけだからです。

--json では、解決できなかった値に unsettled オブジェクトが付き、参照先の 名前と理由(undefined、only_with_service、needs_proxy、secret、 cycle)を持ちます。解決できた値にはこのフィールドがありません。

この機能より前に書かれた値について

${...} と $$ には、これまで無かった意味が付きました。既にこれらを含む値は 挙動が変わります。$$ は $ 1 文字になり、存在しない変数名を指す ${NAME} はそのまま渡されるのではなく kobune up を止めます。文字として渡したい場合は ドルを重ねてください($ は $$、${ は $${)。

シークレットを他の値に埋め込むことはできません

PASSWORD が op:// や keychain:// の参照である場合、 DATABASE_URL = "postgres://user:${PASSWORD}@db/app" は拒否されます。これらは コンテナ起動時にメモリ上で解決される値であり、ここで展開すると kobune env ls や、そこから書き出されるあらゆる出力に平文が載ってしまいます。

組み立て済みの値をシークレットとして保存するか、2 つの変数のままアプリケー ションに渡して、そちらで結合してください。

ファイルに書き出す ​

起動時の環境変数を読まない道具があります。wrangler dev は自身の環境変数を Worker に渡さず、Vite や dotenvx はディスク上のファイルを読みます。env_file は解決済みの値を、それらが見つけられる場所に書き出します。

toml
[services.api]
env_file = ".kobune/env.api"
sh
wrangler dev --env-file .env --env-file .kobune/env.api

パスは worktree からの相対で、サービスの起動直前——kobune up のときと、 scale-to-zero が起こすたび——に書かれます。停止後も残るため、worktree で pnpm dev を直接動かす場合も同じ値を読めます。

書き出されるのは起動するサービスの分だけです。 kobune up web が書くのは web と、その depends_on が引き連れてくるサービスの分だけで、api の分は 書きません。起動を頼まれていないサービスが、頼まれたサービスを巻き添えに失敗 させることはなく、api だけに指定されたパスは api が動くときにだけ書かれ ます。kobune exec は何も書きません。コマンドを実行するだけで、サービスを 起動するわけではないからです。

内容が変わらない場合は書き込みません。 ファイルを監視している dev server が、サービスが起きるたびに再起動してしまうためです。

  • git が追跡しているパスは拒否します。 生成ファイルは worktree を永久に dirty にし、コミットすれば 1 つのブランチの URL が他のすべてのチェックアウト に混入します。gitignore された場所——.kobune/ は既にそうです——を指定して ください。
  • Kobune が書いたのでないファイルは上書きしません。 目印は先頭行のヘッダ です。自分で用意した .env.local は安全で、置き換えではなくファイル名を 含むエラーが返ります。
  • .kobune/env と .kobune/env.local は指定できません。 この 2 つは Kobune 自身が層として読むファイルです。書き出すと生成ファイルがそのまま 入力に戻り、しかも workspace 層は最も優先度が高いため、前回の値が今回 注入される値を上書きしてしまいます。隣に別名で書いてください。
  • 1 つのパスにつき 1 サービスです。 2 つのサービスが同じファイルを指すと、 起動のたびに互いの環境変数を上書きし合います。
  • scope = "project" では使えません。 共有サービスには worktree が マウントされないため、そのコンテナから見えない場所に書かれてしまいます。

シークレットは書き出されません

値が op:// や keychain:// の参照であるキーは、コメントに名前だけ残し、 書き出しません。解決済みのシークレットは daemon のメモリ上にのみ存在し、 ディスクには触れません。ファイルは読み手に渡っていくものなので、ここで書けば その保証は終わります。

道具がシークレットそのものを必要とする場合は、自前の .env を用意して両方の ファイルを渡してください。

シークレット ​

秘匿すべき値はコミットしないでください。参照形式で記述しておけば、コンテナの 起動時に Kobune が解決します。

DATABASE_PASSWORD = op://Development/myapp/password    # 1Password CLI
API_KEY           = keychain://kobune/myapp/api-key    # macOS Keychain
STRIPE_KEY        = env://STRIPE_KEY                   # daemon の環境変数

解決された値はメモリ上でコンテナに渡され、ディスクには書き込まれません。kobune env ls は値ではなく参照を表示します。--reveal を指定した場合も 同様です。実際の値を表示するには解決処理が必要ですが、それは起動時にのみ 実行するためです。

解決に失敗した場合 ​

daemon は停止しません。多くの場合は 1Password にサインインしていないだけで あり、それによって環境全体が起動しなくなるほうが問題だからです。該当する キーのみを除外し、警告を出力します。

warning: cannot resolve the secret for DATABASE_PASSWORD: cannot reach op

アプリケーションは変数が未設定であることを理由に失敗します。誤った値で 動作し続けるよりも、明確な失敗です。

単一の値を取得する ​

console
$ kobune env get DATABASE_URL
postgres://db:5432/app

スクリプトから利用できるよう、値を 1 行だけ出力します。env ls と異なり 実際の値が表示されるのは、キーを明示的に指定しているためです。

解決できない値の場合は、出力せずに失敗します。 env ls は ${...} を 書かれたまま表示しますが、それをスクリプトに渡すと波括弧がそのまま流れ込んで しまうためです。

ファイルで管理する場合 ​

~/.kobune/env              global
.kobune/env                project、コミット対象
.kobune/env.local          workspace、gitignore に追加する

書式は 1 行につき KEY=value で、# から始まる行はコメントです。 .kobune/env.local は .gitignore に入れるファイルで、kobune init が追加 します。

Released under the Apache License 2.0.

nightly ビルドです。リリース版ではなく、まだ安定していません。 詳しく