From 7c92c03eed7fbde44ae5e95cc785addfdffc5a4e Mon Sep 17 00:00:00 2001 From: Khalid Abdi Date: Wed, 15 Jul 2026 20:27:03 +0300 Subject: [PATCH] docker: stop a Windows checkout from breaking the backend image MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo had no .gitattributes at all, so nothing pinned line endings — `git ls-files --eol backend/docker-entrypoint.sh` showed an empty attr/. Git for Windows installs with core.autocrlf=true, which rewrites the entrypoint to CRLF on checkout. The image then ships a script whose shebang is "#!/bin/sh\r" and the container dies at startup with exec /usr/local/bin/docker-entrypoint.sh: no such file or directory naming the file it just copied in, which reads like the COPY failed. Verified by building an image from a deliberately CRLF-ified copy of the entrypoint: it reproduces that exact error, and passes with the sed. Pin *.sh (and the Dockerfiles) to LF, and strip CR in the image before chmod as well — .gitattributes only helps fresh clones, and a Windows contributor who cloned before this commit still has CRLF on disk. Renormalizing changes no tracked file today; this is purely a guard. The arm64/Apple Silicon failure that prompted this is already fixed by 6127a0a (Turbopack has no musl/arm64 binding; the build uses webpack). Confirmed by building both images on an arm64 Mac. Release publishes linux/amd64 + linux/arm64, there are no bind mounts, no platform: keys and no host.docker.internal, so the remaining cross-platform hazard was just the line endings. Also tighten .dockerignore: backend was shipping docker-compose.tunnel .yml, fly.toml, railway.json, render.yaml, .env.example and drizzle.config.ts into the build context; frontend was shipping tsconfig.tsbuildinfo. Co-Authored-By: Claude Opus 4.8 --- .gitattributes | 32 ++++++++++++++++++++++++++++++++ backend/.dockerignore | 10 ++++++++++ backend/Dockerfile | 8 +++++++- frontend/.dockerignore | 3 +++ 4 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e547615 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,32 @@ +# Normalize line endings so the repo builds the same on Windows, macOS and Linux. +# +# Git for Windows installs with core.autocrlf=true by default, which rewrites +# checked-out files to CRLF. That is harmless for source we only ever compile, +# but fatal for anything the Docker images execute: a shell script whose shebang +# becomes "#!/bin/sh\r" fails at container start with +# exec /usr/local/bin/docker-entrypoint.sh: no such file or directory +# which names the file it just copied in and reads like the file is missing. +* text=auto + +# Scripts that run inside a Linux container must stay LF regardless of platform. +*.sh text eol=lf +docker-entrypoint.sh text eol=lf +Dockerfile text eol=lf +*.Dockerfile text eol=lf +.dockerignore text eol=lf + +# Lockfiles: keep LF so they don't churn across platforms. +package-lock.json text eol=lf + +# Binary assets — never touch these. +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.ico binary +*.webp binary +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary +*.pdf binary diff --git a/backend/.dockerignore b/backend/.dockerignore index c048473..6f80bac 100644 --- a/backend/.dockerignore +++ b/backend/.dockerignore @@ -6,5 +6,15 @@ dist *.log .DS_Store Dockerfile +.dockerignore docker-compose.yml +docker-compose.tunnel.yml README.md +CLAUDE.md +# Deploy manifests for other targets — not needed inside the image. +fly.toml +railway.json +render.yaml +# Tracked, but only the runtime migrator (dist/migrate.js) runs in the image. +drizzle.config.ts +.env.example diff --git a/backend/Dockerfile b/backend/Dockerfile index 0e834a7..3ca40bd 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -19,7 +19,13 @@ RUN npm ci --omit=dev COPY --from=build /app/dist ./dist COPY --from=build /app/drizzle ./drizzle COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -RUN chmod +x /usr/local/bin/docker-entrypoint.sh +# Strip CR before chmod. .gitattributes keeps this file LF on fresh clones, but +# a Windows checkout made before that existed still has CRLF on disk, and a +# "#!/bin/sh\r" shebang fails at container start with a "no such file or +# directory" error that names the file it just copied in. Cheap to make the +# image self-healing rather than rely on every contributor re-cloning. +RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \ + && chmod +x /usr/local/bin/docker-entrypoint.sh EXPOSE 4000 # The entrypoint auto-generates any missing secrets, then we apply migrations # and start the API. diff --git a/frontend/.dockerignore b/frontend/.dockerignore index 83c7961..f4a6610 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -7,3 +7,6 @@ npm-debug.log* .DS_Store Dockerfile .dockerignore +tsconfig.tsbuildinfo +CLAUDE.md +AGENTS.md