orchard
mirrorEvery 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
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 golang:1.27-alpine@sha256:4c9fe60190a2a3350ddc51de80d0224b8a6698d12bdfc999fee45ea9d6c46dbc AS build
5WORKDIR /src
6
7COPY go.mod go.sum ./
8RUN go mod download
9COPY web/ ./web/
10# skills/ is its own package, and `COPY *.go` only takes the top level, so a
11# local build succeeds and the image fails to compile without this line.
12COPY skills/ ./skills/
13COPY *.go ./
14COPY templates/ ./templates/
15COPY static/ ./static/
16# -tags embed swaps assets_disk.go for assets_embed.go, so the templates and the
17# stylesheet are compiled in and the binary is the whole site.
18RUN CGO_ENABLED=0 go build -tags embed -trimpath -ldflags="-s -w" -o /app .
19
20# scratch, since modernc.org/sqlite is SQLite transpiled to Go rather than bound
21# to it, so nothing here needs libc. Certificates are the only other thing this
22# process touches on disk, and it fetches over TLS constantly.
23FROM scratch
24
25COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
26COPY --from=build /app /app
27
28# Zone data, because the ambient context block states a local time and a bare
29# scratch image has no /usr/share/zoneinfo to resolve America/New_York against.
30COPY --from=build /usr/local/go/lib/time/zoneinfo.zip /zoneinfo.zip
31ENV ZONEINFO=/zoneinfo.zip
32
33ENV SITE_DATA=/data
34# The model, which is the other service in this project's compose file.
35ENV LLM_URL=http://llm:8091
36
37# 65532 like the other scratch images here, so one number owns every /data
38# volume. There is no adduser on scratch, so the numeric id is set directly and
39# the directory is created in the build stage below.
40COPY --from=build --chown=65532:65532 /tmp /data
41USER 65532:65532
42VOLUME ["/data"]
43
44HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
45 CMD ["/app", "-healthcheck"]
46
47EXPOSE 8000
48ENTRYPOINT ["/app"]