From 293325748d1b80a469faaabe803c9e07fd75a992 Mon Sep 17 00:00:00 2001 From: Jimmy Briggs Date: Thu, 31 Oct 2024 13:46:49 -0400 Subject: [PATCH] feat: update Dockerfile and add new server.js setup --- app/.dockerignore | 81 +++++++++++++++++++++++----------------- app/.gcloudignore | 8 ++++ app/Dockerfile | 94 +++++++++++++++++++++++++---------------------- app/server.js | 74 +++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+), 77 deletions(-) create mode 100644 app/.gcloudignore create mode 100644 app/server.js diff --git a/app/.dockerignore b/app/.dockerignore index fd00eaa..31be768 100644 --- a/app/.dockerignore +++ b/app/.dockerignore @@ -1,34 +1,47 @@ -# Include any files or directories that you don't want to be copied to your -# container here (e.g., local build artifacts, temporary files, etc.). -# -# For more help, visit the .dockerignore file reference guide at -# https://docs.docker.com/go/build-context-dockerignore/ - -**/.classpath -**/.dockerignore -**/.env -**/.git -**/.gitignore -**/.project -**/.settings -**/.toolstarget -**/.vs -**/.vscode -**/.next -**/.cache -**/*.*proj.user -**/*.dbmdl -**/*.jfm -**/charts -**/docker-compose* -**/compose* -**/Dockerfile* -**/node_modules -**/npm-debug.log -**/obj -**/secrets.dev.yaml -**/values.dev.yaml -**/build -**/dist -LICENSE -README.md +# Version control +.git +.gitignore +.github + +# Dependencies +node_modules +.pnpm-store + +# Build outputs +build +dist + +# Development files +.env +.env.* +*.log +npm-debug.log* +.cache + +# IDE and editor files +.vscode +.idea +*.swp +*.swo + +# Test files +__tests__ +*.test.js +*.spec.js +coverage + +# Documentation +docs +*.md + +# Temporary files +.DS_Store +*.tmp +*.temp + +# Allow specific files/folders +!package.json +!pnpm-lock.yaml +!server.js +!app/ +!public/ diff --git a/app/.gcloudignore b/app/.gcloudignore new file mode 100644 index 0000000..bf57f59 --- /dev/null +++ b/app/.gcloudignore @@ -0,0 +1,8 @@ +.git +.gitignore +node_modules/ +npm-debug.log +README.md +.env +*.test.js +coverage/ \ No newline at end of file diff --git a/app/Dockerfile b/app/Dockerfile index ac60d96..4edc94b 100644 --- a/app/Dockerfile +++ b/app/Dockerfile @@ -1,9 +1,9 @@ -ARG NODE_VERSION=20.12.2 +ARG NODE_VERSION=18 ARG PNPM_VERSION=9.0.5 ################################################################################ -# Use node image for base image for all stages. -FROM node:${NODE_VERSION}-alpine as base +# Base stage for shared node/pnpm setup +FROM node:${NODE_VERSION}-alpine AS base # Associate with GitHub repository LABEL org.opencontainers.image.source=https://github.com/noclocks/bastienlaw-remix @@ -16,59 +16,67 @@ RUN --mount=type=cache,target=/root/.npm \ npm install -g pnpm@${PNPM_VERSION} ################################################################################ -# Create a stage for installing production dependecies. -FROM base as deps - -# Download dependencies as a separate step to take advantage of Docker's caching. -# Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds. -# Leverage bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them -# into this layer. -RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ - --mount=type=cache,target=/root/.local/share/pnpm/store \ - pnpm install --prod --frozen-lockfile +# Dependencies stage - install all dependencies +FROM base AS deps + +# Copy package files +COPY package.json pnpm-lock.yaml ./ + +# Install dependencies with a more flexible approach +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm install --prod ################################################################################ -# Create a stage for building the application. -FROM deps as build +# Builder stage - build the application +FROM base AS builder + +# Copy package files +COPY package.json pnpm-lock.yaml ./ -# Download additional development dependencies before building, as some projects require -# "devDependencies" to be installed to build. If you don't need this, remove this step. -RUN --mount=type=bind,source=package.json,target=package.json \ - --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \ - --mount=type=cache,target=/root/.local/share/pnpm/store \ - pnpm install --frozen-lockfile +# Install all dependencies (including dev deps) +RUN --mount=type=cache,target=/root/.local/share/pnpm/store \ + pnpm install -# Copy the rest of the source files into the image. +# Copy source code COPY . . -# Run the build script. + +# Build the application RUN pnpm run build +# List build output for debugging +RUN echo "Build directory structure:" && \ + find build -type f + ################################################################################ -# Create a new stage to run the application with minimal runtime dependencies -# where the necessary files are copied from the build stage. -FROM base as final +# Runner stage - final production image +FROM node:${NODE_VERSION}-alpine AS runner -# Use production node environment by default. -# ENV NODE_ENV production -ENV NODE_ENV development +# Use production node environment +ENV NODE_ENV=production +ENV HOST=0.0.0.0 +ENV PORT=3000 -# # Run the application as a non-root user. -USER node +WORKDIR /usr/src/app -# Copy package.json so that package manager commands can be used. -COPY package.json . +# Install only the packages needed to run the server +COPY package.json pnpm-lock.yaml ./ +RUN npm install --global pnpm@${PNPM_VERSION} && \ + pnpm install --prod -# Copy the production dependencies from the deps stage and also -# the built application from the build stage into the image. -COPY --from=deps /usr/src/app/node_modules ./node_modules -COPY --from=build /usr/src/app/build ./build +# Copy server.js and the build directory +COPY server.js ./ +COPY --from=builder /usr/src/app/build ./build +COPY --from=builder /usr/src/app/public ./public -ENV HOST '0.0.0.0' -ENV PORT 8080 +# List directories for debugging +RUN echo "Final build structure:" && \ + find . -type f + +# Run the application as a non-root user. +USER node # Expose the port that the application listens on. -EXPOSE 8080 +EXPOSE 3000 -# Run the application. -CMD ["pnpm", "run", "serve"] +# Run the application using the express server +CMD ["node", "server.js"] diff --git a/app/server.js b/app/server.js new file mode 100644 index 0000000..be64966 --- /dev/null +++ b/app/server.js @@ -0,0 +1,74 @@ +import { createRequestHandler } from "@remix-run/express"; +import { installGlobals } from "@remix-run/node"; +import compression from "compression"; +import express from "express"; +import { fileURLToPath } from "url"; +import { dirname, join } from "path"; +import fs from 'fs'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); + +installGlobals(); + +const viteDevServer = + process.env.NODE_ENV === "production" + ? undefined + : await import("vite").then((vite) => + vite.createServer({ + server: { middlewareMode: true }, + }) + ); + +const app = express(); + +// Add health check endpoint +app.get('/_health', (req, res) => { + res.status(200).send('OK'); +}); + +app.use(compression()); + +// http://expressjs.com/en/advanced/best-practice-security.html#at-a-minimum-disable-x-powered-by-header +app.disable("x-powered-by"); + +// handle asset requests +if (viteDevServer) { + app.use(viteDevServer.middlewares); +} else { + app.use( + "/assets", + express.static(join(__dirname, "build/client/assets"), { immutable: true, maxAge: "1y" }) + ); +} +app.use(express.static(join(__dirname, "build/client"), { maxAge: "1h" })); + +// Log build directory contents for debugging +console.log('Build directory contents:'); +try { + console.log('Client directory:', fs.readdirSync(join(__dirname, 'build/client'))); +} catch (error) { + console.error('Error reading build directory:', error); +} + +// handle SSR requests +app.all( + "*", + createRequestHandler({ + build: viteDevServer + ? () => viteDevServer.ssrLoadModule("virtual:remix/server-build") + : { default: { default: { handler: (req, res) => res.sendFile(join(__dirname, 'build/client/index.html')) } } }, + }) +); + +const port = process.env.PORT || 3000; +const server = app.listen(port, () => { + console.log(`Express server listening on port ${port}`); +}); + +// Graceful shutdown +process.on('SIGTERM', () => { + console.log('SIGTERM signal received: closing HTTP server'); + server.close(() => { + console.log('HTTP server closed'); + }); +}); \ No newline at end of file