Skip to content

Commit 3ed17f4

Browse files
committed
fixing broken puppeteer providers in docker caused by alpine chromium 146 crashing / switched to debian slim with puppeteer's own chrome for testing / dropped 2-stage build / run as non-root / purge build tools after install, improve docker-test.sh to verify it all works. That's it. ;)
1 parent b531a7b commit 3ed17f4

3 files changed

Lines changed: 99 additions & 59 deletions

File tree

Dockerfile

Lines changed: 41 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,59 @@
1-
# ================================
2-
# Stage 1: Build stage
3-
# ================================
4-
FROM node:22-alpine AS builder
1+
FROM node:22-slim
2+
3+
# System deps for Chrome for Testing + build tools for native modules (better-sqlite3)
4+
# Must run as root
5+
RUN apt-get update && apt-get install -y --no-install-recommends \
6+
curl ca-certificates fonts-liberation libasound2 \
7+
libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 \
8+
libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 \
9+
libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils \
10+
python3 make g++ \
11+
&& rm -rf /var/lib/apt/lists/* \
12+
&& mkdir -p /db /conf /fredy \
13+
&& chown node:node /db /conf /fredy
514

6-
WORKDIR /build
7-
8-
# Install build dependencies needed for native modules (better-sqlite3)
9-
RUN apk add --no-cache python3 make g++
10-
11-
# Copy package files first for better layer caching
12-
COPY package.json yarn.lock ./
13-
14-
# Install all dependencies (including devDependencies for building)
15-
RUN yarn config set network-timeout 600000 \
16-
&& yarn --frozen-lockfile
17-
18-
# Copy source files needed for build
19-
COPY index.html vite.config.js ./
20-
COPY ui ./ui
21-
COPY lib ./lib
15+
WORKDIR /fredy
2216

23-
# Build frontend assets
24-
RUN yarn build:frontend
17+
# Everything from here runs as the built-in non-root node user (UID 1000)
18+
USER node
2519

26-
# ================================
27-
# Stage 2: Production stage
28-
# ================================
29-
FROM node:22-alpine
20+
ENV NODE_ENV=production \
21+
IS_DOCKER=true
3022

31-
WORKDIR /fredy
23+
COPY --chown=node:node package.json yarn.lock ./
3224

33-
# Install Chromium and curl (for healthcheck)
34-
# Using Alpine's chromium package which is much smaller
35-
RUN apk add --no-cache chromium curl
25+
# Install dependencies and purge build tools (only needed to compile better-sqlite3)
26+
RUN yarn config set network-timeout 600000 \
27+
&& yarn --frozen-lockfile \
28+
&& yarn cache clean
3629

37-
ENV NODE_ENV=production \
38-
IS_DOCKER=true \
39-
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
40-
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
30+
# Install Chrome for Testing in a separate layer — it's ~150MB and rarely changes,
31+
# so keeping it separate avoids re-downloading on every code/dependency change
32+
RUN npx puppeteer browsers install chrome
4133

42-
# Install build dependencies for native modules, then remove them after yarn install
43-
COPY package.json yarn.lock ./
34+
# Purge build tools now that native modules are compiled
35+
USER root
36+
RUN apt-get purge -y python3 make g++ \
37+
&& apt-get autoremove -y \
38+
&& rm -rf /var/lib/apt/lists/*
39+
USER node
4440

45-
RUN apk add --no-cache --virtual .build-deps python3 make g++ \
46-
&& yarn config set network-timeout 600000 \
47-
&& yarn --frozen-lockfile --production \
48-
&& yarn cache clean \
49-
&& apk del .build-deps
41+
COPY --chown=node:node index.html vite.config.js ./
42+
COPY --chown=node:node ui ./ui
43+
COPY --chown=node:node lib ./lib
5044

51-
# Copy built frontend from builder stage
52-
COPY --from=builder /build/ui/public ./ui/public
45+
RUN yarn build:frontend
5346

54-
# Copy application source (only what's needed at runtime)
55-
COPY index.js ./
56-
COPY index.html ./
57-
COPY lib ./lib
47+
COPY --chown=node:node index.js ./
5848

59-
# Prepare runtime directories and symlinks for data and config
60-
RUN mkdir -p /db /conf \
61-
&& chown 1000:1000 /db /conf \
62-
&& chmod 777 /db /conf \
63-
&& ln -s /db /fredy/db \
49+
RUN ln -s /db /fredy/db \
6450
&& ln -s /conf /fredy/conf
6551

6652
EXPOSE 9998
6753
VOLUME /db
6854
VOLUME /conf
6955

56+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
57+
CMD curl -f http://localhost:9998/ || exit 1
58+
7059
CMD ["node", "index.js"]

docker-test.sh

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,63 @@ if [ "$(docker ps -aq -f name=fredy)" ]; then
77
docker rm fredy || true
88
fi
99

10+
# On Apple Silicon, force linux/amd64 to match production CI and avoid arm64/x86_64
11+
# Chrome mismatch under Rosetta. On native Linux (amd64 or arm64) let Docker pick naturally. That took me fucking 1 hour to figure out.
12+
PLATFORM=""
13+
if [ "$(uname -m)" = "arm64" ] && [ "$(uname -s)" = "Darwin" ]; then
14+
PLATFORM="linux/amd64"
15+
fi
16+
1017
# Build image from local Dockerfile, forcing a fresh build without cache
11-
docker build --no-cache -t fredy:local .
18+
if [ -n "$PLATFORM" ]; then
19+
docker build --no-cache --platform "$PLATFORM" -t fredy:local .
20+
else
21+
docker build --no-cache -t fredy:local .
22+
fi
1223

1324
# Run container with volumes and port mapping
14-
docker run -d --name fredy \
15-
-v fredy_conf:/conf \
16-
-v fredy_db:/db \
17-
-p 9998:9998 \
18-
fredy:local
25+
if [ -n "$PLATFORM" ]; then
26+
docker run -d --name fredy --platform "$PLATFORM" -v fredy_conf:/conf -v fredy_db:/db -p 9998:9998 fredy:local
27+
else
28+
docker run -d --name fredy -v fredy_conf:/conf -v fredy_db:/db -p 9998:9998 fredy:local
29+
fi
30+
31+
echo "Waiting for app to be ready..."
32+
for i in $(seq 1 30); do
33+
if docker exec fredy curl -sf http://localhost:9998/ > /dev/null 2>&1; then
34+
echo "App is up"
35+
break
36+
fi
37+
if [ "$i" = "30" ]; then
38+
echo "App did not come up in time"
39+
docker logs fredy
40+
exit 1
41+
fi
42+
sleep 2
43+
done
44+
45+
# Verify the process is NOT running as root
46+
RUNNING_USER=$(docker exec fredy id -u)
47+
if [ "$RUNNING_USER" = "0" ]; then
48+
echo "Process is running as root!"
49+
exit 1
50+
fi
51+
echo "Process runs as UID $RUNNING_USER (not root)"
52+
53+
# Verify Chrome launches without crashing
54+
echo "Testing Chrome..."
55+
CHROME=$(docker exec fredy find /home/node/.cache/puppeteer -name chrome -type f 2>/dev/null | head -1)
56+
if [ -z "$CHROME" ]; then
57+
echo "Chrome binary not found"
58+
exit 1
59+
fi
60+
if docker exec fredy "$CHROME" --headless --no-sandbox --disable-gpu --dump-dom https://example.com 2>&1 | grep -q "<html"; then
61+
echo "Chrome works"
62+
else
63+
echo "Chrome failed to render a page"
64+
docker exec fredy "$CHROME" --headless --no-sandbox --disable-gpu --dump-dom https://example.com 2>&1 | head -20
65+
exit 1
66+
fi
67+
68+
echo ""
69+
echo "All checks passed."

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fredy",
3-
"version": "20.0.6",
3+
"version": "20.0.7",
44
"description": "[F]ind [R]eal [E]states [d]amn eas[y].",
55
"scripts": {
66
"prepare": "husky",

0 commit comments

Comments
 (0)