Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -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"]
19 changes: 19 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -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:
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
18 changes: 18 additions & 0 deletions scripts/postbuild.mjs
Original file line number Diff line number Diff line change
@@ -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");