Repository navigation
Expand file tree
/
Copy pathDockerfile
More file actions
230 lines (197 loc) · 8.2 KB
/
Copy pathDockerfile
File metadata and controls
230 lines (197 loc) · 8.2 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
# Multi-stage build for Temps with embedded MaxMind GeoLite2 database
#
# Builds the Rust binary, WASM, and Web UI inside Linux/Alpine so the runtime
# artifact always matches the target architecture and musl libc.
#
# Usage: docker build -t temps:latest .
# Selects which stage the runtime copies the binary from: `artifacts-source`
# (default, compiled by the builder stage below) or `artifacts-prebuilt`
# (CI-injected binary from ci-prebuilt/). Declared before the first FROM so
# it can be referenced in a FROM line.
ARG TEMPS_ARTIFACTS=artifacts-source
# Stage 1: Toolchain — everything that depends only on the Dockerfile, not on
# the source tree. Kept as its own stage so CI can cache it as image layers
# (compiling wasm-pack/wasm-bindgen-cli from source dominates a cold build).
FROM rust:1.94-alpine AS toolchain
# Install required build dependencies
RUN apk add --no-cache \
bash \
build-base \
cmake \
perl \
musl-dev \
pkgconfig \
openssl-dev \
postgresql-dev \
protobuf-dev \
git \
curl \
tar \
gzip \
unzip
# Install Node.js and npm (needed for wasm-pack and bun)
RUN apk add --no-cache nodejs npm
# Install a pinned Bun archive and verify it before execution. Avoid piping a
# mutable remote installer into a shell inside the release build.
ARG BUN_VERSION=1.3.14
ARG BUN_LINUX_X64_MUSL_SHA256=14bd9aedeebf1dba67e8def9531c89bc989ecfdf1de42e5bfcaf1b8cd9294719
ARG BUN_LINUX_AARCH64_MUSL_SHA256=b98e0ad3625c5c00d1d5b5ff55605c7adddbfae151861e68ade57b2d3b8703bb
ARG TARGETARCH
RUN set -eux; \
case "${TARGETARCH:-amd64}" in \
amd64) bun_arch="x64"; bun_sha256="$BUN_LINUX_X64_MUSL_SHA256" ;; \
arm64) bun_arch="aarch64"; bun_sha256="$BUN_LINUX_AARCH64_MUSL_SHA256" ;; \
*) echo "Unsupported Bun architecture: ${TARGETARCH:-unknown}" >&2; exit 1 ;; \
esac; \
bun_zip="bun-linux-${bun_arch}-musl.zip"; \
curl -fsSLo "/tmp/${bun_zip}" "https://github.com/oven-sh/bun/releases/download/bun-v${BUN_VERSION}/${bun_zip}"; \
echo "${bun_sha256} /tmp/${bun_zip}" | sha256sum -c -; \
unzip -q "/tmp/${bun_zip}" -d /opt; \
ln -s "/opt/bun-linux-${bun_arch}-musl/bun" /usr/local/bin/bun; \
rm "/tmp/${bun_zip}"
# Install the Rust-native WASM tooling. The npm wrapper tries to download a
# prebuilt wasm-bindgen binary that does not exist for every Alpine architecture
# (notably arm64), so pin and compile the matching CLI instead.
RUN cargo install wasm-pack --version 0.15.0 --locked && \
cargo install wasm-bindgen-cli --version 0.2.121 --locked
# Install wasm32 target for Rust (needed for WASM compilation)
RUN rustup target add wasm32-unknown-unknown
# The musl target links system zlib statically.
RUN apk add --no-cache zlib-static
# Stage 2: Builder — source-dependent work on top of the cached toolchain.
FROM toolchain AS builder
# Create app directory
RUN mkdir -p /app
# Copy source code
WORKDIR /build
COPY . .
# Build WebAssembly for captcha (required for web UI)
RUN cd /build/crates/temps-captcha-wasm && \
bun install && \
npm run build && \
echo "WASM build completed successfully at pkg/"
# Build web UI (must happen before Rust build to embed in binary)
RUN cd /build/web && \
bun install && \
RSBUILD_OUTPUT_PATH=/build/crates/temps-cli/dist \
bun run build && \
echo "Web UI build completed at /build/crates/temps-cli/dist"
# Build natively in the Alpine builder. Copying a host-built binary here is
# unsafe: macOS produces Mach-O and ordinary Linux builds target glibc, while
# the runtime stage is musl-based Alpine.
#
# TEMPS_BUILD_PROFILE lets CI smoke-test builds (e.g. the Compose Security
# job) use the `fast` profile — same release semantics, parallel codegen, no
# LTO — while production images keep the default `release` profile.
ARG TEMPS_BUILD_PROFILE=release
RUN --mount=type=cache,target=/build/target \
cargo build --profile "$TEMPS_BUILD_PROFILE" --bin temps --package temps-cli && \
cp "/build/target/$TEMPS_BUILD_PROFILE/temps" /app/temps && \
chmod +x /app/temps && \
chown root:root /app/temps
# Verify binary exists
RUN test -f /app/temps || { \
echo "ERROR: Binary not found at /app/temps"; \
exit 1; \
}
# Artifact indirection: the runtime stage copies the binary and GeoLite2
# database from the `artifacts` stage. By default that resolves to the
# from-source builder above. CI passes TEMPS_ARTIFACTS=artifacts-prebuilt to
# inject a binary compiled outside Docker in the same musl toolchain image
# (with a persistent cargo cache), skipping the cold in-Docker workspace
# build. BuildKit only builds the stages the target actually references, so
# ci-prebuilt/ does not need to exist for default from-source builds.
FROM scratch AS artifacts-source
COPY --from=builder /app/temps /temps
COPY --from=builder /build/crates/temps-cli/GeoLite2-City.mmdb /GeoLite2-City.mmdb
FROM scratch AS artifacts-prebuilt
COPY ci-prebuilt/temps /temps
COPY crates/temps-cli/GeoLite2-City.mmdb /GeoLite2-City.mmdb
FROM ${TEMPS_ARTIFACTS} AS artifacts
# Stage 3: Runtime
FROM alpine:3.22
# Install runtime dependencies
RUN apk add --no-cache \
ca-certificates \
libssl3 \
postgresql-client
# Create app user
RUN addgroup -g 1001 -S appgroup && \
adduser -u 1001 -S appuser -G appgroup
# Create app directory
WORKDIR /app
# Copy binary from the selected artifacts stage
COPY --from=artifacts /temps /app/temps
# The city database is tracked in the repository and required by the proxy.
# Keep it outside /app/data so an existing persistent volume cannot mask it
# during an upgrade.
COPY --from=artifacts /GeoLite2-City.mmdb /usr/share/temps/GeoLite2-City.mmdb
# Create data directory structure
RUN mkdir -p /app/data/logs && \
chown -R appuser:appgroup /app
# Set permissions
RUN chown -R appuser:appgroup /app/data && \
chmod -R 755 /app/data && \
chmod 644 /usr/share/temps/GeoLite2-City.mmdb && \
ln -s /usr/share/temps/GeoLite2-City.mmdb /app/GeoLite2-City.mmdb
# Switch to non-root user
USER appuser:appgroup
# Expose API port
EXPOSE 3000
# Expose TLS port (if configured)
EXPOSE 3443
# Expose the console/API listener
EXPOSE 9000
# Health check
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget --no-verbose --tries=1 --spider http://127.0.0.1:9000/readyz || exit 1
# Run the application (Pingora handles signals internally)
CMD ["/app/temps", "serve"]
# Build instructions:
# ==================
# Docker builds the WASM package, web UI, and Rust binary inside its Alpine
# builder. No host toolchain or prebuilt binary is required.
#
# 1. Build the image:
# docker build -t temps:latest .
#
# 2. Run the container:
# docker run -d \
# --name temps \
# -p 3000:3000 \
# -p 127.0.0.1:9000:9000 \
# -e TEMPS_DATABASE_URL="postgresql://user:password@postgres:5432/temps" \
# -v temps_data:/app/data \
# temps:latest
#
# Environment variables:
# ======================
# - TEMPS_ADDRESS: API server address (default: 0.0.0.0:3000)
# - TEMPS_TLS_ADDRESS: TLS server address (optional)
# - TEMPS_DATABASE_URL: PostgreSQL connection string (required)
# - TEMPS_DATA_DIR: Data directory (default: /app/data)
# - TEMPS_CONSOLE_ADDRESS: Console API address (optional)
# - TEMPS_LOG_LEVEL: Log level (default: info)
#
# Volumes:
# ========
# - /app/data: Persistent data directory
# - Stores: logs, encryption keys, and optional runtime databases
#
# Notes:
# ======
# BUILD COMPONENTS:
# - WASM Build: temps-captcha-wasm crate compiled to WebAssembly using wasm-pack
# Location: Built inside Docker at crates/temps-captcha-wasm/pkg/
# - Web UI Build: Rsbuild frontend application built with bun
# Location: Built inside Docker at crates/temps-cli/dist/
# - Rust Binary: built natively in Alpine and embeds the generated web UI
#
# GEOLITE2 DATABASE:
# - The tracked crates/temps-cli/GeoLite2-City.mmdb is copied to the immutable
# /usr/share/temps directory. It remains available when /app/data is an existing
# persistent volume.
# - GeoLite2-ASN.mmdb is optional and may be mounted under /app/data. It powers
# hosting/VPS-provider detection used to keep scraper/bot traffic out of the
# live-visitors view. Without it, that detection is disabled (non-fatal), while
# city/country geolocation continues to use the bundled city database.