From cdfbfcd13e340d5b0971713ff61352a36ee42e31 Mon Sep 17 00:00:00 2001 From: Till JS Date: Thu, 26 Mar 2026 12:17:48 +0100 Subject: [PATCH] feat(infra): add sveltekit-base image and build-app script for Mac Mini MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add docker/Dockerfile.sveltekit-base: pre-built base with all 34 shared packages (mirrors nestjs-base pattern), eliminates redundant COPY/build steps from individual web Dockerfiles - Add scripts/mac-mini/build-app.sh: stops monitoring stack before build to free RAM, auto-restarts on exit (trap cleanup) - Migrate todo web Dockerfile to use sveltekit-base:local (47 COPY lines → 2, 4 build steps → 0) - Update CD workflow to build sveltekit-base when deploying web apps Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/cd-macmini.yml | 21 ++++- apps/todo/apps/web/Dockerfile | 65 ++------------- docker/Dockerfile.sveltekit-base | 76 ++++++++++++++++++ scripts/mac-mini/build-app.sh | 132 +++++++++++++++++++++++++++++++ 4 files changed, 233 insertions(+), 61 deletions(-) create mode 100644 docker/Dockerfile.sveltekit-base create mode 100755 scripts/mac-mini/build-app.sh diff --git a/.github/workflows/cd-macmini.yml b/.github/workflows/cd-macmini.yml index 6b098b644..7f49aa0e8 100644 --- a/.github/workflows/cd-macmini.yml +++ b/.github/workflows/cd-macmini.yml @@ -243,12 +243,29 @@ jobs: done fi + NEEDS_WEB_BASE=false + if [ "$DEPLOY_ALL" == "true" ]; then + NEEDS_WEB_BASE=true + else + for svc in $SERVICES; do + case "$svc" in *-web) NEEDS_WEB_BASE=true; break ;; esac + done + fi + if [ "$NEEDS_BASE" == "true" ]; then echo "=== Building shared NestJS base image ===" docker build -f docker/Dockerfile.nestjs-base -t nestjs-base:local . 2>&1 | tail -5 - echo "Base image built" + echo "NestJS base image built" else - echo "No backends to deploy, skipping base image" + echo "No backends to deploy, skipping NestJS base image" + fi + + if [ "$NEEDS_WEB_BASE" == "true" ]; then + echo "=== Building shared SvelteKit base image ===" + docker build -f docker/Dockerfile.sveltekit-base -t sveltekit-base:local . 2>&1 | tail -5 + echo "SvelteKit base image built" + else + echo "No web apps to deploy, skipping SvelteKit base image" fi - name: Build and deploy services diff --git a/apps/todo/apps/web/Dockerfile b/apps/todo/apps/web/Dockerfile index 0e33fcc18..9e682d2bf 100644 --- a/apps/todo/apps/web/Dockerfile +++ b/apps/todo/apps/web/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker/dockerfile:1 -# Build stage -FROM node:20-alpine AS builder +# Build stage - inherits pre-built shared packages from sveltekit-base +FROM sveltekit-base:local AS builder # Build arguments for SvelteKit static env vars ARG PUBLIC_BACKEND_URL=http://todo-backend:3018 @@ -10,66 +10,13 @@ ARG PUBLIC_MANA_CORE_AUTH_URL=http://mana-core-auth:3001 ENV PUBLIC_BACKEND_URL=$PUBLIC_BACKEND_URL ENV PUBLIC_MANA_CORE_AUTH_URL=$PUBLIC_MANA_CORE_AUTH_URL -# Install pnpm -RUN corepack enable && corepack prepare pnpm@9.15.0 --activate - -WORKDIR /app - -# Copy root workspace files -COPY pnpm-workspace.yaml ./ -COPY package.json ./ -COPY pnpm-lock.yaml ./ - -# --- AUTO-GENERATED COPY STATEMENTS (do not edit manually) --- -COPY patches/ ./patches/ -COPY packages/shared-api-client ./packages/shared-api-client -COPY packages/shared-app-onboarding ./packages/shared-app-onboarding -COPY packages/shared-auth ./packages/shared-auth -COPY packages/shared-auth-ui ./packages/shared-auth-ui -COPY packages/shared-branding ./packages/shared-branding -COPY packages/shared-error-tracking ./packages/shared-error-tracking -COPY packages/shared-feedback-service ./packages/shared-feedback-service -COPY packages/shared-feedback-types ./packages/shared-feedback-types -COPY packages/shared-feedback-ui ./packages/shared-feedback-ui -COPY packages/shared-help-content ./packages/shared-help-content -COPY packages/shared-help-types ./packages/shared-help-types -COPY packages/shared-help-ui ./packages/shared-help-ui -COPY packages/shared-i18n ./packages/shared-i18n -COPY packages/shared-icons ./packages/shared-icons -COPY packages/shared-profile-ui ./packages/shared-profile-ui -COPY packages/shared-pwa ./packages/shared-pwa -COPY packages/shared-splitscreen ./packages/shared-splitscreen -COPY packages/shared-stores ./packages/shared-stores -COPY packages/shared-subscription-ui ./packages/shared-subscription-ui -COPY packages/shared-tags ./packages/shared-tags -COPY packages/shared-tailwind ./packages/shared-tailwind -COPY packages/shared-theme ./packages/shared-theme -COPY packages/shared-theme-ui ./packages/shared-theme-ui -COPY packages/shared-types ./packages/shared-types -COPY packages/shared-ui ./packages/shared-ui -COPY packages/shared-utils ./packages/shared-utils -COPY packages/shared-vite-config ./packages/shared-vite-config -COPY packages/spiral-db ./packages/spiral-db +# Copy app-specific packages COPY apps/todo/packages/shared ./apps/todo/packages/shared -# --- END AUTO-GENERATED --- - COPY apps/todo/apps/web ./apps/todo/apps/web -# Install dependencies -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile - -# Build shared packages that need building -WORKDIR /app/packages/shared-vite-config -RUN pnpm build - -WORKDIR /app/packages/shared-auth -RUN pnpm build || true - -WORKDIR /app/packages/shared-error-tracking -RUN pnpm build - -WORKDIR /app/packages/shared-pwa -RUN pnpm build +# Install app-specific dependencies +RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --ignore-scripts # Build the web app WORKDIR /app/apps/todo/apps/web diff --git a/docker/Dockerfile.sveltekit-base b/docker/Dockerfile.sveltekit-base new file mode 100644 index 000000000..a2d95b34b --- /dev/null +++ b/docker/Dockerfile.sveltekit-base @@ -0,0 +1,76 @@ +# syntax=docker/dockerfile:1 +# Shared builder base for all SvelteKit web apps +# Contains pre-installed shared packages and pre-built dependencies +# +# Usage in web Dockerfiles: +# FROM sveltekit-base:local AS builder +# COPY apps/myapp/packages/shared ./apps/myapp/packages/shared +# COPY apps/myapp/apps/web ./apps/myapp/apps/web +# RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm install --frozen-lockfile +# WORKDIR /app/apps/myapp/apps/web +# RUN pnpm exec svelte-kit sync +# RUN NODE_OPTIONS="--max-old-space-size=4096" pnpm build + +FROM node:20-alpine + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@9.15.0 --activate + +WORKDIR /app + +# Copy root workspace files +COPY pnpm-workspace.yaml ./ +COPY package.json ./ +COPY pnpm-lock.yaml ./ +COPY patches/ ./patches/ + +# Copy ALL shared packages used by SvelteKit web apps +COPY packages/credit-operations ./packages/credit-operations +COPY packages/qr-export ./packages/qr-export +COPY packages/shared-api-client ./packages/shared-api-client +COPY packages/shared-app-onboarding ./packages/shared-app-onboarding +COPY packages/shared-auth ./packages/shared-auth +COPY packages/shared-auth-ui ./packages/shared-auth-ui +COPY packages/shared-branding ./packages/shared-branding +COPY packages/shared-config ./packages/shared-config +COPY packages/shared-error-tracking ./packages/shared-error-tracking +COPY packages/shared-feedback-service ./packages/shared-feedback-service +COPY packages/shared-feedback-types ./packages/shared-feedback-types +COPY packages/shared-feedback-ui ./packages/shared-feedback-ui +COPY packages/shared-help-content ./packages/shared-help-content +COPY packages/shared-help-types ./packages/shared-help-types +COPY packages/shared-help-ui ./packages/shared-help-ui +COPY packages/shared-i18n ./packages/shared-i18n +COPY packages/shared-icons ./packages/shared-icons +COPY packages/shared-profile-ui ./packages/shared-profile-ui +COPY packages/shared-pwa ./packages/shared-pwa +COPY packages/shared-splitscreen ./packages/shared-splitscreen +COPY packages/shared-stores ./packages/shared-stores +COPY packages/shared-subscription-types ./packages/shared-subscription-types +COPY packages/shared-subscription-ui ./packages/shared-subscription-ui +COPY packages/shared-tags ./packages/shared-tags +COPY packages/shared-tailwind ./packages/shared-tailwind +COPY packages/shared-theme ./packages/shared-theme +COPY packages/shared-theme-ui ./packages/shared-theme-ui +COPY packages/shared-types ./packages/shared-types +COPY packages/shared-ui ./packages/shared-ui +COPY packages/shared-utils ./packages/shared-utils +COPY packages/shared-vite-config ./packages/shared-vite-config +COPY packages/spiral-db ./packages/spiral-db +COPY packages/wallpaper-generator ./packages/wallpaper-generator + +# Install dependencies (shared packages only - app deps added later) +RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --ignore-scripts + +# Build shared packages in dependency order +RUN cd packages/shared-vite-config && pnpm build \ + && cd /app/packages/shared-auth && pnpm build || true \ + && cd /app/packages/shared-error-tracking && pnpm build \ + && cd /app/packages/shared-pwa && pnpm build \ + && cd /app/packages/shared-tags && pnpm build \ + && cd /app/packages/shared-api-client && pnpm build 2>/dev/null || true \ + && cd /app/packages/credit-operations && pnpm build 2>/dev/null || true \ + && cd /app/packages/spiral-db && pnpm build 2>/dev/null || true + +WORKDIR /app diff --git a/scripts/mac-mini/build-app.sh b/scripts/mac-mini/build-app.sh new file mode 100755 index 000000000..d858d2242 --- /dev/null +++ b/scripts/mac-mini/build-app.sh @@ -0,0 +1,132 @@ +#!/bin/bash +# Build and deploy specific app containers on the Mac Mini +# Automatically frees RAM by stopping monitoring before build +# +# Usage: +# ./scripts/mac-mini/build-app.sh todo-web +# ./scripts/mac-mini/build-app.sh todo-web todo-backend +# ./scripts/mac-mini/build-app.sh --all-web # rebuild all web apps +# ./scripts/mac-mini/build-app.sh --base # rebuild base images only + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +COMPOSE_FILE="$PROJECT_ROOT/docker-compose.macmini.yml" +DOCKER="${DOCKER_CMD:-/usr/local/bin/docker}" + +# Monitoring services (compose service names) +MONITORING_SERVICES=( + grafana + umami + victoriametrics + pushgateway + cadvisor + postgres-exporter + redis-exporter + node-exporter + vmalert + alertmanager + alert-notifier + glitchtip + glitchtip-worker +) + +# Track if we stopped monitoring +MONITORING_STOPPED=false + +cleanup() { + if [ "$MONITORING_STOPPED" = true ]; then + echo "" + echo "=== Restarting monitoring stack ===" + $DOCKER compose -f "$COMPOSE_FILE" start "${MONITORING_SERVICES[@]}" 2>/dev/null || true + echo "Monitoring restored." + fi +} + +# Always restart monitoring on exit (success, failure, or interrupt) +trap cleanup EXIT + +stop_monitoring() { + echo "=== Stopping monitoring to free RAM ===" + $DOCKER compose -f "$COMPOSE_FILE" stop "${MONITORING_SERVICES[@]}" 2>/dev/null || true + MONITORING_STOPPED=true + + # Also prune dangling build cache + $DOCKER builder prune -f 2>/dev/null | tail -1 || true + echo "RAM freed." + echo "" +} + +build_base_images() { + echo "=== Building sveltekit-base image ===" + $DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.sveltekit-base" -t sveltekit-base:local "$PROJECT_ROOT" 2>&1 | tail -5 + echo "sveltekit-base:local built." + echo "" + + echo "=== Building nestjs-base image ===" + $DOCKER build -f "$PROJECT_ROOT/docker/Dockerfile.nestjs-base" -t nestjs-base:local "$PROJECT_ROOT" 2>&1 | tail -5 + echo "nestjs-base:local built." + echo "" +} + +build_services() { + local services=("$@") + echo "=== Building: ${services[*]} ===" + $DOCKER compose -f "$COMPOSE_FILE" build --no-cache "${services[@]}" + echo "" + echo "=== Restarting: ${services[*]} ===" + $DOCKER compose -f "$COMPOSE_FILE" up -d --no-deps "${services[@]}" +} + +# --- Main --- + +if [ $# -eq 0 ]; then + echo "Usage: $0 | --base | --all-web" + echo "" + echo "Examples:" + echo " $0 todo-web # Build & restart todo web" + echo " $0 todo-web todo-backend # Build & restart both" + echo " $0 --base # Rebuild base images" + echo " $0 --all-web # Rebuild all web apps" + exit 1 +fi + +cd "$PROJECT_ROOT" + +# Pull latest code +echo "=== Pulling latest code ===" +git pull + +# Free RAM +stop_monitoring + +case "$1" in + --base) + build_base_images + ;; + --all-web) + build_base_images + # Find all web services in compose + WEB_SERVICES=$($DOCKER compose -f "$COMPOSE_FILE" config --services 2>/dev/null | grep '\-web$' || true) + if [ -n "$WEB_SERVICES" ]; then + build_services $WEB_SERVICES + else + echo "No web services found." + fi + ;; + *) + build_services "$@" + ;; +esac + +echo "" +echo "=== Build complete ===" + +# Show status of built services +for svc in "$@"; do + if [ "$svc" != "--base" ] && [ "$svc" != "--all-web" ]; then + STATUS=$($DOCKER compose -f "$COMPOSE_FILE" ps --format '{{.Name}}\t{{.Status}}' "$svc" 2>/dev/null || echo "$svc: unknown") + echo " $STATUS" + fi +done