From 302777597bc0043a04e3f38337c6a93c289def96 Mon Sep 17 00:00:00 2001 From: Bashar Qassis <23612682+bashar-qassis@users.noreply.github.com> Date: Fri, 3 Apr 2026 00:58:13 +0300 Subject: [PATCH 1/5] fix: replace bind-mount with develop.watch to fix Docker slowness on Windows The - .:/app bind-mount crosses the WSL2<->Windows filesystem boundary on every source file read (the Elixir compiler reads all .ex files on recompile), causing significant latency. Replace it with Docker Compose develop.watch, which syncs changed files via Docker's internal protocol, bypassing the slow bridge entirely. - Dockerfile.dev: add COPY . . to bake initial source into the image layer - docker-compose.dev.yml: remove .:/app bind-mount, add develop.watch for lib/ config/ priv/ test/ assets/ with rebuild triggers on mix.exs/mix.lock - .dockerignore: add .worktrees/ exclusion; clarify test/ is synced at runtime New workflow: `docker compose -f docker-compose.dev.yml watch` --- .dockerignore | 4 +++- Dockerfile.dev | 7 ++++++- docker-compose.dev.yml | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 1a80d83..ddc1941 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ # Version control .git .gitignore +.worktrees # Environment secrets — NEVER include in build context .env @@ -19,7 +20,8 @@ node_modules *.swp *.swo -# Documentation and tests (not needed in production image) +# Documentation and tests (not needed in built images) +# Note: test/ is synced into the dev container at runtime via `docker compose watch`. docs/ test/ *.md diff --git a/Dockerfile.dev b/Dockerfile.dev index 8dbea89..9c3f174 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -1,4 +1,5 @@ -# Development Dockerfile — source code is bind-mounted, not copied +# Development Dockerfile — source is baked in at build time; changes are synced +# at runtime via `docker compose watch` (avoids slow Windows bind-mount I/O). FROM elixir:1.18-otp-28-alpine RUN apk add --no-cache git build-base inotify-tools nodejs npm @@ -7,6 +8,10 @@ WORKDIR /app RUN mix local.hex --force && mix local.rebar --force +# Bake source into the image layer. deps/_build/node_modules are excluded by +# .dockerignore and will be populated into named volumes at container start. +COPY . . + EXPOSE 4000 CMD ["mix", "phx.server"] diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 357984e..ee9c974 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -59,10 +59,42 @@ services: SMTP_HOST: mailpit PHX_IP: "0.0.0.0" volumes: - - .:/app + # No Windows bind-mount for source — use `docker compose watch` instead. + # deps/_build/node_modules stay as named volumes for fast recompilation. - deps:/app/deps - build:/app/_build - node_modules:/app/assets/node_modules + develop: + watch: + # Sync Elixir source, config, migrations, and static assets into the + # container via Docker's internal protocol — bypasses the slow WSL2↔Windows + # filesystem bridge that makes `- .:/app` bind-mounts sluggish on Windows. + - action: sync + path: lib + target: /app/lib + - action: sync + path: config + target: /app/config + - action: sync + path: priv + target: /app/priv + - action: sync + path: test + target: /app/test + - action: sync + path: assets + target: /app/assets + ignore: + - node_modules/ + # Dependency or tooling changes require a full image rebuild. + - action: rebuild + path: mix.exs + - action: rebuild + path: mix.lock + - action: rebuild + path: assets/package.json + - action: rebuild + path: assets/package-lock.json depends_on: postgres: condition: service_healthy From a1880c8d3265bfe04c4c93a605cc97de26306e64 Mon Sep 17 00:00:00 2001 From: Bashar Qassis <23612682+bashar-qassis@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:07:51 +0300 Subject: [PATCH 2/5] fix: tune BEAM scheduler and disable expensive LiveView checks in Docker Two root causes of sluggish page loads in Docker on Windows: 1. BEAM busy-wait: idle schedulers spin on vCPUs by default, starving each other in WSL2's shared VM. ERL_FLAGS +sbwt none disables spinning so schedulers sleep immediately when idle, reducing per-request latency. 2. enable_expensive_runtime_checks: LiveView validates assigns, slots, and other invariants on every render. In a constrained container environment this is noticeably slow. PHX_EXPENSIVE_CHECKS=false disables it in Docker while native dev keeps it on (env var defaults to "true"). --- config/dev.exs | 7 +++++-- docker-compose.dev.yml | 7 +++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/config/dev.exs b/config/dev.exs index cabcfaf..670bc94 100644 --- a/config/dev.exs +++ b/config/dev.exs @@ -104,5 +104,8 @@ config :phoenix_live_view, # Changing this configuration will require mix clean and a full recompile. debug_heex_annotations: true, debug_attributes: true, - # Enable helpful, but potentially expensive runtime checks - enable_expensive_runtime_checks: true + # Enable helpful, but potentially expensive runtime checks. + # Set PHX_EXPENSIVE_CHECKS=false (e.g. in docker-compose.dev.yml) to turn + # these off in constrained environments like Docker on Windows where they + # noticeably slow down LiveView renders. + enable_expensive_runtime_checks: System.get_env("PHX_EXPENSIVE_CHECKS", "true") == "true" diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index ee9c974..37a97f6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -58,6 +58,13 @@ services: DB_PORT: "5432" SMTP_HOST: mailpit PHX_IP: "0.0.0.0" + # Disable BEAM scheduler busy-waiting. By default idle schedulers spin on + # their vCPU rather than sleeping, starving other threads in WSL2's shared + # VM environment and adding latency to every request. + ERL_FLAGS: "+sbwt none +sbwtdcpu none +sbwtdio none" + # Disable expensive LiveView runtime checks (assign validation, slot checks, + # etc.) which add overhead on every render cycle — too costly in Docker on Windows. + PHX_EXPENSIVE_CHECKS: "false" volumes: # No Windows bind-mount for source — use `docker compose watch` instead. # deps/_build/node_modules stay as named volumes for fast recompilation. From f4e80521573ad5e6d0fe0a2b2525e4d8595c69ad Mon Sep 17 00:00:00 2001 From: Bashar Qassis <23612682+bashar-qassis@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:21:09 +0300 Subject: [PATCH 3/5] =?UTF-8?q?fix:=20CSS=20broken=20=E2=80=94=20exclude?= =?UTF-8?q?=20static/assets=20from=20priv=20sync,=20build=20assets=20befor?= =?UTF-8?q?e=20server?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit develop.watch was syncing priv/ from the Windows host into the container, overwriting freshly-compiled Tailwind/esbuild output with stale host files. Exclude priv/static/assets/ from the sync so the container's own build is authoritative. Add mix assets.build to the startup sequence so CSS/JS are ready before the first request rather than racing the watcher's initial build. Fix .gitattributes to enforce LF for .husky/* so git on Windows no longer checks out the pre-commit hook with CRLF, which causes Exec format error in bash. This unblocks all future commits without --no-verify. --- .gitattributes | 3 ++- .husky/pre-commit | 0 docker-compose.dev.yml | 6 ++++++ 3 files changed, 8 insertions(+), 1 deletion(-) mode change 100755 => 100644 .husky/pre-commit diff --git a/.gitattributes b/.gitattributes index d705a4c..0fade03 100644 --- a/.gitattributes +++ b/.gitattributes @@ -10,7 +10,8 @@ *.toml text eol=lf # Shell scripts — LF -*.sh text eol=lf +*.sh text eol=lf +.husky/* text eol=lf # Windows scripts — CRLF *.bat text eol=crlf diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100755 new mode 100644 diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 37a97f6..320b645 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -85,6 +85,11 @@ services: - action: sync path: priv target: /app/priv + ignore: + # Tailwind/esbuild compile into priv/static/assets/ inside the + # container. Syncing the host copy (which may be stale or absent) + # would overwrite freshly-built CSS/JS with wrong files. + - static/assets/ - action: sync path: test target: /app/test @@ -111,6 +116,7 @@ services: cd assets && npm install && cd .. && mix tailwind.install --if-missing && mix esbuild.install --if-missing && + mix assets.build && mix ecto.create && mix ecto.migrate && exec mix phx.server From 98a332985abead9381f240728d513c6dfa6bc17d Mon Sep 17 00:00:00 2001 From: Bashar Qassis <23612682+bashar-qassis@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:21:18 +0300 Subject: [PATCH 4/5] fix: restore executable bit on pre-commit hook --- .husky/pre-commit | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .husky/pre-commit diff --git a/.husky/pre-commit b/.husky/pre-commit old mode 100644 new mode 100755 From dc4589cee8ba4bb4c722e00135a1b4e1000d4e31 Mon Sep 17 00:00:00 2001 From: Bashar Qassis <23612682+bashar-qassis@users.noreply.github.com> Date: Fri, 3 Apr 2026 01:29:13 +0300 Subject: [PATCH 5/5] fix: run mix assets.vendor on startup to install heroicons vendor files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit heroicons SVGs are installed to assets/vendor/ by a mix task from the heroicons dependency. With the bind-mount those files came from the Windows host; with COPY . . they must be generated at container startup. Also add #!/bin/sh shebang to pre-commit hook — the real fix for Exec format error on Windows. Without a shebang git tries to exec the file as a binary and fails; with one, sh is invoked which handles the script correctly. eol=lf in .gitattributes does not override core.autocrlf=true on checkout. --- .husky/pre-commit | 1 + docker-compose.dev.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.husky/pre-commit b/.husky/pre-commit index e48f617..09f4d4f 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ +#!/bin/sh mix compile --warnings-as-errors && mix format --check-formatted && mix credo --strict diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 320b645..4879715 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -113,6 +113,7 @@ services: command: > sh -c " mix deps.get && + mix assets.vendor && cd assets && npm install && cd .. && mix tailwind.install --if-missing && mix esbuild.install --if-missing &&