A minimal self-hosted git browser on Rust axum: bare repos rendered as a website with commits, diffs, syntax-highlighted blobs, atom feeds, and clone over HTTPS.
axumdockergitgit-browsergitoxidegixrustself-hosted
1# syntax=docker/dockerfile:1
2# ----- builder -----
3FROM rust:alpine AS builder
4
5RUN apk add --no-cache musl-dev
6
7COPY --from=oven/bun:alpine /usr/local/bin/bun /usr/local/bin/bun
8
9WORKDIR /app
10
11COPY Cargo.toml Cargo.lock ./
12COPY src ./src
13COPY frontend ./frontend
14
15RUN cd frontend && bun install --frozen-lockfile && bun run build
16RUN --mount=type=cache,target=/usr/local/cargo/registry \
17 --mount=type=cache,target=/app/target \
18 cargo build --release && \
19 cp target/release/repos /app/repos
20
21# ----- runtime -----
22FROM alpine:3.23
23
24# git-daemon is the apk that ships /usr/libexec/git-core/git-http-backend
25# (the plain `git` apk omits it; the package name is misleading, we don't
26# actually run the daemon). http-backend powers the smart-HTTP clone
27# endpoint; `git` itself is needed for the `git show` subprocess in
28# diff_commit. ca-certificates so any outbound TLS just works.
29RUN apk add --no-cache git git-daemon ca-certificates
30
31# Bare repos under /srv/git are root-owned on the host; the web process
32# runs as UID 1000. Without this, git's CVE-2022-24765 ownership check
33# refuses every operation with "dubious ownership". The read-only bind
34# mount in docker-compose.yml is the real safety control here.
35RUN git config --system --add safe.directory '*'
36
37WORKDIR /app
38
39COPY --from=builder /app/repos ./repos
40COPY --from=builder /app/dist ./dist
41COPY templates ./templates
42
43RUN addgroup -S -g 1000 app && \
44 adduser -S -h /app -s /sbin/nologin -u 1000 -G app app && \
45 chown -R app:app /app
46USER app
47
48ENV PORT=8000
49ENV REPOS_REPO_ROOT=/srv/git
50EXPOSE 8000
51
52CMD ["./repos"]