-
-
Notifications
You must be signed in to change notification settings - Fork 184
Expand file tree
/
Copy pathDockerfile.standalone
More file actions
298 lines (258 loc) · 13 KB
/
Dockerfile.standalone
File metadata and controls
298 lines (258 loc) · 13 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
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
# ---- Stage 0: QEMU .so + ROM binaries ----
# The Velxio runtime needs libqemu-xtensa.so + libqemu-riscv32.so (the
# QEMU shared libraries that simulate ESP32 / ESP32-S3 / ESP32-C3) plus
# the matching boot ROM blobs. Three sources, tried in order:
#
# 1. Local prebuilt files at prebuilt/qemu/<file>.
# Drop your own compiled .so files in there (see docs/BUILD-QEMU.md
# for the full build-from-source guide) and they win — no network
# access required.
#
# 2. velxio.dev gated download endpoint, when VELXIO_LICENSE_KEY is set.
# Free personal-tier keys at https://velxio.dev/license/signup.
#
# 3. Build fails with a clear error telling you which of the two paths
# to pick.
#
# Backward compatibility: the old QEMU_RELEASE_URL ARG is still accepted
# so forks pointing at a private mirror of the binaries keep working.
FROM ubuntu:22.04 AS qemu-provider
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates \
&& rm -rf /var/lib/apt/lists/*
ARG TARGETARCH
ARG VELXIO_LICENSE_KEY=
ARG VELXIO_BINARY_BASE_URL=https://velxio.dev/api/pro/license/downloads
# Legacy escape hatch — set this to keep using the old GitHub Release
# mirror (or any other CDN you proxy from). When set, takes precedence
# over the gated path.
ARG QEMU_RELEASE_URL=
# Copy the prebuilt directory (may contain .so+ROM files or just the .gitkeep)
RUN mkdir -p /qemu
COPY prebuilt/qemu/ /qemu/
# Resolve which fetch path the build will use and fail-fast with a
# friendly message if neither prebuilt files nor a key were provided.
RUN cd /qemu \
&& have_libs=1 && for base in libqemu-xtensa libqemu-riscv32; do \
[ -f "${base}.so" ] || have_libs=0 ; \
done \
&& if [ "$have_libs" = "1" ]; then \
echo "[qemu-provider] using local prebuilt/qemu/ files — no download" ; \
elif [ -n "${QEMU_RELEASE_URL}" ]; then \
echo "[qemu-provider] using legacy QEMU_RELEASE_URL: ${QEMU_RELEASE_URL}" ; \
elif [ -n "${VELXIO_LICENSE_KEY}" ]; then \
echo "[qemu-provider] using velxio.dev gated download with provided license key" ; \
else \
echo "" ; \
echo "ERROR: Velxio docker build needs the QEMU runtime libraries." ; \
echo "" ; \
echo "Pick one:" ; \
echo " a) Free personal key from https://velxio.dev/license/signup ," ; \
echo " then re-build with --build-arg VELXIO_LICENSE_KEY=vlx_personal_..." ; \
echo " b) Build QEMU yourself (see docs/BUILD-QEMU.md) and drop the .so" ; \
echo " files plus the three esp32*-rom.bin files into prebuilt/qemu/." ; \
echo "" ; \
exit 1 ; \
fi
# Download arch-specific .so and arch-independent ROM files.
# The gated endpoint serves the same byte-for-byte content as the legacy
# GitHub Release path; asset paths just drop the .so extension since the
# license module's manifest carries the real filename.
RUN cd /qemu \
&& for base in libqemu-xtensa libqemu-riscv32; do \
f="${base}.so" ; \
if [ -f "$f" ]; then \
echo "Using local $f ($(stat -c%s "$f") bytes)" ; \
elif [ -n "${QEMU_RELEASE_URL}" ]; then \
echo "Downloading ${base}-${TARGETARCH}.so → $f (legacy URL) ..." ; \
curl -fSL -o "$f" "${QEMU_RELEASE_URL}/${base}-${TARGETARCH}.so" ; \
else \
echo "Downloading ${base}-${TARGETARCH} → $f (velxio.dev) ..." ; \
curl -fSL -o "$f" "${VELXIO_BINARY_BASE_URL}/${base}-${TARGETARCH}?key=${VELXIO_LICENSE_KEY}" ; \
fi ; \
done \
&& for f in esp32-v3-rom.bin esp32-v3-rom-app.bin esp32c3-rom.bin; do \
asset="${f%.bin}" ; \
if [ -f "$f" ]; then \
echo "Using local $f ($(stat -c%s "$f") bytes)" ; \
elif [ -n "${QEMU_RELEASE_URL}" ]; then \
echo "Downloading $f (legacy URL) ..." ; \
curl -fSL -o "$f" "${QEMU_RELEASE_URL}/$f" ; \
else \
echo "Downloading $asset → $f (velxio.dev) ..." ; \
curl -fSL -o "$f" "${VELXIO_BINARY_BASE_URL}/${asset}?key=${VELXIO_LICENSE_KEY}" ; \
fi ; \
done \
&& ls -lh /qemu/
# ---- Stage 0.5: ESP-IDF toolchain for ESP32 compilation ----
FROM ubuntu:22.04 AS espidf-builder
RUN apt-get update && apt-get install -y --no-install-recommends \
git wget flex bison gperf python3 python3-pip python3-venv \
cmake ninja-build ccache libffi-dev libssl-dev \
libusb-1.0-0 ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# Install ESP-IDF 4.4.7 (matches Arduino ESP32 core 2.0.17 / lcgamboa QEMU ROM)
RUN git clone -b v4.4.7 --recursive --depth=1 --shallow-submodules \
https://github.com/espressif/esp-idf.git /opt/esp-idf
WORKDIR /opt/esp-idf
# Install toolchains for esp32 (Xtensa) and esp32c3 (RISC-V) only
RUN ./install.sh esp32,esp32c3
# Clean up large unnecessary files to reduce image size
RUN rm -rf .git docs examples \
&& find /root/.espressif -name '*.tar.*' -delete 2>/dev/null || true
# Install Arduino-as-component for full Arduino API support in ESP-IDF builds
RUN git clone --branch 2.0.17 --depth=1 --recursive --shallow-submodules \
https://github.com/espressif/arduino-esp32.git /opt/arduino-esp32 \
&& rm -rf /opt/arduino-esp32/.git
# ---- Stage 1: Build frontend and third-party ----
FROM node:20 AS frontend-builder
WORKDIR /app
# avr8js, rp2040js and @wokwi/elements are pulled directly from the npm
# registry (see frontend/package.json) — no upstream git clones needed.
# Board SVGs live in frontend/public/boards/, component SVGs in
# frontend/public/component-svgs/, and components-metadata.json is committed.
COPY frontend/ frontend/
COPY scripts/ scripts/
WORKDIR /app/frontend
# Lock files aren't committed in this repo (they're gitignored) — see the
# note in .gitignore. The `rm -f` below is defense-in-depth in case
# someone runs `docker build .` from a tree where a local lock exists.
RUN rm -f package-lock.json \
&& npm install --include=optional \
&& npm run build:docker
# ---- Stage 2: Final Production Image ----
FROM python:3.12-slim
# Install system dependencies, nginx, and QEMU runtime.
#
# qemu-system-arm + qemu-utils provide qemu-system-aarch64 and qemu-img,
# both invoked by app/services/qemu_manager.py for Raspberry Pi 3
# simulation. They were missing for the entire 2024-2026 stretch when
# Pi 3 simulation was advertised but broken — see docs/BOOT_IMAGES.md.
# ~200 MB added to the image; trade-off is "Pi 3 actually works".
#
# Other libs (libglib2.0-0, libgcrypt20, libslirp0, libpixman-1-0,
# libfdt1) used to be needed only as runtime deps for libqemu-xtensa.so;
# they're still needed but now also pulled in transitively by
# qemu-system-arm.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
nginx \
libglib2.0-0 \
libgcrypt20 \
libslirp0 \
libpixman-1-0 \
libfdt1 \
cmake \
ninja-build \
libusb-1.0-0 \
git \
ccache \
qemu-system-arm \
qemu-utils \
sdcc \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* \
&& pip install --no-cache-dir packaging
# Install arduino-cli into /usr/local/bin directly (avoids touching /bin)
RUN curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh \
| BINDIR=/usr/local/bin sh
# Only install arduino-cli binary here. Core installation (arduino:avr,
# rp2040:rp2040) is done at first boot by entrypoint.sh and persisted
# in the mounted /root/.arduino15 volume.
# ESP32 compilation uses ESP-IDF instead of arduino-cli.
WORKDIR /app
# Data directory for persistent SQLite database (mounted as a volume at runtime)
RUN mkdir -p /app/data
# Install Python backend dependencies
COPY backend/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy backend application code
COPY backend/app/ ./app/
# One-off maintenance scripts (e.g. backfill_boards_2026_05). Pure stdlib —
# run with: docker exec velxio-app python /app/scripts/<script> --apply
COPY backend/scripts/ ./scripts/
# Setup Nginx configuration. Remove Debian's stock site so it doesn't shadow
# ours as the default_server (was Issue #108: users behind reverse proxies got
# the "Welcome to nginx" page because the stock site claimed default_server).
RUN rm -f /etc/nginx/sites-enabled/default
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
# Copy built frontend assets from builder stage
COPY --from=frontend-builder /app/frontend/dist /usr/share/nginx/html
# Copy and configure entrypoint script (fix Windows CRLF → LF)
COPY docker/entrypoint.sh /app/entrypoint.sh
RUN sed -i 's/\r$//' /app/entrypoint.sh && chmod +x /app/entrypoint.sh
# ── ESP32 emulation: pre-built QEMU .so + ROM binaries ──────────────────────
# Downloaded from GitHub Release (public — no access to qemu-lcgamboa needed)
# libqemu-xtensa.so → ESP32 / ESP32-S3 (Xtensa LX6/LX7)
# libqemu-riscv32.so → ESP32-C3 (RISC-V RV32IMC)
# esp32-v3-rom*.bin → boot/app ROM images required by esp32-picsimlab machine
# esp32c3-rom.bin → ROM image required by esp32c3-picsimlab machine
# NOTE: ROM files must live in the same directory as the .so (worker passes -L
# to QEMU pointing at os.path.dirname(lib_path))
RUN mkdir -p /app/lib
COPY --from=qemu-provider /qemu/ /app/lib/
# Activate ESP32 emulation
# QEMU_ESP32_LIB → Xtensa library (ESP32, ESP32-S3)
# QEMU_RISCV32_LIB → RISC-V library (ESP32-C3 and variants)
ENV QEMU_ESP32_LIB=/app/lib/libqemu-xtensa.so
ENV QEMU_RISCV32_LIB=/app/lib/libqemu-riscv32.so
# ── ESP-IDF toolchain for ESP32 compilation ──────────────────────────────────
# Copied from espidf-builder stage: IDF framework + cross-compiler toolchains
COPY --from=espidf-builder /opt/esp-idf /opt/esp-idf
COPY --from=espidf-builder /root/.espressif /root/.espressif
COPY --from=espidf-builder /opt/arduino-esp32 /opt/arduino-esp32
ENV IDF_PATH=/opt/esp-idf
ENV IDF_TOOLS_PATH=/root/.espressif
ENV ARDUINO_ESP32_PATH=/opt/arduino-esp32
# ── ccache for ESP-IDF compiles ──────────────────────────────────────────────
# ESP-IDF's build system honours IDF_CCACHE_ENABLE=1 and routes every C/C++
# compile through ccache. Cold first compile per container is unchanged
# (cache is empty), but the second and subsequent compiles drop from
# ~5-7 minutes to ~30-60 seconds because every ESP-IDF base object
# (FreeRTOS, lwIP, esp_wifi, libsodium, …) hits the cache.
#
# Cache lives at /var/cache/ccache. Mount as a docker volume in
# docker-compose.yml so the cache survives `docker compose up -d --build`.
# Without the volume, the cache rebuilds itself on first compile after each
# image rebuild — still better than no cache.
ENV CCACHE_DIR=/var/cache/ccache
ENV IDF_CCACHE_ENABLE=1
# CCACHE_BASEDIR makes ccache treat absolute paths under this prefix as
# relative when computing the cache key. Combined with the persistent
# /var/lib/velxio-build/<target>/ build dir, this lets ccache hit across
# compiles even though some flags (-I, -fmacro-prefix-map) embed absolute
# paths into the command line.
ENV CCACHE_BASEDIR=/var/lib/velxio-build
# Cache cap + compression set as env vars (rather than via `ccache
# --set-config` at image-build time) because $CCACHE_DIR is a docker volume:
# anything written into /var/cache/ccache during the RUN step is masked at
# runtime by the volume mount. ccache reads CCACHE_MAXSIZE / CCACHE_COMPRESS
# / CCACHE_COMPRESSLEVEL on every invocation and they override any conf-file
# value, so the cap actually applies at runtime.
ENV CCACHE_MAXSIZE=8G
ENV CCACHE_COMPRESS=1
ENV CCACHE_COMPRESSLEVEL=6
RUN mkdir -p /var/cache/ccache /var/lib/velxio-build /root/Arduino
# ── Persistent paths ────────────────────────────────────────────────────────
# Declaring these as VOLUMEs means `docker run` (without explicit -v) creates
# anonymous volumes for them — they survive `docker stop`/`docker start` and
# even `docker rm`. Without this, every container restart wipes the ccache
# and persistent ESP-IDF build dir, so every compile is cold (~5-7 min on
# modest hardware) instead of the warm-cache 5-30s we measured on prod.
#
# Users SHOULD pass `-v velxio-X:/path` for each of these to get named
# volumes (easier to inspect / back up than anonymous ones), but the
# anonymous default is a sensible fallback.
#
# /app/data — SQLite DB + project files + auto-generated SECRET_KEY
# /root/.arduino15 — arduino-cli config + installed cores
# /root/Arduino — Library Manager-installed Arduino libraries
# /var/cache/ccache — ccache cache (ESP-IDF compiles)
# /var/lib/velxio-build — persistent ESP-IDF build dir (one subdir per target)
VOLUME ["/app/data", "/root/.arduino15", "/root/Arduino", "/var/cache/ccache", "/var/lib/velxio-build"]
# Install ESP-IDF Python dependencies using the final image's Python
# The requirements.txt has version constraints required by ESP-IDF 4.4.x
RUN grep -v 'esp-windows-curses' /opt/esp-idf/requirements.txt \
| pip install --no-cache-dir -r /dev/stdin
EXPOSE 80
CMD ["/app/entrypoint.sh"]