Relative paths: COPY destination and CMD used relative paths (./webhooker), depending on WORKDIR context.
Dynamic linking (the actual root cause): The binary was built with CGO enabled on Debian (glibc) via make build, but deployed to an Alpine runtime (musl). The kernel couldn't find the glibc dynamic linker (/lib64/ld-linux-x86-64.so.2), producing the misleading "no such file or directory" error — even though the file existed on disk.
Fix
Absolute paths throughout: COPY --from=builder /build/bin/webhooker /app/webhooker and CMD ["/app/webhooker"] — no reliance on WORKDIR.
Static rebuild for Alpine: Added a RUN CGO_ENABLED=1 go build -ldflags '-extldflags "-static"' -o bin/webhooker ./cmd/webhooker step after make check. This rebuilds the binary with static linking so it runs on Alpine without glibc. The make check step still runs normally (formatting, linting, tests, dynamic build) — the static rebuild is only for the deployment binary.
Verification
docker build . passes (all checks green)
Container starts successfully and initializes the Fx dependency graph
The README already stated "The runtime binary is statically linked and runs on Alpine" — this fix makes that claim actually true.
Closes #48
## Problem
The Docker container failed to start with:
```
exec ./webhooker: no such file or directory
```
Two root causes:
1. **Relative paths**: `COPY` destination and `CMD` used relative paths (`./webhooker`), depending on `WORKDIR` context.
2. **Dynamic linking** (the actual root cause): The binary was built with CGO enabled on Debian (glibc) via `make build`, but deployed to an Alpine runtime (musl). The kernel couldn't find the glibc dynamic linker (`/lib64/ld-linux-x86-64.so.2`), producing the misleading "no such file or directory" error — even though the file existed on disk.
## Fix
- **Absolute paths throughout**: `COPY --from=builder /build/bin/webhooker /app/webhooker` and `CMD ["/app/webhooker"]` — no reliance on WORKDIR.
- **Static rebuild for Alpine**: Added a `RUN CGO_ENABLED=1 go build -ldflags '-extldflags "-static"' -o bin/webhooker ./cmd/webhooker` step after `make check`. This rebuilds the binary with static linking so it runs on Alpine without glibc. The `make check` step still runs normally (formatting, linting, tests, dynamic build) — the static rebuild is only for the deployment binary.
## Verification
- `docker build .` passes (all checks green)
- Container starts successfully and initializes the Fx dependency graph
- The README already stated "The runtime binary is statically linked and runs on Alpine" — this fix makes that claim actually true.
The container failed to start with 'exec ./webhooker: no such file or
directory'. Two issues:
1. Relative paths: COPY destination and CMD used relative paths (./webhooker).
Changed to absolute paths (/app/webhooker) throughout.
2. Dynamic linking: The binary was built with CGO on Debian (glibc) but
deployed to Alpine (musl). The kernel couldn't find the glibc dynamic
linker, producing the misleading 'no such file or directory' error.
Added a static rebuild step after make check so the binary runs on
Alpine without glibc.
✅Fix relative paths — COPY destination changed from . to /app/webhooker; CMD changed from ["./webhooker"] to ["/app/webhooker"]. No WORKDIR dependency.
✅Fix dynamic linking — Added static rebuild step (CGO_ENABLED=1 go build -ldflags '-extldflags "-static"') after make check, so the deploy binary runs on Alpine without glibc.
Policy Compliance Check
No violations found. Specifically verified:
External images pinned by @sha256: hash with version/date comments ✅
golangci-lint installed via hash-verified archive download ✅
.golangci.yml unmodified ✅
Makefile unmodified ✅
No test modifications ✅
CI workflow unmodified ✅
make check still runs as build step (fmt-check, lint, test, build) ✅
Build Result
docker build .passes — all tests green, all linting passes
Linker warnings about dlopen and getaddrinfo in static builds are expected (standard glibc static linking caveats, non-fatal; sqlite amalgamated code doesn't call dlopen at runtime, and Go has a pure-Go DNS fallback)
Container Verification
✅ Container starts successfully (no more exec ./webhooker: no such file or directory)
✅ Fx dependency graph initializes fully (all 12 providers registered)
✅ HTTP server starts and listens on :8080
✅ Health check at /.well-known/healthcheck responds with 200
Notes
The approach of running make check with a normal CGO build (for tests/linting) followed by a separate static rebuild for the deploy binary is sound. It adds ~2s to the Docker build but maintains the integrity of the check pipeline while producing a binary that actually works on Alpine. The PR description and inline comments clearly explain the rationale.
Verdict: PASS✅
Minimal, correct, well-documented fix that addresses both root causes of the container breakage.
## Code Review: PR #49 — fix: use absolute paths and static linking in Dockerfile
### Requirements Checklist ([Issue #48](https://git.eeqj.de/sneak/webhooker/issues/48))
- ✅ **Fix relative paths** — `COPY` destination changed from `.` to `/app/webhooker`; `CMD` changed from `["./webhooker"]` to `["/app/webhooker"]`. No WORKDIR dependency.
- ✅ **Fix dynamic linking** — Added static rebuild step (`CGO_ENABLED=1 go build -ldflags '-extldflags "-static"'`) after `make check`, so the deploy binary runs on Alpine without glibc.
### Policy Compliance Check
No violations found. Specifically verified:
- External images pinned by `@sha256:` hash with version/date comments ✅
- golangci-lint installed via hash-verified archive download ✅
- `.golangci.yml` unmodified ✅
- `Makefile` unmodified ✅
- No test modifications ✅
- CI workflow unmodified ✅
- `make check` still runs as build step (fmt-check, lint, test, build) ✅
### Build Result
- `docker build .` **passes** — all tests green, all linting passes
- Linker warnings about `dlopen` and `getaddrinfo` in static builds are expected (standard glibc static linking caveats, non-fatal; sqlite amalgamated code doesn't call dlopen at runtime, and Go has a pure-Go DNS fallback)
### Container Verification
- ✅ Container starts successfully (no more `exec ./webhooker: no such file or directory`)
- ✅ Fx dependency graph initializes fully (all 12 providers registered)
- ✅ HTTP server starts and listens on `:8080`
- ✅ Health check at `/.well-known/healthcheck` responds with 200
### Notes
The approach of running `make check` with a normal CGO build (for tests/linting) followed by a separate static rebuild for the deploy binary is sound. It adds ~2s to the Docker build but maintains the integrity of the check pipeline while producing a binary that actually works on Alpine. The PR description and inline comments clearly explain the rationale.
### Verdict: **PASS** ✅
Minimal, correct, well-documented fix that addresses both root causes of the container breakage.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #48
Problem
The Docker container failed to start with:
Two root causes:
Relative paths:
COPYdestination andCMDused relative paths (./webhooker), depending onWORKDIRcontext.Dynamic linking (the actual root cause): The binary was built with CGO enabled on Debian (glibc) via
make build, but deployed to an Alpine runtime (musl). The kernel couldn't find the glibc dynamic linker (/lib64/ld-linux-x86-64.so.2), producing the misleading "no such file or directory" error — even though the file existed on disk.Fix
Absolute paths throughout:
COPY --from=builder /build/bin/webhooker /app/webhookerandCMD ["/app/webhooker"]— no reliance on WORKDIR.Static rebuild for Alpine: Added a
RUN CGO_ENABLED=1 go build -ldflags '-extldflags "-static"' -o bin/webhooker ./cmd/webhookerstep aftermake check. This rebuilds the binary with static linking so it runs on Alpine without glibc. Themake checkstep still runs normally (formatting, linting, tests, dynamic build) — the static rebuild is only for the deployment binary.Verification
docker build .passes (all checks green)Code Review: PR #49 — fix: use absolute paths and static linking in Dockerfile
Requirements Checklist (Issue #48)
COPYdestination changed from.to/app/webhooker;CMDchanged from["./webhooker"]to["/app/webhooker"]. No WORKDIR dependency.CGO_ENABLED=1 go build -ldflags '-extldflags "-static"') aftermake check, so the deploy binary runs on Alpine without glibc.Policy Compliance Check
No violations found. Specifically verified:
@sha256:hash with version/date comments ✅.golangci.ymlunmodified ✅Makefileunmodified ✅make checkstill runs as build step (fmt-check, lint, test, build) ✅Build Result
docker build .passes — all tests green, all linting passesdlopenandgetaddrinfoin static builds are expected (standard glibc static linking caveats, non-fatal; sqlite amalgamated code doesn't call dlopen at runtime, and Go has a pure-Go DNS fallback)Container Verification
exec ./webhooker: no such file or directory):8080/.well-known/healthcheckresponds with 200Notes
The approach of running
make checkwith a normal CGO build (for tests/linting) followed by a separate static rebuild for the deploy binary is sound. It adds ~2s to the Docker build but maintains the integrity of the check pipeline while producing a binary that actually works on Alpine. The PR description and inline comments clearly explain the rationale.Verdict: PASS ✅
Minimal, correct, well-documented fix that addresses both root causes of the container breakage.