uuid: - value: 5c793601-f8ea-470d-8c64-3e669226e651 langcode: - value: en type: - target_id: daily_email target_type: node_type target_uuid: 8bde1f2f-eef9-4f2d-ae9c-96921f8193d7 revision_timestamp: - value: '2025-05-11T09:01:00+00:00' revision_uid: - target_type: user target_uuid: b8966985-d4b2-42a7-a319-2e94ccfbb849 revision_log: { } status: - value: true uid: - target_type: user target_uuid: b8966985-d4b2-42a7-a319-2e94ccfbb849 title: - value: 'Git Worktrees and Docker Compose' created: - value: '2022-08-12T00:00:00+00:00' changed: - value: '2025-05-11T09:01:00+00:00' promote: - value: false sticky: - value: false default_langcode: - value: true revision_translation_affected: - value: true path: - alias: /daily/2022/08/12/git-worktrees-docker-compose langcode: en body: - value: |

I've recently started trialing Git worktrees again as part of my development workflow.

If you are unfamiliar with Git worktrees, they allow you to have muliple branches of a repository checked out at the same time in different directories.

For example, this is what I see within my local checkout of my website repository:

.
      ├── config
      ├── HEAD
      ├── main
      │   ├── ansible
      │   ├── nginx
      │   ├── README.md
      │   └── website
      ├── new-post
      │   ├── ansible
      │   ├── nginx
      │   ├── README.md
      │   └── website
      ├── objects
      │   ├── info
      │   └── pack
      ├── packed-refs
      ├── refs
      │   ├── heads
      │   └── tags
      └── worktrees
          ├── main
          └── new-post
      

The first thing that you'll notice is, because it's a bare clone, it looks a little different to a what you usually see in a Git repository.

Each worktree has it's own directory, so my "main" branch inside the main directory.

If I need to work on a different branch, such as new-post, then I can create a new worktree, move into that directory and start working. I don't need to commit or stash any in-progress work and switch branches.

Complications with Docker Compose

I use Docker and Docker Compose for my projects, and this caused some issues for me the last time that I tried using worktrees.

By default, Docker Compose will use the name of the directory that the Compose file is in to name its containers. If the directory name is "oliverdavies-uk", then the containers will be oliverdavies-uk-web_1, oliverdavies-uk-db_1 etc.

This doesn't work so well if the directory is a worktree called "main" or "master" as you'll have containers called main_web_1 or master_db_1.

The way to solve this is to use the COMPOSE_PROJECT_NAME environment variable.

If you prefix Docker Compose commands with COMPOSE_PROJECT_NAME=your-project, or add it to an .env file (Docker Compose will load this automatically), then this will override the prefix in the container names to be your-project-{service}.

Container names per worktree

Whilst you could use the same Compose project name within all of your worktrees, I prefer to include the worktree name as a suffix - something like my-project-main or my-project-staging - and keep these stored in an .env file in each worktree's directory.

As each worktree now has unique container names, I can have multiple instances of a project running at the same time, and each worktree will have it's own separate data - meaning that I can make changes and test something in one worktree without affecting any others.

You can also use the COMPOSE_PROJECT_NAME variable inside Docker Compose files.

For example, if you use Traefik and needed to override the host URL for a service, the string will be interpolated and the project name would be injected as you'd expect.

labels:
          - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(
              `${COMPOSE_PROJECT_NAME}.docker.localhost`,
              `admin.${COMPOSE_PROJECT_NAME}.docker.localhost`
            )"
      

This means that Traefik would continue to use a different URL for each worktree without you needing to make any changes to your Docker Compose file.

format: full_html processed: |

I've recently started trialing Git worktrees again as part of my development workflow.

If you are unfamiliar with Git worktrees, they allow you to have muliple branches of a repository checked out at the same time in different directories.

For example, this is what I see within my local checkout of my website repository:

.
      ├── config
      ├── HEAD
      ├── main
      │   ├── ansible
      │   ├── nginx
      │   ├── README.md
      │   └── website
      ├── new-post
      │   ├── ansible
      │   ├── nginx
      │   ├── README.md
      │   └── website
      ├── objects
      │   ├── info
      │   └── pack
      ├── packed-refs
      ├── refs
      │   ├── heads
      │   └── tags
      └── worktrees
          ├── main
          └── new-post
      

The first thing that you'll notice is, because it's a bare clone, it looks a little different to a what you usually see in a Git repository.

Each worktree has it's own directory, so my "main" branch inside the main directory.

If I need to work on a different branch, such as new-post, then I can create a new worktree, move into that directory and start working. I don't need to commit or stash any in-progress work and switch branches.

Complications with Docker Compose

I use Docker and Docker Compose for my projects, and this caused some issues for me the last time that I tried using worktrees.

By default, Docker Compose will use the name of the directory that the Compose file is in to name its containers. If the directory name is "oliverdavies-uk", then the containers will be oliverdavies-uk-web_1, oliverdavies-uk-db_1 etc.

This doesn't work so well if the directory is a worktree called "main" or "master" as you'll have containers called main_web_1 or master_db_1.

The way to solve this is to use the COMPOSE_PROJECT_NAME environment variable.

If you prefix Docker Compose commands with COMPOSE_PROJECT_NAME=your-project, or add it to an .env file (Docker Compose will load this automatically), then this will override the prefix in the container names to be your-project-{service}.

Container names per worktree

Whilst you could use the same Compose project name within all of your worktrees, I prefer to include the worktree name as a suffix - something like my-project-main or my-project-staging - and keep these stored in an .env file in each worktree's directory.

As each worktree now has unique container names, I can have multiple instances of a project running at the same time, and each worktree will have it's own separate data - meaning that I can make changes and test something in one worktree without affecting any others.

You can also use the COMPOSE_PROJECT_NAME variable inside Docker Compose files.

For example, if you use Traefik and needed to override the host URL for a service, the string will be interpolated and the project name would be injected as you'd expect.

labels:
          - "traefik.http.routers.${COMPOSE_PROJECT_NAME}.rule=Host(
              `${COMPOSE_PROJECT_NAME}.docker.localhost`,
              `admin.${COMPOSE_PROJECT_NAME}.docker.localhost`
            )"
      

This means that Traefik would continue to use a different URL for each worktree without you needing to make any changes to your Docker Compose file.

summary: null field_daily_email_cta: { }