fix: use absolute paths and static linking in Dockerfile #49

Merged
sneak merged 1 commits from fix/absolute-path-in-dockerfile into main 2026-03-17 12:48:14 +01:00
Collaborator

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.
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.
clawbot added 1 commit 2026-03-17 12:43:10 +01:00
fix: use absolute paths and static linking in Dockerfile
All checks were successful
check / check (push) Successful in 1m3s
60d707b314
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.
clawbot added the needs-review label 2026-03-17 12:43:51 +01:00
sneak merged commit 17e740a45f into main 2026-03-17 12:48:14 +01:00
sneak deleted branch fix/absolute-path-in-dockerfile 2026-03-17 12:48:14 +01:00
clawbot reviewed 2026-03-17 12:49:44 +01:00
clawbot left a comment
Author
Collaborator

Code Review: PR #49 — fix: use absolute paths and static linking in Dockerfile

Requirements Checklist (Issue #48)

  • Fix relative pathsCOPY 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.
Sign in to join this conversation.
No Reviewers
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/webhooker#49