Build the image's static binary with netgo, osusergo and sqlite_omit_load_extension (closes #525)
check / check (push) Waiting to run
check / check (push) Waiting to run
The image's static relink linked glibc's getaddrinfo (from Go's cgo DNS resolver) and dlopen (from SQLite extension loading). A static binary still needs glibc's shared libraries at run time for both, and the Alpine runtime has none. The relink now passes the standard build tags netgo, osusergo and sqlite_omit_load_extension through a GO_TAGS variable, which the Makefile's build target hands to go build as -tags next to GO_LDFLAGS, so the version stamp is composed as before. Model: opus-5-5
This commit is contained in:
+15
-9
@@ -214,16 +214,22 @@ RUN version="$(make version VERSION="$VERSION")"; \
|
|||||||
# (Alpine.js, extracted from its tarball in 3p/) first.
|
# (Alpine.js, extracted from its tarball in 3p/) first.
|
||||||
RUN make build VERSION="$VERSION"
|
RUN make build VERSION="$VERSION"
|
||||||
|
|
||||||
# Rebuild with static linking for Alpine runtime.
|
# Rebuild with static linking for the Alpine runtime. The cgo binary the
|
||||||
# make build already verified compilation.
|
# step above builds is dynamically linked against glibc, which Alpine
|
||||||
# The CGO binary from `make build` is dynamically linked against glibc,
|
# (musl) does not have.
|
||||||
# which doesn't exist on Alpine (musl). Rebuild with static linking so
|
|
||||||
# the binary runs on Alpine without glibc.
|
|
||||||
#
|
#
|
||||||
# The static flags go in through GO_LDFLAGS rather than a -ldflags of
|
# A statically linked glibc still loads shared libraries at run time for
|
||||||
# their own: the build target composes them with the -X that stamps the
|
# getaddrinfo and dlopen, and Alpine has none to load, so the build tags
|
||||||
# version, so this relink cannot silently drop the stamp.
|
# keep both out of the binary: netgo and osusergo select Go's own DNS
|
||||||
RUN CGO_ENABLED=1 make build VERSION="$VERSION" GO_LDFLAGS='-extldflags "-static"'
|
# resolver and user lookup instead of glibc's, and
|
||||||
|
# sqlite_omit_load_extension leaves out SQLite extension loading, which
|
||||||
|
# webhooker never uses.
|
||||||
|
#
|
||||||
|
# The static flags and tags go in through GO_LDFLAGS and GO_TAGS rather
|
||||||
|
# than a -ldflags of their own: the build target composes them with the
|
||||||
|
# -X that stamps the version, so this relink cannot silently drop the
|
||||||
|
# stamp.
|
||||||
|
RUN CGO_ENABLED=1 make build VERSION="$VERSION" GO_TAGS=netgo,osusergo,sqlite_omit_load_extension GO_LDFLAGS='-extldflags "-static"'
|
||||||
|
|
||||||
# Runtime stage
|
# Runtime stage
|
||||||
# alpine:3.21, 2026-03-17
|
# alpine:3.21, 2026-03-17
|
||||||
|
|||||||
@@ -16,14 +16,16 @@ VERSION ?= $(shell script/version)
|
|||||||
# exists to correct.
|
# exists to correct.
|
||||||
override VERSION := $(or $(strip $(VERSION)),$(shell script/version))
|
override VERSION := $(or $(strip $(VERSION)),$(shell script/version))
|
||||||
|
|
||||||
# Extra linker flags for the build target. The static relink in the
|
# Extra linker flags and build tags for the build target. The static
|
||||||
# Dockerfile adds -extldflags here rather than passing its own -ldflags,
|
# relink in the Dockerfile passes -extldflags and its build tags through
|
||||||
# so composing flags cannot drop the version stamp.
|
# these rather than a -ldflags of its own, so composing flags cannot drop
|
||||||
|
# the version stamp.
|
||||||
#
|
#
|
||||||
# The build target itself always passes -trimpath and -s -w, as the Go
|
# The build target itself always passes -trimpath and -s -w, as the Go
|
||||||
# Dockerfile in REPO_POLICIES.md does: no build paths, symbol table or
|
# Dockerfile in REPO_POLICIES.md does: no build paths, symbol table or
|
||||||
# debug information in the binary.
|
# debug information in the binary.
|
||||||
GO_LDFLAGS ?=
|
GO_LDFLAGS ?=
|
||||||
|
GO_TAGS ?=
|
||||||
|
|
||||||
bootstrap:
|
bootstrap:
|
||||||
@script/bootstrap
|
@script/bootstrap
|
||||||
@@ -53,7 +55,7 @@ check:
|
|||||||
@script/check
|
@script/check
|
||||||
|
|
||||||
build: assets
|
build: assets
|
||||||
go build -trimpath -ldflags '$(strip -s -w -X main.version=$(VERSION) $(GO_LDFLAGS))' -o bin/webhooker ./cmd/webhooker
|
go build -trimpath -tags '$(GO_TAGS)' -ldflags '$(strip -s -w -X main.version=$(VERSION) $(GO_LDFLAGS))' -o bin/webhooker ./cmd/webhooker
|
||||||
|
|
||||||
run: build
|
run: build
|
||||||
./bin/webhooker
|
./bin/webhooker
|
||||||
|
|||||||
@@ -3419,12 +3419,14 @@ fixed independently of the compiler's:
|
|||||||
phases and the `css-check` and `markdown-check` stages passing (it copies a
|
phases and the `css-check` and `markdown-check` stages passing (it copies a
|
||||||
file from each), runs `make build` (which extracts Alpine.js from `3p/`
|
file from each), runs `make build` (which extracts Alpine.js from `3p/`
|
||||||
first), and then rebuilds the binary with `CGO_ENABLED=1` and static linking
|
first), and then rebuilds the binary with `CGO_ENABLED=1` and static linking
|
||||||
so it runs on musl. Both builds go through `make build`, the relink adding
|
so it runs on musl, with the build tags `netgo`, `osusergo` and
|
||||||
its `-extldflags` via `GO_LDFLAGS`, so neither can drop the `-X` that stamps
|
`sqlite_omit_load_extension` so it needs none of glibc's shared libraries at
|
||||||
the version. The version is the `VERSION` build arg if one is given,
|
run time. Both builds go through `make build`, the relink adding its
|
||||||
otherwise derived from the `.git` in the context, and the stage fails if a
|
`-extldflags` via `GO_LDFLAGS` and its tags via `GO_TAGS`, so neither can
|
||||||
context with `.git` would stamp an empty version, `dev` or `unknown` (see
|
drop the `-X` that stamps the version. The version is the `VERSION` build arg
|
||||||
[Version stamping](#version-stamping)).
|
if one is given, otherwise derived from the `.git` in the context, and the
|
||||||
|
stage fails if a context with `.git` would stamp an empty version, `dev` or
|
||||||
|
`unknown` (see [Version stamping](#version-stamping)).
|
||||||
7. **Runtime stage** (`alpine:3.21`) — copies the static binary and
|
7. **Runtime stage** (`alpine:3.21`) — copies the static binary and
|
||||||
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker` directory for
|
`deploy/docker-entrypoint.sh`, creates the `/var/lib/webhooker` directory for
|
||||||
all SQLite databases, exposes port 8080, and includes a health check against
|
all SQLite databases, exposes port 8080, and includes a health check against
|
||||||
|
|||||||
@@ -156,7 +156,8 @@ func TestVersion_IsStableAcrossInvocations(t *testing.T) {
|
|||||||
// The build target is the only place composing linker flags. If a
|
// The build target is the only place composing linker flags. If a
|
||||||
// future edit drops either half, the binary silently reports "dev"
|
// future edit drops either half, the binary silently reports "dev"
|
||||||
// again (the defect this package exists for) or fails to link on
|
// again (the defect this package exists for) or fails to link on
|
||||||
// Alpine.
|
// Alpine. If it stops passing GO_TAGS, the image's static binary links
|
||||||
|
// glibc's getaddrinfo and dlopen again.
|
||||||
func TestMakefile_BuildComposesVersionAndExtraFlags(t *testing.T) {
|
func TestMakefile_BuildComposesVersionAndExtraFlags(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
@@ -164,6 +165,7 @@ func TestMakefile_BuildComposesVersionAndExtraFlags(t *testing.T) {
|
|||||||
|
|
||||||
require.Contains(t, makefile, "-X main.version=$(VERSION)")
|
require.Contains(t, makefile, "-X main.version=$(VERSION)")
|
||||||
require.Contains(t, makefile, "$(GO_LDFLAGS)")
|
require.Contains(t, makefile, "$(GO_LDFLAGS)")
|
||||||
|
require.Contains(t, makefile, "-tags '$(GO_TAGS)'")
|
||||||
require.Contains(t, makefile, "VERSION ?= $(shell script/version)")
|
require.Contains(t, makefile, "VERSION ?= $(shell script/version)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,7 +235,9 @@ func TestDockerfile_BuildsThroughTheMakeTarget(t *testing.T) {
|
|||||||
"a raw go build bypasses the Makefile's -X flag")
|
"a raw go build bypasses the Makefile's -X flag")
|
||||||
require.Contains(t, dockerfile, "ARG VERSION")
|
require.Contains(t, dockerfile, "ARG VERSION")
|
||||||
require.Contains(t, dockerfile,
|
require.Contains(t, dockerfile,
|
||||||
`make build VERSION="$VERSION" GO_LDFLAGS='-extldflags "-static"'`)
|
`make build VERSION="$VERSION"`+
|
||||||
|
` GO_TAGS=netgo,osusergo,sqlite_omit_load_extension`+
|
||||||
|
` GO_LDFLAGS='-extldflags "-static"'`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func read(t *testing.T, path string) string {
|
func read(t *testing.T, path string) string {
|
||||||
|
|||||||
Reference in New Issue
Block a user