[DÚVIDA] Como usar Docker com NestJS e habilitar o modo watch para hot-reload?

Estou desenvolvendo uma aplicação usando o framework NestJS e gostaria de containerizar a aplicação usando Docker. O NestJS possui um modo de desenvolvimento (watch mode), que aplica alterações automaticamente no código sem precisar reiniciar o servidor.

No entanto, estou com dificuldades para configurar o Docker de forma que o modo watch funcione corretamente dentro do container, permitindo que as alterações no código-fonte local sejam aplicadas automaticamente no container em execução, sem a necessidade de reconstruir a imagem Docker manualmente.

Estou em dúvida se containerizar toda a aplicação NestJS, incluindo o modo watch para desenvolvimento, é realmente a maneira correta de usar Docker. Ou seria mais adequado usar o Docker apenas para serviços auxiliares, como o banco de dados, enquanto a aplicação em si roda localmente fora do container durante o desenvolvimento? Quais são as melhores práticas nesse caso?

Pra ter o hot-reload, normalmente a gente deixa a pasta do nestjs dentro do docker como um volume montado, e coloca o comando que irá rodar dentro do docker como pnpm start:dev, assim quando os arquivos alterarem na pasta externa ao docker, ele irá refletir internamente, chamando novamente o watch.

Funciona pra boa parte dos cenários

Use docker para tudo. Não só banco de dados.

docker-compose.yaml

  #
  #
  # API
  #
  #
  backend:
    container_name: backend
    build:
      context: .
      dockerfile: ./backend/Dockerfile.development
    restart: unless-stopped
    ports:
      - 3300:3300
    networks:
      - qrdapio
    depends_on:
      - postgres
      - redis
    volumes:
      - ./backend:/home/node/app

e o arquivo dockerfile.development referenciado no docker compose

FROM node:20.11.1

WORKDIR  /home/node/app

USER node

# Subir o docker sem executar. 
# A execução será feita separadamente.
CMD [ "tail", "-f", "/dev/null" ]

Destaco que o código acima eu só uso pra desenvolvimento. Abaixo segue o dockerfile de produção

#  Cria a imagem para testar se está tudo ok
#  docker build -t qrdapio/backend:1.0 .
# 
# 

FROM node:20.11.1 as build

WORKDIR  /home/node/app
# USER node

COPY package*.json ./
RUN npm install --force
COPY . .

ENV HOST 0.0.0.0
ENV PORT 8080

RUN npm run build

# 

FROM node:20.11.1
WORKDIR  /home/node/app
COPY package*.json ./



RUN npm install --production
COPY --from=build /home/node/app/dist ./dist

# Executa com a função interna para poder ter acesso
# à versão definida em package.json
CMD [ "npm", "run", "start:prod" ]
# CMD [ "node", "./dist/src/main.js" ]

Para desenvolver, observe que o docker compose fica em uma pasta e o dockerfile fica em uma subpasta chamada backend. O dockerfile de desenvolvimento apenas inicia o container. você precisa acessar o container usando docker compose exec backend bash e lá dentro você faz o npm run start:dev.

Então, resumidamente, você sempre irá trabalhar dentro do container. A pasta de trabalho está compartilhada com o docker através da criação do volume lá dentro do docker compose.

Em produção, você usa o dockerfile.

E é só isso. Seguindo essa lógica, o seu dockercompose consegue subir seu ambiente inteiro, inclusive frontend, site etc.

Roda ele com o Tilt. Falo mais sobre ele nesse post, e aqui tem um exemplo com nest, se quiser ver: https://github.com/KozielGPC/uzum-drawing-game