-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDockerfile
More file actions
247 lines (221 loc) · 12.4 KB
/
Copy pathDockerfile
File metadata and controls
247 lines (221 loc) · 12.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# syntax=docker/dockerfile:1
# -----------------------------------------------------------------------------
# Builder stage: compile the static-linked musl server binary and frontend assets
# -----------------------------------------------------------------------------
# Trixie (Debian 13, glibc 2.41) — required because the prebuilt `dx` v0.7.9
# binary (aarch64/x86_64-unknown-linux-gnu) needs GLIBC_2.39; Bookworm only
# ships 2.36, so `dx --version` fails with "version `GLIBC_2.39' not found".
FROM rust:1.96-trixie AS builder
# Point every network download at a Chinese mirror so the build is fast/reliable
# from inside the container (the host proxy at 127.0.0.1:10808 is unreachable
# from Docker Desktop's NAT, and the official sources are slow or intercepted):
# - Debian apt -> TUNA (Tsinghua)
# - Rust + crates.io -> rsproxy (ByteDance)
# - Node.js + npm/pnpm -> npmmirror (Alibaba)
ARG DEBIAN_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian
ARG DEBIAN_SECURITY_MIRROR=https://mirrors.tuna.tsinghua.edu.cn/debian-security
ARG NODE_MIRROR=https://registry.npmmirror.com/-/binary/node
ARG NPM_REGISTRY=https://registry.npmmirror.com
ARG RS_PROXY=https://rsproxy.cn
# GitHub Releases proxy — the dx tarball and tailwindcss binary live on
# github.com releases and download at ~300 KB/s with frequent connection
# resets from China. Prefixing the raw github.com URL routes the download
# through the proxy. Set to "" (empty) to bypass the proxy (e.g. building
# outside China where github.com is fast/reliable).
ARG GH_PROXY=https://gh-proxy.com
# --- Debian apt: rewrite the DEB822 sources to the TUNA mirror. ---
RUN sed -i \
-e "s|http://deb.debian.org/debian|${DEBIAN_MIRROR}|g" \
-e "s|http://deb.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g" \
-e "s|http://security.debian.org/debian-security|${DEBIAN_SECURITY_MIRROR}|g" \
/etc/apt/sources.list.d/debian.sources
# Install system build tooling. Native dependencies are needed for:
# - musl-tools: linker for x86_64-unknown-linux-musl
# - cmake/clang/nasm/libssl-dev: libwebp (zenwebp), ring, syntect
# - binaryen: provides `wasm-opt` on PATH so dx's release client build
# uses the local binary instead of fetching one from GitHub Releases.
# dx's internal wasm-opt download ignores GH_PROXY (only the explicit
# curl calls for dx/tailwindcss honor it), so without this the build
# hangs ~80 min then fails with "stream error received: unspecific
# protocol error detected" trying to fetch wasm-opt mid-build.
# - curl/gnupg/ca-certificates: download tooling
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
cmake \
clang \
nasm \
pkg-config \
libssl-dev \
musl-tools \
binaryen \
ca-certificates \
curl \
gnupg \
git \
&& rm -rf /var/lib/apt/lists/*
# --- Node.js 22 + pnpm: install from the npmmirror binary mirror instead of
# the NodeSource apt repo (both TUNA and USTC have dropped their NodeSource
# mirrors; the upstream repo is slow/unreliable from inside the container). ---
ARG NODE_VERSION=22.20.0
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) NODE_ARCH=x64 ;; \
arm64) NODE_ARCH=arm64 ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSL "${NODE_MIRROR}/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.gz" \
| tar -xz -C /usr/local --strip-components=1 \
&& corepack enable \
&& corepack prepare pnpm@11.8.0 --activate
# Configure npm/pnpm to use the npmmirror registry for all subsequent installs.
RUN npm config set registry "${NPM_REGISTRY}" \
&& pnpm config set registry "${NPM_REGISTRY}"
# --- Rust: point rustup and cargo at the rsproxy mirror. ---
ENV RUSTUP_DIST_SERVER=${RS_PROXY}
ENV RUSTUP_UPDATE_ROOT=${RS_PROXY}/rustup
RUN mkdir -p /usr/local/cargo \
&& printf \
'[source.crates-io]\nreplace-with = "rsproxy-sparse"\n\n[source.rsproxy-sparse]\nregistry = "sparse+%s/index/"\n' \
"${RS_PROXY}" \
> /usr/local/cargo/config.toml
# Add the targets used by Dioxus fullstack builds. Both musl targets are
# installed; each buildx platform leg builds only its native one (see below).
RUN rustup target add wasm32-unknown-unknown \
x86_64-unknown-linux-musl aarch64-unknown-linux-musl
# Install the Dioxus CLI from the official prebuilt binary (GitHub Releases),
# NOT `cargo install` (which compiles dx-cli's huge dep tree from source — the
# slowest single Docker step). The release tag v0.7.9 matches the crate version
# we previously pinned. The prebuilt dx is a glibc (linux-gnu) binary requiring
# GLIBC_2.39 — that's why the builder stage above uses Trixie (glibc 2.41), not
# Bookworm (glibc 2.36). dx runs only in this builder stage (to emit the WASM
# client bundle); it never enters the static-musl runtime image. Each buildx
# platform leg downloads only its native arch; the sha256 pins the exact
# artifact (supply-chain integrity, verified against the release's .sha256
# sidecar).
ARG DX_VERSION=0.7.9
# The 32 MB dx tarball sits on github.com releases; from China it downloads at
# ~300 KB/s and the connection is frequently reset mid-transfer with
# "curl: (56) ... unexpected eof while reading" — the same flaky-upstream
# problem the mirror rewrites above solve for apt/crates/npm. --retry with
# --retry-all-errors (curl 7.71+, Trixie ships 8.x) covers SSL/EOF resets, and
# --continue-at - resumes the partial file instead of restarting from zero on
# each retry. The sha256 pin still catches a corrupted/partial download.
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) DX_TRIPLET=x86_64-unknown-linux-gnu DX_SHA256=3b132551b480bc96f938f9f0d37936ee1190f994977539dcc347eaf38540d005 ;; \
arm64) DX_TRIPLET=aarch64-unknown-linux-gnu DX_SHA256=8cf14db0b11b43b31dd6d39e71b00e567f2fccfde85ae3a8f7ef0f8745e5ccfb ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac \
&& DX_URL="${GH_PROXY:+${GH_PROXY}/}https://github.com/DioxusLabs/dioxus/releases/download/v${DX_VERSION}/dx-${DX_TRIPLET}.tar.gz" \
&& curl -fsSL --retry 5 --retry-delay 5 --retry-all-errors --retry-connrefused --continue-at - "${DX_URL}" -o /tmp/dx.tar.gz \
&& echo "${DX_SHA256} /tmp/dx.tar.gz" | sha256sum -c - \
&& tar -xzf /tmp/dx.tar.gz -C /usr/local/bin \
&& rm /tmp/dx.tar.gz \
&& dx --version
# --- Tailwind CSS v4: the standalone binary is distributed via GitHub
# Releases (~106 MB). ---
ARG TAILWIND_VERSION=4.3.1
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) TW_ARCH=x64 ;; \
arm64) TW_ARCH=arm64 ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac \
&& GH_URL="${GH_PROXY:+${GH_PROXY}/}https://github.com/tailwindlabs/tailwindcss/releases/download/v${TAILWIND_VERSION}/tailwindcss-linux-${TW_ARCH}" \
&& curl -fsSL -o /usr/local/bin/tailwindcss "${GH_URL}" \
&& chmod +x /usr/local/bin/tailwindcss
WORKDIR /build
# Cache the pnpm workspace node_modules by copying only package manifests first.
# Copying all 4 libs' manifests + the workspace root lets pnpm install everything
# in one shot; this layer is reused as long as the manifests don't change.
# `pnpm-workspace.yaml` declares a patched dep (@tiptap/markdown) pointing at
# `patches/@tiptap__markdown@3.27.3.patch`, so the patches/ tree must be present
# before `pnpm install --frozen-lockfile` or it fails with ENOENT on the patch.
COPY libs/package.json libs/pnpm-workspace.yaml libs/pnpm-lock.yaml libs/
COPY libs/patches/ libs/patches/
COPY libs/tiptap-editor/package.json libs/tiptap-editor/
COPY libs/codemirror-editor/package.json libs/codemirror-editor/
COPY libs/lightbox/package.json libs/lightbox/
COPY libs/yggdrasil-core/package.json libs/yggdrasil-core/
RUN cd libs && pnpm install --frozen-lockfile
# Build-time git info, injected by the caller via --build-arg. `.dockerignore`
# excludes `.git/`, so build.rs can't run `git` inside the container — these
# ARGs are the only channel for git metadata to reach build.rs (it reads them
# via std::env::var, its first-precedence source). Defaults are empty so a
# bare `docker build` without args degrades to "unknown" gracefully (build.rs
# falls back to the git command, then "unknown").
#
# Each ARG → ENV pair is needed: ARG is only visible in Dockerfile RUN
# commands (and build.rs is invoked by cargo, not a RUN), so we export it as
# ENV for the cargo build step to inherit. Both default to empty; Makefile's
# docker/docker-amd64/docker-multiarch targets override them on the host.
ARG YGG_BUILD_GIT_DESCRIBE=""
ARG YGG_BUILD_GIT_HASH=""
ARG YGG_BUILD_GIT_COMMIT_DATE=""
ENV YGG_BUILD_GIT_DESCRIBE=${YGG_BUILD_GIT_DESCRIBE}
ENV YGG_BUILD_GIT_HASH=${YGG_BUILD_GIT_HASH}
ENV YGG_BUILD_GIT_COMMIT_DATE=${YGG_BUILD_GIT_COMMIT_DATE}
# Copy the rest of the source tree and build everything.
COPY . .
# Build all 4 JS libs, syntax-highlight CSS, KaTeX CSS + fonts and Tailwind
# stylesheet. These steps produce the contents of the public/ directory.
# Must stay in sync with make build-linux — katex-css was previously missing,
# which left math rendering as bare spans without KaTeX fonts.
RUN make build-libs && make highlight-css && make katex-css && tailwindcss -i input.css -o public/style.css --minify
# Build the client-side Dioxus WASM bundle. We use dx only for the client assets;
# dx's linker wrapper is incompatible with a raw static linker, so the server
# binary is built with plain cargo in the next step. The client build emits a
# ready-to-serve public/ directory under target/dx/yggdrasil/*/web/public.
# restore-webp overwrites dx's re-encoded VP8L .webp stills with the source
# originals — keep in sync with make build-linux, which runs the same target.
RUN dx build @client --release --debug-symbols=false --wasm-js-cfg false && \
make restore-webp && \
mkdir -p /build/dist/public && \
cp -r /build/target/dx/yggdrasil/*/web/public/* /build/dist/public/
# Build the server as a fully static musl binary, **natively for the buildx
# platform leg**. Each leg builds only its own arch, so musl-gcc (which Debian
# ships for the host arch only) and the target always match — no cross-compiler,
# no QEMU. Cross-compiling here (e.g. building the x86_64 musl target from an
# arm64 leg) breaks ring: cc-rs emits -m64 for the x86_64 target and hands it to
# the arm64 musl-gcc, whose cc1 has no -m64 → "unrecognized command-line option".
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) MUSL_TARGET=x86_64-unknown-linux-musl ;; \
arm64) MUSL_TARGET=aarch64-unknown-linux-musl ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac \
&& export CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc \
&& export CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER=musl-gcc \
&& export RUSTFLAGS="-C target-feature=+crt-static -C relocation-model=static" \
&& cargo build --release --target "$MUSL_TARGET" --no-default-features --features server
# Ensure the uploads directory exists for runtime image caching.
RUN mkdir -p uploads
# Stage the built binary + assets at arch-independent paths so the scratch
# runtime stage can COPY them without knowing which musl target was built.
RUN ARCH="$(dpkg --print-architecture)" \
&& case "$ARCH" in \
amd64) MUSL_TARGET=x86_64-unknown-linux-musl ;; \
arm64) MUSL_TARGET=aarch64-unknown-linux-musl ;; \
*) echo "unsupported arch: $ARCH" >&2; exit 1 ;; \
esac \
&& cp "/build/target/${MUSL_TARGET}/release/yggdrasil" /build/server
# -----------------------------------------------------------------------------
# Runtime stage: minimal scratch image with the static musl binary
# -----------------------------------------------------------------------------
FROM scratch
WORKDIR /app
# Copy the static musl server binary and the bundled public assets.
COPY --from=builder --chown=65534:65534 /build/server /app/server
COPY --from=builder --chown=65534:65534 /build/dist/public /app/public
COPY --from=builder --chown=65534:65534 /build/uploads /app/uploads
# The app checks for DATABASE_URL on startup even though this image is intended
# to run without a real database. A placeholder is enough to let the server boot.
ENV DATABASE_URL=postgres://postgres:postgres@localhost:5432/yggdrasil
ENV DIOXUS_PUBLIC_PATH=/app/public
ENV IP=0.0.0.0
ENV PORT=3000
ENV RUST_LOG=info
USER 65534:65534
EXPOSE 3000
ENTRYPOINT ["/app/server"]