detached 470cf12

build(docker): add release image

Multi-stage build on golang:1.27-alpine, runtime alpine with git and
git-http-backend, non-root uid 10001. The version is stamped with
-X main.stamped from a VERSION build argument.
commit
470cf1226074a719baf63712bf1ca1adb6917a32
author
brice · · 2026-10-10 11:55 UTC
parent
2a9866e
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
4 files · +66 -6
A .dockerignore +12 -0
@@ -0,0 +1,12 @@
1+# Keep the build context to the sources.
2+.git
3+.worktrees
4+.forgejo
5+.air.toml
6+compose.yaml
7+Dockerfile
8+.dockerignore
9+/ermite
10+/tmp
11+/dist
12+tmp
@@ -0,0 +1,12 @@
1+# Keep the build context to the sources.
2+.git
3+.worktrees
4+.forgejo
5+.air.toml
6+compose.yaml
7+Dockerfile
8+.dockerignore
9+/ermite
10+/tmp
11+/dist
12+tmp
A Dockerfile +41 -0
@@ -0,0 +1,41 @@
1+# syntax=docker/dockerfile:1
2+
3+# Release image: a static ermite binary on alpine with git and git-http-backend (ermite shells out to git >= 2.45).
4+#
5+# docker build --build-arg VERSION="$(git describe --tags --always --dirty)" -t ermite .
6+#
7+# The modules bnema.dev/ermite-* are private and resolve through git.bnema.dev, which is
8+# reachable from the NetBird network only. The build container uses the host's DNS and
9+# routes, so on a NetBird host it needs nothing more. Behind a different route (CI), set
10+# --build-arg GIT_REWRITE_BASE=http://git.bnema.dev:8088/ [--add-host git.bnema.dev:<ip>]
11+# to fetch the modules over that URL instead of https://git.bnema.dev/.
12+
13+FROM golang:1.27-alpine AS build
14+RUN apk add --no-cache git
15+ENV CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local GOPRIVATE=bnema.dev/ermite*
16+ARG GIT_REWRITE_BASE=
17+RUN if [ -n "$GIT_REWRITE_BASE" ]; then \
18+ git config --global url."$GIT_REWRITE_BASE".insteadOf https://git.bnema.dev/; \
19+ fi
20+WORKDIR /src
21+COPY . .
22+ARG VERSION=
23+RUN --mount=type=cache,target=/go/pkg/mod \
24+ --mount=type=cache,target=/root/.cache/go-build \
25+ go build -trimpath -buildvcs=false -ldflags "-s -w -X main.stamped=${VERSION}" \
26+ -o /out/ermite ./cmd/ermite
27+
28+FROM alpine:3
29+# git-daemon provides git-http-backend on alpine.
30+RUN apk add --no-cache git git-daemon ca-certificates \
31+ && adduser -D -u 10001 -h /home/ermite ermite \
32+ && mkdir /data \
33+ && chown ermite:ermite /data
34+COPY --from=build /out/ermite /usr/local/bin/ermite
35+ENV ERMITE_DATA_DIR=/data ERMITE_LOG_FORMAT=json
36+USER ermite
37+WORKDIR /data
38+VOLUME /data
39+EXPOSE 7341
40+ENTRYPOINT ["/usr/local/bin/ermite"]
41+CMD ["serve"]
@@ -0,0 +1,41 @@
1+# syntax=docker/dockerfile:1
2+
3+# Release image: a static ermite binary on alpine with git and git-http-backend (ermite shells out to git >= 2.45).
4+#
5+# docker build --build-arg VERSION="$(git describe --tags --always --dirty)" -t ermite .
6+#
7+# The modules bnema.dev/ermite-* are private and resolve through git.bnema.dev, which is
8+# reachable from the NetBird network only. The build container uses the host's DNS and
9+# routes, so on a NetBird host it needs nothing more. Behind a different route (CI), set
10+# --build-arg GIT_REWRITE_BASE=http://git.bnema.dev:8088/ [--add-host git.bnema.dev:<ip>]
11+# to fetch the modules over that URL instead of https://git.bnema.dev/.
12+
13+FROM golang:1.27-alpine AS build
14+RUN apk add --no-cache git
15+ENV CGO_ENABLED=0 GOWORK=off GOTOOLCHAIN=local GOPRIVATE=bnema.dev/ermite*
16+ARG GIT_REWRITE_BASE=
17+RUN if [ -n "$GIT_REWRITE_BASE" ]; then \
18+ git config --global url."$GIT_REWRITE_BASE".insteadOf https://git.bnema.dev/; \
19+ fi
20+WORKDIR /src
21+COPY . .
22+ARG VERSION=
23+RUN --mount=type=cache,target=/go/pkg/mod \
24+ --mount=type=cache,target=/root/.cache/go-build \
25+ go build -trimpath -buildvcs=false -ldflags "-s -w -X main.stamped=${VERSION}" \
26+ -o /out/ermite ./cmd/ermite
27+
28+FROM alpine:3
29+# git-daemon provides git-http-backend on alpine.
30+RUN apk add --no-cache git git-daemon ca-certificates \
31+ && adduser -D -u 10001 -h /home/ermite ermite \
32+ && mkdir /data \
33+ && chown ermite:ermite /data
34+COPY --from=build /out/ermite /usr/local/bin/ermite
35+ENV ERMITE_DATA_DIR=/data ERMITE_LOG_FORMAT=json
36+USER ermite
37+WORKDIR /data
38+VOLUME /data
39+EXPOSE 7341
40+ENTRYPOINT ["/usr/local/bin/ermite"]
41+CMD ["serve"]
M cmd/ermite/forge.go +3 -5
@@ -9,7 +9,6 @@ import (
99 "net"
1010 "os"
1111 "path/filepath"
12- "runtime/debug"
1312 "time"
1413
1514 "github.com/bnema/zerowrap"
@@ -69,11 +68,10 @@ type forge struct {
6968 // serverVersion is the version GetVersion reports: the module version of the
7069 // build, or "devel" for an untagged one.
7170 func serverVersion() string {
72- info, ok := debug.ReadBuildInfo()
73- if !ok || info.Main.Version == "" || info.Main.Version == "(devel)" {
74- return "devel"
71+ if v := buildVersion(); v != "(devel)" {
72+ return v
7573 }
76- return info.Main.Version
74+ return "devel"
7775 }
7876
7977 // newForge opens the metadata store, resets run/, checks git, reconciles the repositories,
@@ -9,7 +9,6 @@ import (
9 "net"9 "net"
10 "os"10 "os"
11 "path/filepath"11 "path/filepath"
12- "runtime/debug"
13 "time"12 "time"
14 13
15 "github.com/bnema/zerowrap"14 "github.com/bnema/zerowrap"
@@ -69,11 +68,10 @@ type forge struct {
69 // serverVersion is the version GetVersion reports: the module version of the68 // serverVersion is the version GetVersion reports: the module version of the
70 // build, or "devel" for an untagged one.69 // build, or "devel" for an untagged one.
71 func serverVersion() string {70 func serverVersion() string {
72- info, ok := debug.ReadBuildInfo()71+ if v := buildVersion(); v != "(devel)" {
73- if !ok || info.Main.Version == "" || info.Main.Version == "(devel)" {72+ return v
74- return "devel"
75 }73 }
76- return info.Main.Version74+ return "devel"
77 }75 }
78 76
79 // newForge opens the metadata store, resets run/, checks git, reconciles the repositories,77 // newForge opens the metadata store, resets run/, checks git, reconciles the repositories,
M cmd/ermite/main.go +10 -1
@@ -16,8 +16,17 @@ func main() {
1616 }
1717 }
1818
19-// buildVersion is the module version stamped into the binary.
19+// stamped is the release version set at link time by the image build
20+// (-ldflags "-X main.stamped=v0.1.0"). Empty for a plain go build, which then
21+// reports the module version recorded by the Go toolchain.
22+var stamped string
23+
24+// buildVersion is the version stamped into the binary: the link-time version
25+// when set, else the module version of the build.
2026 func buildVersion() string {
27+ if stamped != "" {
28+ return stamped
29+ }
2130 info, ok := debug.ReadBuildInfo()
2231 if !ok || info.Main.Version == "" {
2332 return "(devel)"
@@ -16,8 +16,17 @@ func main() {
16 }16 }
17 }17 }
18 18
19-// buildVersion is the module version stamped into the binary.19+// stamped is the release version set at link time by the image build
20+// (-ldflags "-X main.stamped=v0.1.0"). Empty for a plain go build, which then
21+// reports the module version recorded by the Go toolchain.
22+var stamped string
23+
24+// buildVersion is the version stamped into the binary: the link-time version
25+// when set, else the module version of the build.
20 func buildVersion() string {26 func buildVersion() string {
27+ if stamped != "" {
28+ return stamped
29+ }
21 info, ok := debug.ReadBuildInfo()30 info, ok := debug.ReadBuildInfo()
22 if !ok || info.Main.Version == "" {31 if !ok || info.Main.Version == "" {
23 return "(devel)"32 return "(devel)"

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 2

Tags 42

↑↓ moveenter openesc close