Compare commits

...

1 Commits

3 changed files with 99 additions and 59 deletions

View File

@@ -1,70 +1,59 @@
# ================================ FROM node:22-slim
# Stage 1: Build stage
# ================================
FROM node:22-alpine AS builder
WORKDIR /build # System deps for Chrome for Testing + build tools for native modules (better-sqlite3)
# Must run as root
# Install build dependencies needed for native modules (better-sqlite3) RUN apt-get update && apt-get install -y --no-install-recommends \
RUN apk add --no-cache python3 make g++ curl ca-certificates fonts-liberation libasound2 \
libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 \
# Copy package files first for better layer caching libdrm2 libgbm1 libgtk-3-0 libnspr4 libnss3 \
COPY package.json yarn.lock ./ libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils \
python3 make g++ \
# Install all dependencies (including devDependencies for building) && rm -rf /var/lib/apt/lists/* \
RUN yarn config set network-timeout 600000 \ && mkdir -p /db /conf /fredy \
&& yarn --frozen-lockfile && chown node:node /db /conf /fredy
# Copy source files needed for build
COPY index.html vite.config.js ./
COPY ui ./ui
COPY lib ./lib
# Build frontend assets
RUN yarn build:frontend
# ================================
# Stage 2: Production stage
# ================================
FROM node:22-alpine
WORKDIR /fredy WORKDIR /fredy
# Install Chromium and curl (for healthcheck) # Everything from here runs as the built-in non-root node user (UID 1000)
# Using Alpine's chromium package which is much smaller USER node
RUN apk add --no-cache chromium curl
ENV NODE_ENV=production \ ENV NODE_ENV=production \
IS_DOCKER=true \ IS_DOCKER=true
PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \
PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
# Install build dependencies for native modules, then remove them after yarn install COPY --chown=node:node package.json yarn.lock ./
COPY package.json yarn.lock ./
RUN apk add --no-cache --virtual .build-deps python3 make g++ \ # Install dependencies and purge build tools (only needed to compile better-sqlite3)
&& yarn config set network-timeout 600000 \ RUN yarn config set network-timeout 600000 \
&& yarn --frozen-lockfile --production \ && yarn --frozen-lockfile \
&& yarn cache clean \ && yarn cache clean
&& apk del .build-deps
# Copy built frontend from builder stage # Install Chrome for Testing in a separate layer — it's ~150MB and rarely changes,
COPY --from=builder /build/ui/public ./ui/public # so keeping it separate avoids re-downloading on every code/dependency change
RUN npx puppeteer browsers install chrome
# Copy application source (only what's needed at runtime) # Purge build tools now that native modules are compiled
COPY index.js ./ USER root
COPY index.html ./ RUN apt-get purge -y python3 make g++ \
COPY lib ./lib && apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
USER node
# Prepare runtime directories and symlinks for data and config COPY --chown=node:node index.html vite.config.js ./
RUN mkdir -p /db /conf \ COPY --chown=node:node ui ./ui
&& chown 1000:1000 /db /conf \ COPY --chown=node:node lib ./lib
&& chmod 777 /db /conf \
&& ln -s /db /fredy/db \ RUN yarn build:frontend
COPY --chown=node:node index.js ./
RUN ln -s /db /fredy/db \
&& ln -s /conf /fredy/conf && ln -s /conf /fredy/conf
EXPOSE 9998 EXPOSE 9998
VOLUME /db VOLUME /db
VOLUME /conf VOLUME /conf
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD curl -f http://localhost:9998/ || exit 1
CMD ["node", "index.js"] CMD ["node", "index.js"]

View File

@@ -7,12 +7,63 @@ if [ "$(docker ps -aq -f name=fredy)" ]; then
docker rm fredy || true docker rm fredy || true
fi fi
# On Apple Silicon, force linux/amd64 to match production CI and avoid arm64/x86_64
# Chrome mismatch under Rosetta. On native Linux (amd64 or arm64) let Docker pick naturally. That took me fucking 1 hour to figure out.
PLATFORM=""
if [ "$(uname -m)" = "arm64" ] && [ "$(uname -s)" = "Darwin" ]; then
PLATFORM="linux/amd64"
fi
# Build image from local Dockerfile, forcing a fresh build without cache # Build image from local Dockerfile, forcing a fresh build without cache
docker build --no-cache -t fredy:local . if [ -n "$PLATFORM" ]; then
docker build --no-cache --platform "$PLATFORM" -t fredy:local .
else
docker build --no-cache -t fredy:local .
fi
# Run container with volumes and port mapping # Run container with volumes and port mapping
docker run -d --name fredy \ if [ -n "$PLATFORM" ]; then
-v fredy_conf:/conf \ docker run -d --name fredy --platform "$PLATFORM" -v fredy_conf:/conf -v fredy_db:/db -p 9998:9998 fredy:local
-v fredy_db:/db \ else
-p 9998:9998 \ docker run -d --name fredy -v fredy_conf:/conf -v fredy_db:/db -p 9998:9998 fredy:local
fredy:local fi
echo "Waiting for app to be ready..."
for i in $(seq 1 30); do
if docker exec fredy curl -sf http://localhost:9998/ > /dev/null 2>&1; then
echo "App is up"
break
fi
if [ "$i" = "30" ]; then
echo "App did not come up in time"
docker logs fredy
exit 1
fi
sleep 2
done
# Verify the process is NOT running as root
RUNNING_USER=$(docker exec fredy id -u)
if [ "$RUNNING_USER" = "0" ]; then
echo "Process is running as root!"
exit 1
fi
echo "Process runs as UID $RUNNING_USER (not root)"
# Verify Chrome launches without crashing
echo "Testing Chrome..."
CHROME=$(docker exec fredy find /home/node/.cache/puppeteer -name chrome -type f 2>/dev/null | head -1)
if [ -z "$CHROME" ]; then
echo "Chrome binary not found"
exit 1
fi
if docker exec fredy "$CHROME" --headless --no-sandbox --disable-gpu --dump-dom https://example.com 2>&1 | grep -q "<html"; then
echo "Chrome works"
else
echo "Chrome failed to render a page"
docker exec fredy "$CHROME" --headless --no-sandbox --disable-gpu --dump-dom https://example.com 2>&1 | head -20
exit 1
fi
echo ""
echo "All checks passed."

View File

@@ -1,6 +1,6 @@
{ {
"name": "fredy", "name": "fredy",
"version": "20.0.6", "version": "20.0.7",
"description": "[F]ind [R]eal [E]states [d]amn eas[y].", "description": "[F]ind [R]eal [E]states [d]amn eas[y].",
"scripts": { "scripts": {
"prepare": "husky", "prepare": "husky",