@jpgilldev / steid

steid/Dockerfile
4.0 KBRaw
1# syntax=docker/dockerfile:1
2
3# Steid as a container.
4#
5# Two stages: a Rust builder that compiles and bundles assets, and a slim Debian
6# runtime that carries the binary, the asset bundle, and `git`.
7#
8# Debian, not Alpine, on both sides. `build.rs` runs the *standalone* Tailwind CLI,
9# downloaded from GitHub at build time, and those Linux builds are glibc-linked —
10# a musl builder fails at `cargo build`, not at runtime, which is at least loud.
11# The runtime stays on the same libc so the binary needs no static-linking dance.
12
13
14# --- builder ---------------------------------------------------------------
15
16# rustc >= 1.95 is a hard floor: Topcoat 0.5 requires it, and on an older toolchain
17# `topcoat` resolves to an empty `v0.0.0` placeholder rather than failing. Pinned
18# rather than `rust:bookworm` so an image rebuilt in six months is the same build.
19FROM rust:1.97-bookworm AS builder
20
21# `topcoat asset bundle` needs the CLI. Installed before the source is copied so
22# editing the app doesn't rebuild it. `--locked` keeps it reproducible.
23RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
24 cargo install topcoat-cli --version 0.5.0 --locked
25
26WORKDIR /src
27COPY . .
28
29# One command does both jobs: `topcoat asset bundle` runs `cargo build` itself and
30# then scans the linked binary for the assets it declares, writing them plus a
31# `manifest.toml` to `target/assets`. A plain `cargo build --release` is *not*
32# enough — it produces the binary, but `AssetBundle::load()` looks for
33# `assets/manifest.toml` beside the executable at startup and the app fails to boot
34# without it.
35#
36# `target/` and the cargo registry are cache mounts, so a rebuild after a code edit
37# reuses compiled dependencies. Nothing is faked to get that: no dummy `main.rs`,
38# no split manifest copy. Those tricks interact badly with `build.rs`, which scans
39# the real sources for Tailwind classes, and a stale stylesheet is a silent wrong
40# answer rather than a build failure. Because a cache mount is not part of the
41# image, the artefacts are copied to `/out` inside the same `RUN`.
42RUN --mount=type=cache,target=/usr/local/cargo/registry,sharing=locked \
43 --mount=type=cache,target=/src/target,sharing=locked \
44 topcoat asset bundle --release \
45 && mkdir -p /out \
46 && cp target/release/steid /out/steid \
47 && cp -r target/assets /out/assets
48
49
50# --- runtime ---------------------------------------------------------------
51
52FROM debian:bookworm-slim
53
54# `git` is not optional. Steid shells out to it for everything: `git init --bare`
55# creates a repository and `git http-backend` (shipped inside the git package, at
56# /usr/lib/git-core) serves clone and push. An image without it builds cleanly and
57# then fails at the first repository the user creates.
58# `ca-certificates` for outbound TLS.
59RUN apt-get update \
60 && apt-get install -y --no-install-recommends git ca-certificates \
61 && rm -rf /var/lib/apt/lists/*
62
63# Non-root. A fixed uid so a bind-mounted host directory can be chowned to match.
64RUN useradd --system --create-home --home-dir /home/steid --uid 10001 steid
65
66# The bundle must sit beside the binary: `AssetBundle::load()` walks up from the
67# executable looking for `assets/manifest.toml`, so /app/steid finds /app/assets.
68WORKDIR /app
69COPY --from=builder --chown=root:root /out/steid /app/steid
70COPY --from=builder --chown=root:root /out/assets /app/assets
71
72# All persistent state under one directory, so one volume covers it: the SQLite
73# database as a file in /data, the bare repositories under /data/repos. SQLite
74# writes `-wal` and `-shm` siblings, so /data itself must be writable, not just the
75# database file.
76RUN mkdir -p /data/repos && chown -R steid:steid /data
77VOLUME ["/data"]
78
79ENV STEID_DATABASE_URL="sqlite:/data/steid.db?mode=rwc" \
80 STEID_DATA_DIR="/data/repos" \
81 HOST="0.0.0.0" \
82 PORT="3000" \
83 HOME="/home/steid"
84
85# Deliberately not set: STEID_INSECURE_COOKIES. It strips `Secure` from the session
86# cookie and belongs to plain-HTTP local development only.
87
88EXPOSE 3000
89USER steid
90CMD ["/app/steid"]