[{"data":1,"prerenderedAt":504},["ShallowReactive",2],{"mdc--r1g5xh-key":3,"mdc-hs66x1-key":41,"mdc--hc60r1-key":133,"mdc--c8bb49-key":142,"mdc-gjdvjf-key":154,"mdc-27mnla-key":248,"mdc--c716iz-key":258,"mdc--8bk7gl-key":335,"mdc-vbw0vz-key":347,"mdc-ld1ec4-key":387},{"data":4,"body":5},{},{"type":6,"children":7},"root",[8,16,30],{"type":9,"tag":10,"props":11,"children":12},"element","p",{},[13],{"type":14,"value":15},"text","The development loop that puts people off containers is: edit a file, rebuild the image, recreate the container, wait, refresh. Thirty seconds for a one-character change.",{"type":9,"tag":10,"props":17,"children":18},{},[19,21,28],{"type":14,"value":20},"The old workaround was bind-mounting your source in, which works until it does not — the dependency directory disappears under the mount, the UIDs are wrong, and a change to ",{"type":9,"tag":22,"props":23,"children":25},"code",{"className":24},[],[26],{"type":14,"value":27},"package.json",{"type":14,"value":29}," needs a rebuild the mount cannot give you.",{"type":9,"tag":10,"props":31,"children":32},{},[33,39],{"type":9,"tag":34,"props":35,"children":36},"strong",{},[37],{"type":14,"value":38},"Compose Watch",{"type":14,"value":40}," is the built-in answer, and it distinguishes between changes that need copying and changes that need rebuilding.",{"data":42,"body":43},{},{"type":6,"children":44},[45,52,65,75,105,119],{"type":9,"tag":46,"props":47,"children":49},"h2",{"id":48},"the-three-actions",[50],{"type":14,"value":51},"The three actions",{"type":9,"tag":53,"props":54,"children":60},"pre",{"className":55,"code":57,"language":58,"meta":59},[56],"language-yaml","services:\n  web:\n    build: .\n    command: npm run dev\n    ports:\n      - \"127.0.0.1:3000:3000\"\n    develop:\n      watch:\n        - action: sync\n          path: ./src\n          target: /app/src\n          ignore:\n            - node_modules/\n\n        - action: rebuild\n          path: ./package.json\n\n        - action: sync+restart\n          path: ./config/app.yaml\n          target: /app/config/app.yaml\n","yaml","",[61],{"type":9,"tag":22,"props":62,"children":63},{"__ignoreMap":59},[64],{"type":14,"value":57},{"type":9,"tag":53,"props":66,"children":70},{"className":67,"code":69,"language":14},[68],"language-text","docker compose up --watch          # run the stack and watch\ndocker compose watch               # watch only, logs kept separate\n",[71],{"type":9,"tag":22,"props":72,"children":73},{"__ignoreMap":59},[74],{"type":14,"value":69},{"type":9,"tag":10,"props":76,"children":77},{},[78,87,89,95,97,103],{"type":9,"tag":34,"props":79,"children":80},{},[81],{"type":9,"tag":22,"props":82,"children":84},{"className":83},[],[85],{"type":14,"value":86},"sync",{"type":14,"value":88}," copies changed files into the running container. Nothing restarts. For anything with hot reload — Vite, nodemon, ",{"type":9,"tag":22,"props":90,"children":92},{"className":91},[],[93],{"type":14,"value":94},"flask --debug",{"type":14,"value":96},", ",{"type":9,"tag":22,"props":98,"children":100},{"className":99},[],[101],{"type":14,"value":102},"air",{"type":14,"value":104}," — this is the fast path, and it is genuinely instant.",{"type":9,"tag":10,"props":106,"children":107},{},[108,117],{"type":9,"tag":34,"props":109,"children":110},{},[111],{"type":9,"tag":22,"props":112,"children":114},{"className":113},[],[115],{"type":14,"value":116},"rebuild",{"type":14,"value":118}," builds a new image and replaces the container. For a dependency manifest, or a compiled language where the source is not what runs.",{"type":9,"tag":10,"props":120,"children":121},{},[122,131],{"type":9,"tag":34,"props":123,"children":124},{},[125],{"type":9,"tag":22,"props":126,"children":128},{"className":127},[],[129],{"type":14,"value":130},"sync+restart",{"type":14,"value":132}," copies the file and restarts the container's process without rebuilding. For configuration a running process reads once at startup.",{"data":134,"body":135},{},{"type":6,"children":136},[137],{"type":9,"tag":138,"props":139,"children":141},"terminal-teaser",{":lines":140},"[{\"cmd\":\"docker compose up --watch\",\"out\":\"✔ Container app-web-1  Started\\nWatch enabled\"},{\"cmd\":\"# edit src/routes.js on the host\",\"out\":\"Syncing service \\\"web\\\" after changes were detected:\\n  - src/routes.js\"},{\"cmd\":\"# edit package.json on the host\",\"out\":\"Rebuilding service \\\"web\\\" after changes were detected...\\n✔ Container app-web-1  Recreated\"}]",[],{"data":143,"body":144},{},{"type":6,"children":145},[146],{"type":9,"tag":147,"props":148,"children":153},"quiz",{":answer":149,":options":150,"explanation":151,"question":152},"0","[\"`rebuild` — the dependency set changed, so the image has to be built again\",\"`sync` — copy the file in and the running process will notice\",\"`sync+restart` — copy it and restart the process\"]","Copying the file in does nothing on its own; the packages are installed at build time. `rebuild` is the only action that reruns the install. The usual pairing is `sync` on the source directory and `rebuild` on the manifest.","Which action fits a change to `requirements.txt` in a Python service?",[],{"data":155,"body":156},{},{"type":6,"children":157},[158,164,176,235],{"type":9,"tag":46,"props":159,"children":161},{"id":160},"why-this-beats-a-bind-mount",[162],{"type":14,"value":163},"Why this beats a bind mount",{"type":9,"tag":10,"props":165,"children":166},{},[167,169,174],{"type":14,"value":168},"Watch ",{"type":9,"tag":34,"props":170,"children":171},{},[172],{"type":14,"value":173},"copies into",{"type":14,"value":175}," the container rather than mounting over it. That single difference removes the three classic bind-mount problems:",{"type":9,"tag":177,"props":178,"children":179},"ul",{},[180,215,225],{"type":9,"tag":181,"props":182,"children":183},"li",{},[184,189,191,197,199,205,207,213],{"type":9,"tag":34,"props":185,"children":186},{},[187],{"type":14,"value":188},"Nothing gets hidden.",{"type":14,"value":190}," The image's ",{"type":9,"tag":22,"props":192,"children":194},{"className":193},[],[195],{"type":14,"value":196},"node_modules",{"type":14,"value":198}," at ",{"type":9,"tag":22,"props":200,"children":202},{"className":201},[],[203],{"type":14,"value":204},"/app/node_modules",{"type":14,"value":206}," stays exactly where it was, because no mount is covering ",{"type":9,"tag":22,"props":208,"children":210},{"className":209},[],[211],{"type":14,"value":212},"/app",{"type":14,"value":214},". No anonymous-volume workaround needed.",{"type":9,"tag":181,"props":216,"children":217},{},[218,223],{"type":9,"tag":34,"props":219,"children":220},{},[221],{"type":14,"value":222},"Ownership is the container's.",{"type":14,"value":224}," Files arrive owned by the container's user, so no permission-denied on a file your host user created.",{"type":9,"tag":181,"props":226,"children":227},{},[228,233],{"type":9,"tag":34,"props":229,"children":230},{},[231],{"type":14,"value":232},"Rebuilds are part of the model.",{"type":14,"value":234}," A bind mount has no concept of \"this change needs a new image\". Watch does.",{"type":9,"tag":10,"props":236,"children":237},{},[238,240,246],{"type":14,"value":239},"The cost is that it is one-directional: files a container generates do not come back to the host. Anything you need on the host — a generated migration, a lockfile update — still wants a bind mount or a ",{"type":9,"tag":22,"props":241,"children":243},{"className":242},[],[244],{"type":14,"value":245},"docker compose cp",{"type":14,"value":247},".",{"data":249,"body":250},{},{"type":6,"children":251},[252],{"type":9,"tag":147,"props":253,"children":257},{":answer":149,":options":254,"explanation":255,"question":256},"[\"It copies files into the container instead of mounting over the directory, so nothing is hidden\",\"It mounts `node_modules` separately by default\",\"It reinstalls dependencies on every change\"]","A mount replaces the view at its target and hides whatever the image put there. A copy leaves the rest of the directory intact — which is why the anonymous-volume trick stops being necessary.","You bind-mounted your project over `/app` and the image's installed `node_modules` vanished. How does Watch avoid this?",[],{"data":259,"body":260},{},{"type":6,"children":261},[262,274,283,301],{"type":9,"tag":46,"props":263,"children":265},{"id":264},"ignore-and-why-it-matters-more-than-it-looks",[266,272],{"type":9,"tag":22,"props":267,"children":269},{"className":268},[],[270],{"type":14,"value":271},"ignore",{"type":14,"value":273},", and why it matters more than it looks",{"type":9,"tag":53,"props":275,"children":278},{"className":276,"code":277,"language":58,"meta":59},[56],"        - action: sync\n          path: .\n          target: /app\n          ignore:\n            - node_modules/\n            - .git/\n            - \"*.log\"\n            - dist/\n",[279],{"type":9,"tag":22,"props":280,"children":281},{"__ignoreMap":59},[282],{"type":14,"value":277},{"type":9,"tag":10,"props":284,"children":285},{},[286,291,293,299],{"type":9,"tag":22,"props":287,"children":289},{"className":288},[],[290],{"type":14,"value":271},{"type":14,"value":292}," paths are relative to ",{"type":9,"tag":22,"props":294,"children":296},{"className":295},[],[297],{"type":14,"value":298},"path",{"type":14,"value":300},". Without them, watching a project root means watching every dependency directory and every build artifact — thousands of files, a constant stream of sync events, and on macOS and Windows a noticeable CPU cost from the filesystem event bridge.",{"type":9,"tag":10,"props":302,"children":303},{},[304,306,312,314,319,321,326,328,333],{"type":14,"value":305},"Watch also honours ",{"type":9,"tag":22,"props":307,"children":309},{"className":308},[],[310],{"type":14,"value":311},".dockerignore",{"type":14,"value":313}," for ",{"type":9,"tag":22,"props":315,"children":317},{"className":316},[],[318],{"type":14,"value":116},{"type":14,"value":320}," actions, but ",{"type":9,"tag":22,"props":322,"children":324},{"className":323},[],[325],{"type":14,"value":271},{"type":14,"value":327}," is what governs ",{"type":9,"tag":22,"props":329,"children":331},{"className":330},[],[332],{"type":14,"value":86},{"type":14,"value":334},". They are worth keeping consistent.",{"data":336,"body":337},{},{"type":6,"children":338},[339],{"type":9,"tag":340,"props":341,"children":346},"fill-blank",{":answer":342,"hint":343,"placeholder":344,"prompt":345},"[\"docker compose up --watch\",\"docker compose up -w\",\"docker compose watch\"]","The normal start command plus one flag.","docker compose up ...","Start the stack in the foreground with file watching enabled, in one command.",[],{"data":348,"body":349},{},{"type":6,"children":350},[351,357,366],{"type":9,"tag":46,"props":352,"children":354},{"id":353},"a-complete-development-file",[355],{"type":14,"value":356},"A complete development file",{"type":9,"tag":53,"props":358,"children":361},{"className":359,"code":360,"language":58,"meta":59},[56],"services:\n  api:\n    build:\n      context: .\n      target: dev\n    command: npm run dev\n    ports:\n      - \"127.0.0.1:3000:3000\"\n    environment:\n      NODE_ENV: development\n      DATABASE_URL: postgres://app:secret@db:5432/app\n    depends_on:\n      db:\n        condition: service_healthy\n    develop:\n      watch:\n        - action: sync\n          path: ./src\n          target: /app/src\n        - action: sync\n          path: ./public\n          target: /app/public\n        - action: rebuild\n          path: ./package.json\n\n  db:\n    image: postgres:17-alpine\n    environment:\n      POSTGRES_USER: app\n      POSTGRES_PASSWORD: secret\n      POSTGRES_DB: app\n    volumes:\n      - pgdata:/var/lib/postgresql/data\n    healthcheck:\n      test: [\"CMD-SHELL\", \"pg_isready -U app\"]\n      interval: 5s\n      retries: 5\n\nvolumes:\n  pgdata:\n",[362],{"type":9,"tag":22,"props":363,"children":364},{"__ignoreMap":59},[365],{"type":14,"value":360},{"type":9,"tag":10,"props":367,"children":368},{},[369,371,377,379,385],{"type":14,"value":370},"Note ",{"type":9,"tag":22,"props":372,"children":374},{"className":373},[],[375],{"type":14,"value":376},"target: dev",{"type":14,"value":378}," on the build. Watch pairs naturally with a multi-stage Dockerfile that has a ",{"type":9,"tag":22,"props":380,"children":382},{"className":381},[],[383],{"type":14,"value":384},"dev",{"type":14,"value":386}," stage carrying the dev dependencies and the watcher, and a production stage that has neither.",{"data":388,"body":389},{},{"type":6,"children":390},[391,499],{"type":9,"tag":392,"props":393,"children":395},"deep-dive",{"title":394},"When Watch is the wrong tool",[396,409,414,453,470,487],{"type":9,"tag":10,"props":397,"children":398},{},[399,401,407],{"type":14,"value":400},"Watch only applies to services built from a local ",{"type":9,"tag":22,"props":402,"children":404},{"className":403},[],[405],{"type":14,"value":406},"build:",{"type":14,"value":408}," context. A service running a pulled image has nothing to sync into and nothing to rebuild, so the key is ignored.",{"type":9,"tag":10,"props":410,"children":411},{},[412],{"type":14,"value":413},"Three situations where something else is a better fit:",{"type":9,"tag":10,"props":415,"children":416},{},[417,422,424,429,431,436,438,444,446,451],{"type":9,"tag":34,"props":418,"children":419},{},[420],{"type":14,"value":421},"A compiled language with a slow build.",{"type":14,"value":423}," ",{"type":9,"tag":22,"props":425,"children":427},{"className":426},[],[428],{"type":14,"value":116},{"type":14,"value":430}," on every source change means a full image build. Native tooling with a file watcher inside the container — ",{"type":9,"tag":22,"props":432,"children":434},{"className":433},[],[435],{"type":14,"value":102},{"type":14,"value":437}," for Go, ",{"type":9,"tag":22,"props":439,"children":441},{"className":440},[],[442],{"type":14,"value":443},"cargo watch",{"type":14,"value":445}," for Rust — plus ",{"type":9,"tag":22,"props":447,"children":449},{"className":448},[],[450],{"type":14,"value":86},{"type":14,"value":452}," on the source is usually faster, because the incremental compiler cache survives.",{"type":9,"tag":10,"props":454,"children":455},{},[456,461,463,468],{"type":9,"tag":34,"props":457,"children":458},{},[459],{"type":14,"value":460},"You need generated files back on the host.",{"type":14,"value":462}," Watch pushes one way. A framework that writes migrations, updates a lockfile, or regenerates types is producing artifacts you want committed. Bind-mount that specific directory, or ",{"type":9,"tag":22,"props":464,"children":466},{"className":465},[],[467],{"type":14,"value":245},{"type":14,"value":469}," after the fact.",{"type":9,"tag":10,"props":471,"children":472},{},[473,478,480,485],{"type":9,"tag":34,"props":474,"children":475},{},[476],{"type":14,"value":477},"Very large trees on macOS or Windows.",{"type":14,"value":479}," File events cross a VM boundary, and a watch over tens of thousands of files is measurably expensive. Narrow ",{"type":9,"tag":22,"props":481,"children":483},{"className":482},[],[484],{"type":14,"value":298},{"type":14,"value":486}," to the directories that actually change, rather than watching the root and ignoring most of it.",{"type":9,"tag":10,"props":488,"children":489},{},[490,492,497],{"type":14,"value":491},"The general shape worth keeping: ",{"type":9,"tag":34,"props":493,"children":494},{},[495],{"type":14,"value":496},"sync the code, rebuild the dependencies, and keep the dev stage separate from the production one.",{"type":14,"value":498}," Everything else is tuning.",{"type":9,"tag":10,"props":500,"children":501},{},[502],{"type":14,"value":503},"Next up: healthchecks, signals, and lifecycle — making a container start, stop, and fail the way you intended.",1787908868309]