Skip to content
Laviyx Learn

Try

Docker and Kubernetes course menu

Docker Compose for Development: Live Reload and Overrides

Set up a development environment with Compose: bind mounts and live reload, protecting node_modules, Compose watch, override files, and profiles for optional services.

Intermediate8 min readUpdated
On this page

In production, code is built into the image. In development, you want to save a file and see the change at once. Compose handles both from the same project.

Live reload with a bind mount#

Here a Node.js server runs with --watch, and the project folder is mounted into the container:

Terminal
mkdir api
cd api
cat > server.js <<'EOF'
import { createServer } from "node:http";
createServer((req, res) => res.end("Version 1\n")).listen(3000);
EOF
echo '{ "type": "module" }' > package.json
cat > compose.yaml <<'EOF'
services:
  api:
    image: node:24-alpine
    working_dir: /app
    command: node --watch server.js
    ports:
      - "3000:3000"
    volumes:
      - .:/app
EOF
docker compose up -d --quiet-pull > /dev/null 2>&1
sleep 3
curl -s http://localhost:3000
sed -i 's/Version 1/Version 2/' server.js
sleep 3
curl -s http://localhost:3000
docker compose down > /dev/null 2>&1
Output
Version 1
Version 2

No rebuild was needed. For Python, use flask --debug run or uvicorn --reload in the same way.

Protecting node_modules#

Mounting your project over /app hides any node_modules that the image installed. The usual fix is an extra volume on that one folder, which covers the bind mount at that path:

YAML
services:
  web:
    build: .
    volumes:
      - .:/app
      - /app/node_modules

Compose watch#

docker compose watch is a built-in alternative to bind mounts. It syncs changed files into the container, and rebuilds when dependencies change:

YAML
services:
  web:
    build: .
    develop:
      watch:
        - action: sync
          path: ./src
          target: /app/src
        - action: rebuild
          path: package.json
Terminal
docker compose watch

It works well on Windows and macOS, where bind mounts can be slow.

Override files#

Keep production settings in compose.yaml, and development extras in compose.override.yaml. Compose merges that file automatically:

Terminal
cat > compose.yaml <<'EOF'
services:
  api:
    image: node:24-alpine
    environment:
      APP_ENV: production
EOF
cat > compose.override.yaml <<'EOF'
services:
  api:
    environment:
      APP_ENV: development
      DEBUG: "true"
    ports:
      - "3000:3000"
EOF
docker compose config | grep -E 'APP_ENV|DEBUG|published'
echo ---
docker compose -f compose.yaml config | grep -E 'APP_ENV|DEBUG|published'
Output
      APP_ENV: development
      DEBUG: "true"
        published: "3000"
---
      APP_ENV: production

docker compose config prints the final, merged result. With -f, Compose uses only the files you name, which is how you ignore the override file in production.

Profiles#

Mark optional services with a profile, and they start only when asked for:

Terminal
cat > compose.yaml <<'EOF'
services:
  api:
    image: redis:8.6-alpine
  debug-tools:
    image: redis:8.6-alpine
    profiles: [debug]
EOF
rm compose.override.yaml
docker compose config --services
echo ---
docker compose --profile debug config --services | sort
Output
api
---
api
debug-tools

One-off commands#

docker compose run starts a temporary container, which suits tests and migrations:

Terminal
docker compose run --rm api redis-cli --version 2>&1 | grep '^redis-cli' | cut -d' ' -f1,2
Output
redis-cli 8.6.8

Typical uses are these:

Terminal
docker compose run --rm api npm test
docker compose run --rm api python manage.py migrate

A good team setup#

  • A compose.yaml that starts everything with no extra steps.
  • A .env.example that people can copy to .env.
  • Seed data, so that the app is useful at once.
  • A README with one command: docker compose up.

Common mistakes#

Practice#

Practice exercise

Without starting anything, use docker compose config --services to check that a file with services web and db is valid, and print how many services it has.

Show solution
Terminal
cat > compose.yaml <<'EOF'
services:
  web:
    image: redis:8.6-alpine
  db:
    image: redis:8.6-alpine
EOF
docker compose config --services | wc -l
Output
2

Key takeaway#

Next: Health Checks and Resource Limits.

Finished this lesson?

Some links are affiliate links. If you buy through them, Laviyx Learn may earn a commission at no extra cost to you. As an Amazon Associate, we earn from qualifying purchases.