
Docker Compose Watch connects saved files on your computer to a running development service. A rule can copy a changed file, restart the container after copying it, or rebuild the image. The right action depends on how the application reads that file.
This guide builds a small Python example with separate rules for HTML, application code, and the Dockerfile. The configuration and local HTTP behavior passed checks. Docker’s engine was unavailable during preparation, so the image build, Watch events, and container updates remain untested.
The Compose Develop specification defines Watch actions. This example uses three:
| Action | Change in this example | Intended result |
|---|---|---|
| sync | Edit site/index.html | Copy the file; the server reads it on the next request |
| sync+restart | Edit server.py | Copy the code, then restart the container to load it |
| rebuild | Edit Dockerfile | Build the image again and recreate the service |
Basic Watch support starts with Compose 2.22.0; sync+restart requires 2.23.0 or later. Use a recent supported version for the full example, which also includes initial_sync. Our configuration checks used v5.1.4. A successful parse does not verify runtime file watching.
Use a new directory containing server.py, Dockerfile, .dockerignore, compose.yaml, and a site directory containing index.html. Use Linux containers. No external Python packages are needed.
Save this as server.py:
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
SITE = Path(__file__).resolve().parent / "site"
class Handler(SimpleHTTPRequestHandler):
def __init__(self, *args, **kwargs):
super().__init__(*args, directory=str(SITE), **kwargs)
def end_headers(self):
self.send_header("X-Dev-Example", "first")
self.send_header("Cache-Control", "no-store")
super().end_headers()
if __name__ == "__main__":
ThreadingHTTPServer(("0.0.0.0", 8000), Handler).serve_forever()
Save this as site/index.html:
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Compose Watch demo</title>
<h1>First edit</h1>
</html>
The handler reads files from site, adds a diagnostic response header, and asks clients not to cache the response. Python’s http.server documentation cautions against production use. Keep this example on your development machine.
In our local test, the server returned the page, supplied both headers, returned 404 for a missing file, and served an edited HTML file without restarting. Those observations concern the Python program on the host, not Docker synchronization.
Save this as Dockerfile:
FROM python:3.13-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
RUN mkdir -p /app/site && chown -R 1000:1000 /app \
&& command -v stat && command -v mkdir && command -v rmdir
COPY --chown=1000:1000 server.py ./
COPY --chown=1000:1000 site/ ./site/
USER 1000:1000
CMD ["python", "server.py"]
The image runs with UID and GID 1000. The directory setup and COPY --chown give that identity ownership of the example files. The build also checks for stat, mkdir, and rmdir, which Docker lists as Watch prerequisites. The build itself has not been run here.
Watch needs to write to its target paths. A read-only application directory would conflict with these sync rules. Fix ownership for the intended user rather than running the service as root to hide a permissions problem. Use a tested image digest when you need a repeatable environment; the readable tag shown here can change.
Save these exclusions as .dockerignore:
.git
.venv
**/__pycache__
**/*.pyc
**/*.tmp
.env
.env.*
secrets/
The build-context rules keep matching files out of the context sent to the builder. The Dockerfile copies only the server and site files. Keep credentials outside the example; ignore rules do not remove secrets already stored in an older image.
Save this as compose.yaml:
services:
web:
build: .
ports:
- "127.0.0.1:8080:8000"
develop:
watch:
- action: sync
path: ./site
target: /app/site
initial_sync: true
ignore:
- "**/*.tmp"
- action: sync+restart
path: ./server.py
target: /app/server.py
- action: rebuild
path: ./Dockerfile
The three source paths have different roles, so one edit has one intended action. The HTML rule maps the local site directory to /app/site; initial_sync checks that content when attaching a new Watch session to existing containers. It does not add automatic browser refresh.
Docker’s Watch guide explains that source paths are relative to the project directory, while rule-specific ignore patterns are relative to the watched path. Here, temporary files are excluded from both the build context and the HTML watch rule. An ignored change will not produce the update you are looking for.
The published port is 127.0.0.1:8080 on the host and maps to port 8000 inside the container. The Python server listens on all container interfaces so that mapping can reach it. Our Compose networking guide explains host ports and container addresses.
From the example directory, run:
docker compose version
docker compose -p watch-demo config --quiet
docker compose -p watch-demo up --build --watch
config --quiet checks the Compose model. up --build --watch builds and starts the service, then watches for changes. Leave this terminal open and visit http://127.0.0.1:8080/ on the same machine.
Try one change at a time and record the actual result:
site/index.html, save, and refresh the page. The expected result is the new heading after a sync, without rebuilding the image.X-Dev-Example header value in server.py from first to second. After the sync and restart, inspect a new response in the browser’s network tools. The expected header value is second.LABEL dev.example="second" to the Dockerfile and save. Watch should report a rebuild and recreate the service. Confirm it returns the page after that operation finishes.These are verification steps, not recorded container results. A Python syntax error can prevent the service from starting after an edit; read its logs before changing the Watch rules.
The separate docker compose watch command keeps Watch output apart from application logs. Its --no-up option skips the initial build and start, so use that option only when the intended services are already running. The CLI also documents image pruning during rebuilds.
In another terminal, use these checks from the same directory:
docker compose -p watch-demo ps
docker compose -p watch-demo logs --tail 30 web
docker compose -p watch-demo exec web id
docker compose -p watch-demo exec web sh -c "test -w /app/site && test -w /app/server.py"
The final command succeeds only if both example targets are writable by the container user. It checks permissions, not whether Watch processed an event. Our Docker logs guide explains how to inspect application output.
Choose a clear mechanism for each development path. A bind mount can suit direct host-file access; Watch adds explicit update actions. Neither is a backup system for application data. Our Docker storage guide covers persistent data separately.
Stop the attached up --watch session with Ctrl+C. When you have finished with this disposable project, run:
docker compose -p watch-demo down
compose down removes its service containers and project network. The host source files remain your working copy. For production, build and test a deliberate release image instead of relying on an open development Watch session.
Pick one AI, compute, or storage workload and see the difference for yourself. Spin it up in minutes, or let our team map your fastest path to production.