repos
/ orchard main

orchard

mirror

Every site I host, in one repo, along with the Cloudflare Tunnel and Caddy that front them. It's all Go, Vite, and SQLite, and it runs on a desktop at home with nothing listening on an inbound port.

blogbuncaddycloudflare-tunneldockergogolanghomelabhtml-templatemonorepoself-hostedseosqlitestatic-sitetypstuptime-monitoringviteweb-analytics

2.2 KB · 58 lines · Docker Raw History
 1# Build context is this directory alone: the site is its own Go module with its
 2# own copy of web/. Base images are pinned by digest because :1-alpine floats.
 3
 4FROM oven/bun:1-alpine@sha256:07235578f79ef8c6f97d94aee7938e76f5cdba5f21ae5dbfdd3d3d38058437eb AS frontend
 5WORKDIR /src
 6# bunfig.toml has to arrive before install runs, or install.peer = false
 7# silently does nothing.
 8COPY frontend/package.json frontend/bun.lock frontend/bunfig.toml ./
 9RUN bun install --frozen-lockfile
10COPY frontend/ ./
11RUN bun run build
12
13FROM golang:1.27-alpine@sha256:4c9fe60190a2a3350ddc51de80d0224b8a6698d12bdfc999fee45ea9d6c46dbc AS build
14WORKDIR /src
15
16COPY go.mod go.sum ./
17RUN go mod download
18COPY web/ ./web/
19COPY *.go ./
20COPY templates/ ./templates/
21# -tags embed reads build/ at compile time, so the bundle has to land here
22# before the compile below.
23COPY --from=frontend /build/dist ./build/dist
24# CGO off, since modernc.org/sqlite is SQLite transpiled to Go rather than bound
25# to it, so the binary is static and needs no libc. It is also what makes FROM
26# scratch possible.
27RUN CGO_ENABLED=0 go build -tags embed -trimpath -ldflags="-s -w" \
28    -o /app .
29
30# The data directory is created here rather than at runtime, because a scratch
31# image has no shell to mkdir with and Docker seeds a fresh volume from the
32# image path it mounts over, carrying its ownership. Without this the mountpoint
33# is root-owned and the first boot dies opening the database.
34RUN mkdir -p /data && chown 65532:65532 /data
35
36FROM scratch
37
38# Templates and the Vite bundle are inside the executable, and nothing here
39# reaches the internet: ntfy is plain HTTP on the bridge, so there is no TLS and
40# no need for ca-certificates.
41COPY --from=build /app /app
42COPY --from=build --chown=65532:65532 /data /data
43
44ENV SITE_DATA=/data
45VOLUME ["/data"]
46
47# There is no /etc/passwd in a scratch image to name a user in, so this is the
48# bare numeric UID:GID, the same one that owns every other /data volume here.
49USER 65532:65532
50
51# No shell in a scratch image for HEALTHCHECK to call, so the binary probes
52# itself. Compose sets the same check.
53HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
54    CMD ["/app", "-healthcheck"]
55
56EXPOSE 8000
57ENTRYPOINT ["/app"]