はじめに
VSCodeやCursorの拡張機能であるDev Containersは、プロジェクトごとに独立した開発環境をコンテナとして構築できる非常に便利な機能であり、Dev Containersを利用することで開発者毎の環境に左右されることなく、開発環境のセットアップが非常に容易になります。
そんな便利なツールであるDev Containersですが、CursorでDevContainer環境をセッティングしている際に、ホストマシンのSSH鍵がコンテナに自動転送されない という事象に遭遇しました。
本記事ではこの問題を回避しながら、開発者毎の環境に左右されない形でのセットアップについて共有します。
事象について
事象に遭遇後、改めて VSCodeのDev Containers ドキュメント を確認すると、ホスト上でssh-agentが設定されていれば自動的にコンテナ内にssh-agentが転送されるとの記載がありました。
実際、CursorではなくVSCodeでDev Containersを立ち上げ同様の環境を作ってみた所、意図した通りにssh-agentが転送されていることが確認できます。
しかし、CurosrのDev Containersを立ち上げるとssh-agentの自動転送が行われていませんでした。
そこで、そもそも各開発者にssh設定を強制する事に関しても積極的ではなかったというのもあり、自動転送のアプローチは諦めて別の手段を模索することにしました。
※ Microsoft製の拡張機能がCursor等のFork版エディタで使用できなくなっており、Cursorで使用されているDev ContainersはVSCodeで配信されているDev Containersとは機能に差異があるようです。
実現したいこと
以下の状態を目指します。
- コンテナ内での
git clone,pull,pushといった操作を、SSHキーの有無を意識せずに行えるようにする。 -
git@github.com:...のようなSSH形式のリモートURLを、Gitコマンド実行時に自動でhttps://github.com/...のHTTPS形式に変換する。 - ホスト環境のGit設定には影響を与えずにDev Containers利用時のみ、この変換を適用する。
DockerfileでのHTTPS化
このアプローチの鍵となるのが、コンテナをビルドする際の Dockerfile に追記するGitの設定です。
以下は、実際に利用している Dockerfile の一部です。
# ... (前段の処理は省略) ...
# Git : HTTPS化とalias設定
ARG GIT_NAME
ARG GIT_EMAIL
RUN git config --global user.name $GIT_NAME \
&& git config --global user.email $GIT_EMAIL \
&& git config --global --add url."https://github.com/".insteadOf git@github.com: \
&& git config --global --add url."https://github.com/".insteadOf ssh://git@github.com
# ... (後段の処理は省略) ...
この設定の中で最も重要なのが、以下の2行です。
git config --global --add url."https://github.com/".insteadOf git@github.com:
git config --global --add url."https://github.com/".insteadOf ssh://git@github.com
これはGitの url.<base>.insteadOf という機能を利用した設定です。
この設定により、git@github.com: で始まるURLに対するすべてのGit操作は、代わりにhttps://github.com/ をベースとするURLに対して実行されるようになります。
内部的にURLを書き換えてから通信を行うため、origin に登録されているリモートURLをSSH形式のまま変更する必要がありません。
コンテナ内のGitがプロトコルを変換してくれるため、開発者はSSHかHTTPSかを意識せずに利用できます。
また、user.name とuser.email もコンテナビルド時に引数( args )として渡すことで、コンテナ内でのコミット者情報が正しく設定されるようにしています。
これらの引数は、 docker-compose.yml ファイル経由で渡します。 .env ファイルで設定するようにすると良いでしょう。
services:
api:
build:
context: ..
dockerfile: .docker/dockerfile/Dockerfile.local
args:
- GIT_NAME=${GIT_NAME}
- GIT_EMAIL=${GIT_EMAIL}
# ... (以下省略) ...
Dev Containers設定の全体像
Dev Containersは、.devcontainer/devcontainer.json ファイルをエントリーポイントとして、Dockerコンテナを構築・起動します。
{
"name": "example-api",
"dockerComposeFile": [
"../docker-compose.yml",
"docker-compose.yml"
],
"service": "api",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
// ... (以下省略) ...
}
上記の例はほぼデフォルトですが、プロジェクトルートの docker-compose.yml と .devcontainer ディレクトリ内の docker-compose.yml を読み込み、api という名前のサービスを開発コンテナとして利用するよう設定されています。
そして、その api サービスが、先ほど解説したGitのHTTPS化設定を含む Dockerfile をビルドする。という流れになっています。
実際の動作確認
設定が完了したコンテナを起動し、ターミナルを開いてみましょう。
まず、リモートリポジトリのURLを確認します。
$ git remote -v
origin https://github.com/xxx/example-api.git (fetch)
origin https://github.com/xxx/example-api.git (push)
URLがHTTPS形式に変換されていることがわかります。しかし、この状態でgit pullやfetchを実行しても、認証を行っていないためエディタ上で認証を求められるかと思います。
通常のケースでは以下画像のフローで進めることで認証が完了しますが、今回は別の手段として gh を使った方法についても紹介します。
※ 通常のケースでのフロー
- 未認証状態でgitの認証が必要な動作を行うと以下のようなダイアログが表示されます。
2. [許可] を押下するとコードと共にGitHubへの認証画面へ進むダイアログが表示されます。
3. 認証画面へ進むと、以下のような画面になるので前項で表示されたコードを入力し [Continue] を押下します。
4. 以下の画面が表示されたら認証は完了です。
ghを使った認証設定
まずはDevContainer内で gh を使えるようにするために以下の設定を devcontainer.json に追記します。
"features": {
"ghcr.io/devcontainers/features/github-cli:1": {}
},
上記設定後、DevContainerを再ビルドすることでDevContainer内で gh コマンドが使えるようになります。
使えるようになったら、以下のコマンドを実行することで通常フローの③と同様に、ブラウザからの認証フローに進むことができます。
gh auth login --hostname github.com --git-protocol https --web
私個人としては、エディタからの認証フローに頼ってしまうと、開発者の環境によって思いもよらぬトラブルなどが生じる可能性があるので、マニュアルでの対応フローとして gh コマンドを使えるようにしておくのが良いと思います。
また、gh は今回のような用途だけでなくPRの確認などにも役立つので導入しておいて損はないと思っています。
引用
# GitHub CLI について
GitHub CLI は、すべての作業を 1 か所で行うことができるように、pull request、issues、GitHub Actions などの GitHub 機能をターミナルに集めたコマンドライン ツールです。
おまけ: 開発体験を向上させる設定
本プロジェクトのDockerfileには、GitのHTTPS化以外にも、コンテナでの開発を快適にするためのいくつかの工夫が施されています。ここではその一部をご紹介します。
1. ホストとの権限問題を解決するユーザー設定
基本的には devcontiner.json に以下の記述をすることで、自動的にホストのユーザー情報でコンテナ内の指定ユーザー情報を上書きするような挙動になります。
"remoteUser": "dev-user"
しかし、上記の設定をするにはコンテナ内にあらかじめユーザーを作っておく必要があることがある点から、ある程度明示的に適切なUIDとGIDを設定したユーザーを作っておくのが安全かと思います。
以下の設定でコンテナ内にホストと同じUID/GIDを持つユーザーを作成しています。
ARG USERNAME=dev-user
ARG HOST_UID
ARG HOST_GID
RUN groupadd --gid $HOST_GID $USERNAME \
&& useradd --uid $HOST_UID --gid $HOST_GID -m $USERNAME
# ... (中略) ...
USER $USERNAME
services:
api:
build:
context: ..
dockerfile: .docker/dockerfile/Dockerfile.local
args:
- GIT_NAME=${GIT_NAME}
- GIT_EMAIL=${GIT_EMAIL}
- HOST_UID=${HOST_UID:-1000}
- HOST_GID=${HOST_GID:-1000}
# ... (以下省略) ...
-
ARG HOST_UID,ARG HOST_GID:docker-compose.ymlからホストマシンのユーザーIDとグループIDを受け取るためのビルド引数です。 -
useradd --uid $HOST_UID --gid $HOST_GIDでは、受け取ったIDを使ってコンテナ内にユーザーを作成します。
また、デフォルトとして各IDを 1000 とすることで、ホストのユーザー環境が一致する場合には特に .env などに特別設定すること無く開発することが可能です。
ホストのユーザー情報が違う場合にはこれらも併せて .env で管理することになります。
この設定により、コンテナ内で新しいファイルを作成したりしても、ホストマシンから見たときにファイルの所有者が自分自身となり、権限の問題を気にすることなくスムーズに作業を続けられます。
2. Gitブランチ表示付きのBashプロンプトカスタマイズ
コンテナのターミナルで作業している場合、今どのGitブランチにいるのかが分からなくなると不便です。
そこで、以下のような設定でBashのプロンプト(コマンド入力待ちの表示)をカスタマイズしています。
# Bash : 表示をカスタマイズ
RUN wget -qO /home/$USERNAME/.git-prompt.sh https://raw.githubusercontent.com/git/git/master/contrib/completion/git-prompt.sh && \
echo 'source ~/.git-prompt.sh' >> /home/$USERNAME/.bashrc && \
echo 'PROMPT_COMMAND='\''PS1="\[\033[1;32m\]\u\[\033[00m\]:\[\033[1;34m\]\w\[\033[1;31m\]\$(__git_ps1)\[\033[00m\] \$ "'\''' \
>> /home/$USERNAME/.bashrc
この設定は3つのステップで構成されています。
- Git公式が配布している
git-prompt.shスクリプトをダウンロードします。これにはカレントディレクトリのGitステータス(ブランチ名など)を取得する便利な関数が含まれています。 -
~/.bashrcにsource ~/.git-prompt.shを追記し、ターミナル起動時にこのスクリプトが読み込まれるようにします。 - プロンプトの表示形式を定義する変数
PS1をPROMPT_COMMAND経由で設定します。\u(ユーザー名)や\w(カレントディレクトリ)といった特殊な変数に加えて、\$(__git_ps1)を記述することで、git-prompt.shが提供する関数を呼び出し、現在のブランチ名をプロンプトに埋め込んでいます。
この設定により、ターミナルのプロンプトが ユーザー名:カレントディレクトリ(ブランチ名) $ のようにカラフルに表示され、作業効率が向上します。
出典
終わりに
いかがでしたでしょうか。
今回は、git config の url.insteadOf 機能を利用して、CursorのDev ContainersにおけるSSHエージェント転送の問題を回避する方法を共有しました。
SSHキーの管理や転送設定が不要で、ホストのGit環境には影響を与えないという点で満足しています。
将来的にはアップデートでSSHエージェントの自動転送が行えるようになるかもしれませんが、それまではこのようなアプローチで運用していきたいと思います。
ここまで読んでいただきありがとうございました。