Step 1: Isolate the Development Image
Production Dockerfiles should compile TypeScript, strip dev dependencies, and run optimized binaries. Development images must retain the toolchain required for live reloading.
# Dockerfile.local
FROM node:22-slim
WORKDIR /workspace
# Install dependencies with dev tooling preserved
COPY package.json package-lock.json ./
RUN npm ci
# Copy configuration and source
COPY tsconfig.json ./
COPY src ./src
# Expose development port
EXPOSE 4000
# Run with built-in TypeScript watcher
CMD ["npx", "tsx", "watch", "src/server.ts"]
Rationale: Keeping tsx and development dependencies ensures the container can detect file changes and restart the Node.js process without external orchestration. The tsx watch command monitors the src directory and performs hot module replacement, which pairs cleanly with Compose Watch's file synchronization.
Step 2: Define Watch Rules in Compose
Compose Watch operates through the develop.watch directive. Each rule specifies an action, a host path, a container target, and optional exclusion patterns.
# compose.dev.yml
services:
prompt-router:
build:
context: .
dockerfile: Dockerfile.local
ports:
- "4000:4000"
environment:
NODE_ENV: development
CACHE_URL: redis://cache-layer:6379
VECTOR_DB: postgres://db-engine:5432/ai_state
depends_on:
cache-layer:
condition: service_healthy
db-engine:
condition: service_healthy
develop:
watch:
- action: sync
path: ./src
target: /workspace/src
ignore:
- "**/*.test.ts"
- "**/*.spec.ts"
- "**/__snapshots__"
- action: rebuild
path: ./package.json
- action: rebuild
path: ./package-lock.json
- action: sync+restart
path: ./config
target: /workspace/config
cache-layer:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
db-engine:
image: postgres:16-alpine
environment:
POSTGRES_DB: ai_state
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev_secret
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
Architecture Decisions:
sync for ./src: Delegates reload responsibility to tsx watch. Compose only transfers changed bytes, avoiding process termination.
rebuild for package.json and package-lock.json: Dependency changes require a fresh npm ci execution. Syncing these files would leave the container's node_modules out of sync with the declared versions.
sync+restart for ./config: Configuration files (e.g., rate limits, model endpoints, feature flags) are typically read at startup. Syncing them without a restart leaves stale values in memory.
- Explicit
ignore patterns: Prevents test artifacts, coverage reports, and IDE metadata from triggering unnecessary syncs or watcher events.
healthcheck on dependencies: depends_on only guarantees container startup, not service readiness. Healthchecks ensure the AI router doesn't crash on connection attempts during initialization.
Step 3: Execute the Watch Loop
Launch the environment with the watch flag:
docker compose -f compose.dev.yml up --watch
Docker's sync engine monitors the host filesystem, applies the defined rules, and routes changes to the appropriate action. Source edits trigger immediate file transfer. The internal tsx watch process detects the update, recompiles TypeScript in memory, and hot-reloads the Express/Fastify server. Dependency changes trigger a background image rebuild. Configuration updates sync and restart the container. The entire stack remains available throughout the cycle.
Pitfall Guide
1. The Double-Sync Trap
Explanation: Developers frequently combine bind mounts with Compose Watch rules targeting the same directory. This creates two concurrent file synchronization mechanisms competing for the same container path, resulting in race conditions, stale files, and watcher instability.
Fix: Choose one synchronization strategy per path. Compose Watch is superior for development workflows. Remove volumes: entries that overlap with develop.watch paths.
2. Syncing Dependency Manifests
Explanation: Using action: sync for package.json or package-lock.json copies the files into the container but does not execute npm install. The running process continues using outdated modules, causing silent failures or version mismatches.
Fix: Always pair dependency manifests with action: rebuild. This ensures the image layer is reconstructed and npm ci runs against the updated lockfile.
3. Watcher Blind Spots
Explanation: Configuring sync without an internal hot-reload process (like tsx watch, nodemon, or Vite) results in files being copied into the container while the Node.js process continues serving stale code. Developers mistakenly believe the sync failed.
Fix: Verify the container's CMD or ENTRYPOINT includes a file watcher. If the application lacks one, switch to sync+restart or inject a watcher process.
4. Unbounded Path Expansion
Explanation: Setting path: ./ or omitting ignore rules causes Docker to monitor every file in the project directory. Build artifacts, .git objects, and temporary files trigger constant sync events, consuming CPU and causing container thrashing.
Fix: Scope watch rules to specific directories (./src, ./config). Explicitly exclude node_modules/, dist/, .git/, and test output directories.
5. Readiness Assumptions
Explanation: Relying solely on depends_on assumes dependent services are immediately usable. Redis and Postgres require startup time for persistence initialization and network binding. The AI router may crash with ECONNREFUSED before dependencies are ready.
Fix: Implement healthcheck directives on all infrastructure services. Reference condition: service_healthy in depends_on to guarantee network availability before the application starts.
6. Production Configuration Leakage
Explanation: Developers occasionally copy compose.dev.yml watch rules into production deployment manifests. Compose Watch is a local development feature; it has no equivalent in Kubernetes, ECS, or standard Docker Swarm. It also exposes host filesystem paths that don't exist in production.
Fix: Maintain strict separation between compose.dev.yml and compose.prod.yml. Use Docker Compose profiles or separate files to prevent development tooling from leaking into CI/CD pipelines.
7. Ignoring Environment Variable Changes
Explanation: .env files or environment overrides are often modified during AI prompt tuning or API key rotation. Compose Watch does not automatically restart containers when environment variables change, leaving services running with stale configuration.
Fix: Add a dedicated watch rule for .env files using action: sync+restart, or manually restart the service after credential updates. Consider using Docker's env_file directive with explicit restart triggers.
Production Bundle
Action Checklist
Decision Matrix
| Scenario | Recommended Approach | Why | Cost Impact |
|---|
| Rapid prompt/tool iteration | sync + internal watcher | Zero process restart, sub-second feedback | Negligible CPU overhead |
| Dependency or lockfile updates | rebuild | Ensures node_modules matches declared versions | 5β15s rebuild window |
| Configuration/routing changes | sync+restart | Forces config reload without full image reconstruction | 1β3s restart window |
| Persistent data (Postgres/Redis) | Named volumes | Guarantees data survives container lifecycle | Storage cost only |
| Production deployment | Standard image build + CI/CD | Compose Watch is dev-only; production requires immutable artifacts | Pipeline compute cost |
Configuration Template
# compose.dev.yml
services:
ai-gateway:
build:
context: .
dockerfile: Dockerfile.local
ports:
- "4000:4000"
environment:
NODE_ENV: development
CACHE_ENDPOINT: redis://cache:6379
PERSISTENCE_URL: postgres://db:5432/ai_platform
depends_on:
cache:
condition: service_healthy
db:
condition: service_healthy
develop:
watch:
- action: sync
path: ./src
target: /workspace/src
ignore:
- "**/*.test.ts"
- "**/*.spec.ts"
- "coverage/"
- ".turbo/"
- action: rebuild
path: ./package.json
- action: rebuild
path: ./package-lock.json
- action: sync+restart
path: ./config
target: /workspace/config
- action: sync+restart
path: ./.env
target: /workspace/.env
cache:
image: redis:7-alpine
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 5
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: ai_platform
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev_secret
ports:
- "5432:5432"
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 3s
retries: 5
volumes:
pgdata:
Quick Start Guide
- Create the development Dockerfile: Save
Dockerfile.local with node:22-slim, copy dependencies, install with npm ci, copy source, and set CMD ["npx", "tsx", "watch", "src/server.ts"].
- Define the compose manifest: Create
compose.dev.yml with your AI service, infrastructure dependencies, and develop.watch rules matching the template above.
- Launch the watch loop: Run
docker compose -f compose.dev.yml up --watch. Docker will build the image, start dependencies, and activate the file synchronization engine.
- Validate the cycle: Edit a TypeScript file in
src/. Observe the terminal output confirming sync completion and tsx hot-reload. Verify the API responds with updated logic without manual restarts.
- Test dependency changes: Modify
package.json to add a new library. Compose Watch will automatically trigger a rebuild, install the dependency, and restart the service. Confirm the new module is available in the running container.