From f646ee43ce3ef5c4cef759a61da8f63a67d3e588 Mon Sep 17 00:00:00 2001 From: root Date: Thu, 11 Dec 2025 17:50:51 +0000 Subject: [PATCH] Complete production setup with Express server --- Dockerfile | 48 +++++++++++++++++++++++++--------------- app/routes/api.health.ts | 14 ++++++------ package-additions.json | 9 ++++++++ package.json | 13 +++++++---- server.js | 27 ++++++++++++++++++++++ server.mjs | 40 +++++++++++++++++++++++++++++++++ 6 files changed, 122 insertions(+), 29 deletions(-) create mode 100644 package-additions.json create mode 100644 server.js create mode 100644 server.mjs diff --git a/Dockerfile b/Dockerfile index 8fbf9ab..2f6c48a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,24 +1,30 @@ +# syntax=docker/dockerfile:1 FROM node:22-bookworm-slim AS base # Install pnpm RUN corepack enable && corepack prepare pnpm@9.15.9 --activate -# Install git (needed for some dependencies) +# Install system dependencies RUN apt-get update && \ - apt-get install -y --no-install-recommends git && \ - rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends \ + git \ + ca-certificates \ + && rm -rf /var/lib/apt/lists/* + +WORKDIR /app + +# Dependencies stage +FROM base AS deps + +COPY package.json pnpm-lock.yaml ./ + +# Install ALL dependencies (needed for build) +RUN pnpm install --frozen-lockfile # Build stage FROM base AS build -WORKDIR /app -# Copy package files -COPY package.json pnpm-lock.yaml ./ - -# Install ALL dependencies (including dev) for building -RUN pnpm install --frozen-lockfile - -# Copy source code +COPY --from=deps /app/node_modules ./node_modules COPY . . # Build the app @@ -26,19 +32,25 @@ RUN pnpm run build # Production stage FROM base AS production -WORKDIR /app + +ENV NODE_ENV=production +ENV PORT=8788 # Copy package files COPY package.json pnpm-lock.yaml ./ -# Install only production dependencies -RUN pnpm install --prod --frozen-lockfile +# Install production dependencies +RUN pnpm install --prod --frozen-lockfile --ignore-scripts -# Copy built files from build stage +# Copy built app COPY --from=build /app/build ./build +COPY --from=build /app/server.mjs ./server.mjs +COPY --from=build /app/public ./public -# Expose port EXPOSE 8788 -# Start the app -CMD ["pnpm", "start"] +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s \ + CMD node -e "require('http').get('http://localhost:8788/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" + +CMD ["node", "server.mjs"] diff --git a/app/routes/api.health.ts b/app/routes/api.health.ts index e5f5a30..5b012c5 100644 --- a/app/routes/api.health.ts +++ b/app/routes/api.health.ts @@ -1,8 +1,8 @@ -import { json, type LoaderFunctionArgs } from '@remix-run/cloudflare'; - -export const loader = async ({ request: _request }: LoaderFunctionArgs) => { - return json({ - status: 'healthy', - timestamp: new Date().toISOString(), +export function loader() { + return new Response("OK", { + status: 200, + headers: { + "Content-Type": "text/plain", + }, }); -}; +} diff --git a/package-additions.json b/package-additions.json new file mode 100644 index 0000000..f674f9c --- /dev/null +++ b/package-additions.json @@ -0,0 +1,9 @@ +{ + "dependencies": { + "@remix-run/express": "^2.13.1", + "@remix-run/node": "^2.13.1", + "express": "^4.19.2", + "compression": "^1.7.4", + "morgan": "^1.10.0" + } +} diff --git a/package.json b/package.json index 10477ba..9256afc 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "electron:build:mac": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mac", "electron:build:win": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --win", "electron:build:linux": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --linux", - "electron:build:dist": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mwl" + "electron:build:dist": "rm -rf dist && pnpm electron:build:renderer && pnpm electron:build:deps && electron-builder --mwl", + "start:prod": "node server.mjs" }, "engines": { "node": ">=18.18.0" @@ -95,7 +96,7 @@ "@radix-ui/react-tooltip": "^1.1.4", "@remix-run/cloudflare": "^2.15.2", "@remix-run/cloudflare-pages": "^2.15.2", - "@remix-run/node": "^2.15.2", + "@remix-run/node": "^2.13.1", "@remix-run/react": "^2.15.2", "@tanstack/react-virtual": "^3.13.0", "@types/react-beautiful-dnd": "^13.1.8", @@ -156,7 +157,11 @@ "use-debounce": "^10.0.4", "vite-plugin-node-polyfills": "^0.22.0", "zod": "^3.24.1", - "zustand": "^5.0.3" + "zustand": "^5.0.3", + "@remix-run/express": "^2.13.1", + "express": "^4.19.2", + "compression": "^1.7.4", + "morgan": "^1.10.0" }, "devDependencies": { "@blitz/eslint-plugin": "0.1.0", @@ -209,4 +214,4 @@ "@typescript-eslint/utils": "^8.0.0-alpha.30" }, "packageManager": "pnpm@9.14.4" -} +} \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..36b9da7 --- /dev/null +++ b/server.js @@ -0,0 +1,27 @@ +const { createRequestHandler } = require("@remix-run/express"); +const express = require("express"); +const path = require("path"); + +const BUILD_DIR = path.join(process.cwd(), "build/server"); + +const app = express(); + +// Serve static files +app.use(express.static("build/client", { + maxAge: "1y", + immutable: true, +})); + +// Handle Remix requests +app.all( + "*", + createRequestHandler({ + build: require(BUILD_DIR), + mode: process.env.NODE_ENV, + }) +); + +const port = process.env.PORT || 8788; +app.listen(port, "0.0.0.0", () => { + console.log(`✅ Bolt.diy running on http://0.0.0.0:${port}`); +}); diff --git a/server.mjs b/server.mjs new file mode 100644 index 0000000..82df829 --- /dev/null +++ b/server.mjs @@ -0,0 +1,40 @@ +import { createRequestHandler } from "@remix-run/express"; +import { installGlobals } from "@remix-run/node"; +import compression from "compression"; +import express from "express"; +import morgan from "morgan"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; + +installGlobals(); + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const BUILD_PATH = join(__dirname, "build", "server", "index.js"); +const PUBLIC_PATH = join(__dirname, "build", "client"); + +const app = express(); + +app.use(compression()); +app.disable("x-powered-by"); +app.use(morgan("tiny")); + +app.use( + express.static(PUBLIC_PATH, { + maxAge: "1h", + immutable: true, + }) +); + +app.all( + "*", + createRequestHandler({ + build: await import(BUILD_PATH), + }) +); + +const port = process.env.PORT || 8788; +app.listen(port, "0.0.0.0", () => { + console.log(`Express server listening on port ${port}`); +});