·

Git Worktree in Docker: Fix “Not a Git Repository”

Prepared and checked using AI. Sources are linked in the article.

git-worktrees tutorial ai-coding

A Git worktree in Docker can fail with “not a git repository” because mounting the checkout alone leaves its repository metadata outside the container. A linked worktree’s .git file points to a private administrative directory, which also depends on the shared repository directory. Mount the checkout and the shared Git directory, including its worktrees subtree, so those paths resolve inside the container. Preserving their host paths is the most straightforward fix.

Which paths Git needs

For example, suppose the main checkout is /home/dev/project, and an agent uses /home/dev/worktrees/agent-auth. A typical layout is:

Path on the hostPurpose
/home/dev/worktrees/agent-auth/The agent’s checked-out files
/home/dev/worktrees/agent-auth/.gitA text file pointing to the private Git directory
/home/dev/project/.git/worktrees/agent-auth/Private metadata, including this worktree’s HEAD and index
/home/dev/project/.git/Shared repository metadata, including objects, refs and configuration

The .git file might contain:

gitdir: /home/dev/project/.git/worktrees/agent-auth

Inside that private directory, a relative commondir path resolves from the private Git directory. In this layout, its contents would be:

../..

That leads back to /home/dev/project/.git. Mounting only worktrees/agent-auth therefore leaves the repository incomplete. Mounting the entire shared Git directory includes both the common metadata and the private directory beneath it.

The private directory also contains a gitdir backlink to the checkout’s .git file. Keeping the original absolute paths available preserves that connection too. You do not need the main checkout’s source files merely to provide these metadata paths.

Inspect the paths on the host

Run these commands where the worktree already works, before starting the container. Use the actual private directory Git reports; its name can differ from the checkout’s basename.

rev-parse identifies the checkout root, private Git directory and common Git directory; --path-format=absolute makes the common-directory result absolute.

WT=$(git -C /home/dev/worktrees/agent-auth rev-parse --show-toplevel)
PRIVATE=$(git -C "$WT" rev-parse --absolute-git-dir)
COMMON=$(git -C "$WT" rev-parse --path-format=absolute --git-common-dir)

cat "$WT/.git"
cat "$PRIVATE/commondir"
cat "$PRIVATE/gitdir"

If these Git commands already fail on the host, investigate the host worktree connection first. A container mount cannot restore metadata that is missing there.

Fix: mount the checkout and metadata at their host paths

Prerequisites are a working host worktree, Docker access to both source directories, and a development image containing Git. In the commands below, project-dev:local is an example image name; substitute your existing image.

Docker bind mounts map a host source to a container destination. Use the same absolute path on both sides for each mount. With --mount, a missing source produces an error instead of silently creating a directory.

The example also uses --workdir to select the checkout, --user to select the process UID/GID and --entrypoint to invoke Git. The numeric IDs come from your current host user; confirm that this identity can access the mounted files in your Docker setup.

For this diagnostic, --no-optional-locks avoids the optional index refresh write performed by git status.

docker run --rm \
  --user "$(id -u):$(id -g)" \
  --mount "type=bind,source=$WT,target=$WT" \
  --mount "type=bind,source=$COMMON,target=$COMMON" \
  --workdir "$WT" \
  --entrypoint git \
  project-dev:local \
  --no-optional-locks status

This supplies three logical locations through two mounts:

  • The checkout mount includes its .git pointer file.
  • The common-directory mount includes the referenced private directory.
  • That same metadata mount provides the shared objects, refs and configuration.

There is no need for a separate private-directory mount when it is already inside the mounted common directory.

Both mounts are writable by default. Mounting the shared directory writable gives the container access to shared repository state, including metadata for other worktrees. Treat that as a repository-wide permission decision when the container runs an agent.

If the checkout must appear at /workspace

Changing only the checkout’s destination does not translate the path stored in its .git file. If your container requires a different layout, explicitly tell Git where each part lives rather than editing the host’s pointer files.

GIT_DIR selects private metadata, GIT_COMMON_DIR selects shared metadata and GIT_WORK_TREE selects the checkout. For the standard layout above, derive the private directory’s location within the common directory, then map both mounts:

PRIVATE_REL=${PRIVATE#"$COMMON"/}

docker run --rm \
  --user "$(id -u):$(id -g)" \
  --mount "type=bind,source=$WT,target=/workspace" \
  --mount "type=bind,source=$COMMON,target=/repo-metadata" \
  --env "GIT_DIR=/repo-metadata/$PRIVATE_REL" \
  --env GIT_COMMON_DIR=/repo-metadata \
  --env GIT_WORK_TREE=/workspace \
  --workdir /workspace \
  --entrypoint git \
  project-dev:local \
  --no-optional-locks status

Use this derivation only after confirming that PRIVATE is beneath COMMON. GIT_DIR must select the agent’s private directory, not the main checkout’s Git directory: the index and HEAD come from that private location.

These variables configure Git processes that receive them. They do not rewrite the checkout’s .git file or its backlink. Prefer the path-preserving solution when other tools need to follow those files directly, and perform worktree maintenance from the host where the registered paths resolve.

Distinguish path failures from permission failures

Git reports dubious ownership

Once the paths resolve, Git may reject a repository owned by another user. safe.directory permits explicit exceptions for trusted repositories; it does not repair missing mounts or grant filesystem write permission.

First check the container user and mounted-file ownership. If differing ownership is intentional, add only the trusted container checkout path to the container user’s global configuration. Avoid safe.directory=*, which disables the ownership check for every repository.

Git can read the repository but cannot write the index

Check whether the metadata mount is read-only and whether the container user can write the private directory. Making only the checkout writable does not make its external index writable. For a related diagnosis, see worktree index.lock permission failures.

The worktree was actually moved

Use git worktree repair when a real move has broken the worktree’s connections. Avoid running repair merely to replace host paths with temporary container paths: bind-mounted metadata is shared with the host. After fixing the layout, verify the agent’s worktree and branch before continuing.

Bind sources must also exist on the Docker daemon’s host. A remote daemon cannot mount a checkout that exists only on your client machine.

Where Parallel Code fits

Parallel Code is our free, open-source desktop app for macOS and Linux that runs several AI coding agents in parallel, each isolated in its own Git worktree. When containerizing work from those checkouts, use each worktree’s actual metadata paths; its diff-first review surface helps you review the resulting changes.