Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,29 @@
# this workspace once, and every byte of it is uploaded to the daemon before the first
# instruction runs. The build produces its own artefacts inside the image; the ones lying here
# are from another toolchain, another architecture, or both.
target/
node_modules/
# **Anchored at the context root unless they start with `**/`, and that is the whole point of this
# block.** Docker matches these against paths relative to the context, so a bare `node_modules/`
# excludes the one at the top and copies `apps/web/node_modules` — 683 MiB of it — straight into
# the image. Measured, not feared: the build context carried 683 files where the source tree has
# 457.
#
# `apps/web/dist` was the one that cost an afternoon. Copied in, Tailwind scanned the previous
# bundle for class names and emitted a different stylesheet, so the chunk hashes moved and the
# deployment stopped matching the manifest `.github/workflows/release.yml` publishes — the exact
# property `scripts/verify-web.sh` exists to check. A build whose output depends on whether
# somebody ran `pnpm run build` beforehand is not reproducible, and nothing said so.
**/target/
**/node_modules/
.git/
.worktrees/

# Secrets. `.env` is the development one and would be baked into a layer.
.env
.env.local
deploy/.env
**/.env
**/.env.local

# Build outputs that the image rebuilds itself.
dist/
pkg/
*.tsbuildinfo
**/dist/
**/pkg/
**/*.tsbuildinfo
apps/desktop/gen/
release/artefacts/
21 changes: 19 additions & 2 deletions deploy/Dockerfile.web
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ FROM node:22.23.2-bookworm-slim AS build
# The media server's origin, for calls. Empty by default, and it must stay that way for a
# deployment that runs none: an empty value keeps its host out of `connect-src` instead of
# widening the policy for a service that will never be contacted.
ARG VITE_MEDIA_URL=
ARG VITE_MEDIA_URL

# The version is pinned rather than left to corepack's default, for the reason
# `rust-toolchain.toml` gives at length about the compiler: "recent" is not a version.
Expand Down Expand Up @@ -75,7 +75,24 @@ COPY apps/web ./
# toolchain and no `wasm-pack` are needed here. That artefact is the one `scripts/verify-wasm.sh`
# checks against `crates/crypto-wasm` on every pull request; serving it is serving the bytes that
# check covers.
RUN pnpm run build
# **`unset` when empty, and this is not a formality.** Vite substitutes `import.meta.env.VITE_*`
# statically, and it distinguishes a variable that is *absent* from one that is *defined and
# empty*. Absent, the bundle keeps `const e={}, x=!!e?.VITE_MEDIA_URL`. Defined-and-empty, Vite
# folds it to a constant and minification deletes the branch — different bytes, different chunk
# hashes, different `index.html`.
#
# `ARG VITE_MEDIA_URL=` put every deployment in the second case, so a deployment that runs **no**
# media server still produced a bundle that did not match the manifest published by
# `.github/workflows/release.yml`. That is backwards: `docs/THREAT-MODEL.md` § 4quinquies says the
# trade is "verifiable **or** calls", and a deployment configuring no calls was paying it anyway.
#
# Measured rather than reasoned, on node 22.23.2 and pnpm 11.22.0, `dist/index.html`:
#
# variable absent, as CI builds it 2a26c3f24aff3326 ← what the manifest lists
# ARG VITE_MEDIA_URL= (before) 9b4c1178cc297802
# unset when empty (after) 2a26c3f24aff3326
# unset when empty, calls configured a13e1dcae6b3cbae ← differs, and should
RUN if [ -z "${VITE_MEDIA_URL:-}" ]; then unset VITE_MEDIA_URL; fi; pnpm run build

FROM caddy:2-alpine

Expand Down