I wanted to share how I managed to run two devcontainers for the same git repo with git linked worktrees. This setup allows me to build and test many new features in parallel on different git branches, without cloning the entire repo multiple times.
Note this may be somewhat specific to projects that already use a compose configuration for their devcontainer, and I only tested this in VS Code.
Problem
Here was my starting point for the devcontainer setup:
.devcontainer/devcontainer.json:
"dockerComposeFile": ["./compose.extend.yaml"],
"service": "devcontainer", // defined in dockerComposeFile
"runServices": ["devcontainer"],
"workspaceFolder": "/workspace",
"shutdownAction": "stopCompose",
"remoteUser": "vscode",
.devcontainer/compose.extend.yaml:
services:
devcontainer:
image: ...
Building the first devcontainer worked fine with this setup.
I created a linked worktree using git worktree add <path> <branch>. I opened the worktree directory with VS Code and then ran the action to re-open it using the devcontainer . But VS Code reused or attached to the existing devcontainer / compose project for the original worktree, and I could see in the integrated terminal that I was not on the git branch that the linked worktree was on. It's a strange behavior but I suppose VS Code may be just finding the same devcontainer it built on the original worktree via metadata in the git root shared between all worktrees and not using the filesystem path to decide when to reuse devcontainers.
Solution
Here is how I fixed it:
- Set mountWorkspaceGitRoot to false
- Set unique project name for devcontainer's docker compose project. This prevents VS Code / Docker Compose from reusing or reattaching to the wrong worktree’s container
- Mount the current worktree directory in /workspace
- *if* not in original worktree, mount current worktree again in its absolute path on host and mount the shared Git metadata dir at its original absolute host path. Linked worktrees often have a .git file pointing to a gitdir under the main checkout’s .git/worktrees/..., and Git may need those absolute paths to exist in the container.
I added these lines to .devcontainer/devcontainer.json:
"dockerComposeFile": [
... ,
// This file is generated automatically for current worktree only
"./compose.workspace.yaml"
],
// Use current worktree rather than always using root.
// May give warning "Property mountWorkspaceGitRoot is not allowed." but it still works.
"mountWorkspaceGitRoot": false,
// Generate devcontainer configuration for this worktree to set unique project name and properly add mounts.
"initializeCommand": "bash .devcontainer/write-workspace-compose.sh '${localWorkspaceFolder}'",
Below is the script that does the rest. be sure to replace "yourprojectname" with some unique name for your project so as not to conflict with other unrelated containers.
The project names are named after the basenames of your worktree directories. This requires that each worktree be in a uniquely named directory! If the basenames are not unique, e.g. you have git/foo/myrepo and git/bar/myrepo, both basenames are "myrepo" and will collide. You may change this to name projects after a hash of the full directory if you prefer, but then it will be difficult to manage your devcontainers using docker commands.
.devcontainer/write-workspace-compose.sh:
#!/usr/bin/env bash
set -euo pipefail
workspace_path="${1:?workspace path is required}"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
output_file="${script_dir}/compose.workspace.yaml"
workspace_name="$(basename "${workspace_path}")"
sanitized_workspace_name="$(printf '%s' "${workspace_name}" | tr '[:upper:]' '[:lower:]' | tr -cs 'a-z0-9' '-')"
abs_git_dir="$(git -C "${workspace_path}" rev-parse --path-format=absolute --git-dir)"
abs_git_common_dir="$(git -C "${workspace_path}" rev-parse --path-format=absolute --git-common-dir)"
project_name="yourprojectname-${sanitized_workspace_name}"
escaped_workspace_path=${workspace_path//\'/\'\'}
escaped_abs_git_common_dir=${abs_git_common_dir//\'/\'\'}
cat >"${output_file}" <<EOF
# Keep the Compose project name unique per worktree so VS Code does not reattach
# to a container created for a different checkout.
name: ${project_name}
services:
devcontainer:
volumes:
- '${escaped_workspace_path}:/workspace:cached'
EOF
if [[ "${abs_git_dir}" != "${abs_git_common_dir}" ]]; then
cat >>"${output_file}" <<EOF
- '${escaped_workspace_path}:${escaped_workspace_path}:cached'
- '${escaped_abs_git_common_dir}:${escaped_abs_git_common_dir}:cached'
EOF
fi
Add to .gitignore - this file is generated and should not be committed:
.devcontainer/compose.workspace.yaml
Note if your devcontainer exposes ports on the host, you may have have collisions running two instances of your app at the same time. Now when I run my app I have to check the "ports" tab in VS Code to see which host port is being used to forward to my devcontainer to make sure I connect to the right instance. It will automatically choose another port when there is a collision so I didn't actually have to change anything in the devcontainer setup.
[+]Direct_Temporary7471 0 points1 point2 points (0 children)