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/.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 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/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/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 357984e..4879715 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -58,20 +58,66 @@ 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: - - .:/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 + 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 + - 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 command: > sh -c " mix deps.get && + mix assets.vendor && 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