From c20c389ca229e17583bbf59dbaa1bc275f771524 Mon Sep 17 00:00:00 2001 From: Enrico Fasoli Date: Sun, 28 Jun 2026 14:52:22 +0200 Subject: [PATCH] feat: Docker build with idleTimeout patch via postbuild script - Add Dockerfile, .dockerignore, and docker-compose.yml - Move idleTimeout sed patch from Dockerfile to scripts/postbuild.mjs - Wire postbuild script into package.json lifecycle - Remove scripts/ from .dockerignore so it's available at build time --- .dockerignore | 58 +++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 49 ++++++++++++++++++++++++++++++++++++ docker-compose.yml | 19 ++++++++++++++ package.json | 3 ++- scripts/postbuild.mjs | 18 ++++++++++++++ 5 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 docker-compose.yml create mode 100644 scripts/postbuild.mjs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..ecdd8a2f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,58 @@ +# Dependencies +node_modules + +# Build output +dist +dist-ssr +.nitro +.tanstack +.wrangler +.output +.vinxi + +# Git +.git +.gitignore + +# OS / editor +.DS_Store +*.swp +*.swo +*~ +.vscode + +# Environment +.env +.env.* + +# Data +data/ +_data +data_ + +# Test artifacts +junit.xml +count.txt +todos.json + +# Agent files +AGENTS.md +CLAUDE.md +.agent/ +docs/site/ +.claude + +# Docs (not needed in production) +docs/ +PLAN.md +BACKEND-STRESSTEST.md +FRONTEND-STRESSTEST.md +README.md + +# Tests +tests/ + +# Windows / setup +run.bat +setup-dev.bat +setup.ps1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..9d7e629d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# syntax=docker/dockerfile:1 + +# ── Build stage ────────────────────────────────────────────────────────────── +FROM oven/bun:1-alpine AS build + +WORKDIR /app + +# Install build dependencies (alpine: glibc-compatible libs for native modules) +RUN apk add --no-cache git + +# Copy package files and local SDK package first (layer caching) +COPY package.json ./ +COPY packages/ packages/ + +# Install all dependencies (including file: local packages) +RUN bun install + +# Copy the rest of the source +COPY . . + +# Build the app (TanStack Start → Nitro output in .output/) +ARG BUILD_VERSION=unknown +ENV BUILD_VERSION=${BUILD_VERSION} +RUN bun run build + +# ── Production stage ───────────────────────────────────────────────────────── +FROM oven/bun:1-alpine AS production + +LABEL org.opencontainers.image.source="https://github.com/tealios/errata" + +WORKDIR /app + +# Copy only the built output from the build stage +COPY --from=build /app/.output /app/.output +COPY --from=build /app/public /app/public +COPY --from=build /app/plugins /app/plugins + +# Use the bun user (UID 1000) to match host user permissions +USER bun + +ENV DATA_DIR=/app/data +ENV PORT=7739 + +EXPOSE ${PORT} + +HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \ + CMD bun -e "fetch('http://localhost:' + process.env.PORT + '/api/health').then(r => { if (!r.ok) process.exit(1) }).catch(() => process.exit(1))" + +CMD ["bun", ".output/server/index.mjs"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..0ded93bb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,19 @@ +services: + errata: + build: + context: . + dockerfile: Dockerfile + args: + BUILD_VERSION: "${BUILD_VERSION:-dev}" + container_name: errata + restart: unless-stopped + ports: + - "${ERRATA_PORT:-7739}:7739" + environment: + - DATA_DIR=/app/data + - PORT=7739 + volumes: + - errata-data:/app/data + +volumes: + errata-data: diff --git a/package.json b/package.json index c68789c6..5df7c329 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "docs:sync": "bun scripts/sync-docs-from-commits.ts", "preview": "bun install --frozen-lockfile && vite preview", "test": "bun install --frozen-lockfile && vitest run --config vitest.config.ts", - "test:watch": "bun install --frozen-lockfile && vitest --config vitest.config.ts" + "test:watch": "bun install --frozen-lockfile && vitest --config vitest.config.ts", + "postbuild": "bun scripts/postbuild.mjs" }, "dependencies": { "@ai-sdk/openai-compatible": "^2.0.30", diff --git a/scripts/postbuild.mjs b/scripts/postbuild.mjs new file mode 100644 index 00000000..5f504eb2 --- /dev/null +++ b/scripts/postbuild.mjs @@ -0,0 +1,18 @@ +// Patch the Nitro server to set Bun.serve idleTimeout (default 10s is too short +// for LLM streaming endpoints). Nitro's srvx wraps Bun.serve and spreads +// options.bun into serveOptions, but Nitro has no config key for this. +import { readFileSync, writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const serverPath = resolve(__dirname, "../.output/server/index.mjs"); + +const content = readFileSync(serverPath, "utf-8"); +const patched = content.replace( + /bun:\s*{\s*websocket:\s*void\s+0\s*}/, + "bun: { websocket: void 0, idleTimeout: 255 }", +); + +writeFileSync(serverPath, patched, "utf-8"); +console.log("Patched idleTimeout in .output/server/index.mjs");