Docker compose watch recursively

Hi all,

I have been struggling to “watch” and “sync” when using docker compose up --watch recursively.
The watch does work when changing the files directly under ./app, like main.py and middleware.py
However, it doesn’t work when I change the files in subdirectories like ./app/routers

For the app, I am using FastAPI, a python framework.

Things I’ve tried

  1. remove the trailing slash
watch:
    - action: sync
          path: ./app
          target: /example/app
  1. add wildcard
watch:
    - action: sync
        path: ./app/*
        target: /example/app/*
  1. Add action for each directory
watch:
    - action: sync
        path: ./app/
        target: /example/app/

    - action: sync
        path: ./app/routers/
        target: /example/app/routers/

- action: sync
        path: ./app/services/
        target: /example/app/services/
  1. Add action for each file
watch:
    - action: sync
        path: ./app/
        target: /example/app/routers/router1.py

    - action: sync
        path: ./app/routers/
        target: /example/app/routers/router2.py
  1. Add “–reload-dir”, “/example/app” to the CMD in Dockerfile
  2. The move the CMD to docker compose file from Dockerfile
  3. Add WATCHFILES_FORCE_POLLING: true as an environment variable

None of these worked. Also, changing “sync” to “sync+restart” or “rebuild” didn’t work as well.
In the documentation, it says “Directories are watched recursively” so I thought this would work but I haven’t been successful.

Can you advise on what I am missing or doing anything wrong please?
Thank you for your help.

References I looked into

compose.yaml

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - 8080:8080
    container_name: api-example
    develop:
      watch:
        - action: sync
          path: ./app/
          target: /example/app/
    environment:
      WATCHFILES_FORCE_POLLING: true
    depends_on:
      - database

  database:
    image: postgres:16
    restart: always
    container_name: example-db
    environment:
      POSTGRES_DB: example
      POSTGRES_USER: example
      POSTGRES_PASSWORD: example
    volumes:
      - ./local_init.sql:/docker-entrypoint-initdb.d/init.sql

  flyway:
    image: flyway/flyway
    depends_on:
      - database
    container_name: flyway
    command: migrate
    environment:
      FLYWAY_URL: example
      FLYWAY_USER: example
      FLYWAY_PASSWORD: example
      FLYWAY_SCHEMAS: example
      FLYWAY_LOCATIONS: filesystem:/flyway/sql
    volumes:
      - ./app/database/migration:/flyway/sql

Dockerfile

FROM python:3.12-slim

WORKDIR /example
COPY ./requirements.txt ./requirements.txt

RUN pip install --no-cache-dir --upgrade -r ./requirements.txt

COPY ./app ./app
COPY ./logging_config.ini ./logging_config.ini

CMD [ "uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8080", "--reload"]

directory structure

.
├── app
│   ├── database
│   │   ├── database_connection_pool.py
│   │   ├── entities
│   │   │   ├── entity1.py
│   │   │   ├── entity2.py
│   │   │   ├── __init__.py
│   │   │   ├── entity3.py
│   │   │   └── entity4.py
│   │   ├── __init__.py
│   │   ├── migration
│   │   │   └── V1__migration1.sql
│   │   └── repositories
│   │       ├── __init__.py
│   │       └── repository.py
│   ├── dto
│   │   ├── dto1.py
│   │   ├── dto2.py
│   │   ├── __init__.py
│   │   ├── dto3.py
│   │   ├── dto4.py
│   │   ├── dto5.py
│   │   └── dto6.py
│   ├── __init__.py
│   ├── main.py
│   ├── middleware.py
│   ├── routers
│   │   ├── __init__.py
│   │   ├── router1.py
│   │   └── router2.py
│   ├── services
│       ├── service1.py
│       ├── service2.py
│       ├── __init__.py
│       ├── service3.py
│       ├── service4.py
│       └── service5.py
├── compose.yaml
├── Dockerfile
├── README.md
├── requirements.txt
├── init.sql
├── local_init.sql
├── logging_config.ini
1 Like

I went through a fit of insanity trying to figure this out on my own, as this was the only similar post to my own situation.
I don’t know what os or pipeline OP is using, but for me I am developing on WSL since I had to install it for docker to work and, what the heck, I had to learn linux anyway. So I would mostly be typing away in the linux layer for most of my tooling.
That is absolutely, apparently, not what one should do when using docker watch. Docker watch does not like recursing files when running on WSL. When the documentation says ‘Directories are watched recursively’, this is an abject lie whispered in your ear by a sweet devil. It should come with an asterisk that says *When running on the windows host layer.
SOLUTION:
Run docker compose watch from the windows terminal.
No, seriously, that’s it. I got taken for a ride with my ankles tied to a bull’s tail frying my skull before doing that. Your docker compose should look like

watch:
- action: sync
path: ./app
target: /example/app

And the rest should work fine. Unless you were already doing that, and I’m the weird one for running everything on WSL, in which case god help you and have mercy on your soul.

Thanks! I also use WSL and was having this issue. Fortunately, this actually worked haha

What I found after your reply is that it only works on windows terminal when the files are under windows file system. If you put the files under Linux file system, docker watch does work recursively on WSL.

This was tricky for me since I didn’t know much about WSL.
My key takeaway is to be careful when you use WSL, especially where to put your files.

This only mentions about the performance but it’s probably not just that.

Thanks again!

1 Like