diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..0a3de7fe --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +.devcontainer diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..c6168113 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Stage 1: Builder +FROM node:20-alpine AS builder + +# Install pnpm and required dependencies +RUN corepack enable && corepack prepare pnpm@10.10.0 --activate +RUN apk add --no-cache libc6-compat openssl + +WORKDIR /app + +# Copy package files and install dependencies +COPY package.json pnpm-lock.yaml ./ +COPY prisma ./prisma/ +RUN pnpm install --frozen-lockfile + +# Generate Prisma client +RUN pnpm prisma generate + +# Copy application files and build +COPY . . +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +RUN pnpm build + +# Stage 2: Runner +FROM node:20-alpine AS runner + +RUN apk add --no-cache openssl +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +WORKDIR /app + +ENV NODE_ENV=production \ + NEXT_TELEMETRY_DISABLED=1 \ + PORT=3000 \ + HOSTNAME="0.0.0.0" + +# Copy necessary files from builder +COPY --from=builder /app/public ./public +COPY --from=builder /app/package.json ./package.json +COPY --from=builder /app/prisma ./prisma +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \ + CMD node -e "require('http').get('http://localhost:3000/api/health', (r) => {process.exit(r.statusCode === 200 ? 0 : 1)})" || exit 1 + +CMD ["node", "server.js"] diff --git a/README.md b/README.md index 3466e5c8..2223904c 100644 --- a/README.md +++ b/README.md @@ -70,26 +70,28 @@ Create a `.env` file in the root directory: ```bash # Database -DATABASE_URL="postgresql://username:password@localhost:5432/auto_rfp" -DIRECT_URL="postgresql://username:password@localhost:5432/auto_rfp" +DATABASE_URL=postgresql://username:password@localhost:5432/auto_rfp +DIRECT_URL=postgresql://username:password@localhost:5432/auto_rfp # Supabase Configuration -NEXT_PUBLIC_SUPABASE_URL="your-supabase-project-url" -NEXT_PUBLIC_SUPABASE_ANON_KEY="your-supabase-anon-key" +NEXT_PUBLIC_SUPABASE_URL= +NEXT_PUBLIC_SUPABASE_ANON_KEY= # OpenAI API -OPENAI_API_KEY="your-openai-api-key" +OPENAI_API_KEY= # LlamaCloud -LLAMACLOUD_API_KEY="your-llamacloud-api-key" +LLAMACLOUD_API_KEY= # Optional: Internal API key and domain for internal users -# LLAMACLOUD_API_KEY_INTERNAL="your-internal-llamacloud-api-key" -# INTERNAL_EMAIL_DOMAIN="@yourdomain.com" # Defaults to @runllama.ai +# LLAMACLOUD_API_KEY_INTERNAL= +# INTERNAL_EMAIL_DOMAIN= # Defaults to @runllama.ai # App Configuration -NEXT_PUBLIC_APP_URL="http://localhost:3000" +NEXT_PUBLIC_APP_URL=http://localhost:3000 ``` +**Note**: To use the env file for the app AND for docker the env var values cannot be in quotes. + ### 4. Database Setup #### Set up PostgreSQL Database @@ -241,6 +243,22 @@ The application can be deployed to any platform that supports Node.js: - AWS Amplify - Google Cloud Run +### Build and Run with Docker + +AutoRFP includes Docker support for containerized deployment. + +```bash +# Build the Docker image +pnpm docker-build + +# Run the container +pnpm docker-run +``` + +**Note:** The Docker container uses Next.js standalone output mode for optimized production deployment. Make sure your `.env.local` includes a database connection string that's accessible from within the Docker container. + +**IMPORTANT NOTE**: When you build/run a docker container all of the `NEXT_*` env vars are resolved at `build-time` (when the container is built; because the vars also need to be available in the frontend and are generated into the frontend code). The other vars are resolved at `run-time` (when the container is started). Means, if you (for instance) need to set `NEXT_PUBLIC_APP_URL` to your own site (e.g. `https://rfp.mydomain.com`) then you need to make this change to the env file, before you run docker-build. + ## 🔌 API Endpoints ### Core APIs @@ -351,6 +369,7 @@ This project is licensed under the MIT License - see the [LICENSE][] file for de Built with ❤️ using Next.js, LlamaIndex, and OpenAI +[Docker]: https://docs.docker.com/get-docker/ [LICENSE]: ./LICENSE [cloud.llamaindex.ai]: https://cloud.llamaindex.ai [http://localhost:3000]: http://localhost:3000 diff --git a/app/api/health/route.ts b/app/api/health/route.ts new file mode 100644 index 00000000..181f5c55 --- /dev/null +++ b/app/api/health/route.ts @@ -0,0 +1,22 @@ +import { NextRequest } from "next/server"; +import { apiHandler } from "@/lib/middleware/api-handler"; +import { db } from "@/lib/db"; +import { env } from "@/lib/env"; + +/** + * Health check endpoint for Docker and monitoring + * GET /api/health + */ +export async function GET(request: NextRequest) { + return apiHandler(async () => { + // Check database connectivity + await db.$queryRaw`SELECT 1`; + + return { + status: "healthy", + timestamp: new Date().toISOString(), + uptime: process.uptime(), + environment: process.env.NODE_ENV, + }; + }); +} diff --git a/lib/utils/supabase/middleware.ts b/lib/utils/supabase/middleware.ts index 65a5fc10..edb0e079 100644 --- a/lib/utils/supabase/middleware.ts +++ b/lib/utils/supabase/middleware.ts @@ -40,7 +40,8 @@ export async function updateSession(request: NextRequest) { if ( !user && !request.nextUrl.pathname.startsWith('/login') && - !request.nextUrl.pathname.startsWith('/auth') + !request.nextUrl.pathname.startsWith('/auth') && + !request.nextUrl.pathname.startsWith('/api/health') ) { // no user, potentially respond by redirecting the user to the login page const url = request.nextUrl.clone() diff --git a/next.config.js b/next.config.js deleted file mode 100644 index de8beef0..00000000 --- a/next.config.js +++ /dev/null @@ -1,17 +0,0 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = { - // Configure environment variables that will be available on both server and client side - env: { - // You will need to set LLAMACLOUD_API_KEY in your deployment environment - // or in a .env file that's not committed to version control - LLAMACLOUD_API_KEY: process.env.LLAMACLOUD_API_KEY, - // Internal API key for internal users - LLAMACLOUD_API_KEY_INTERNAL: process.env.LLAMACLOUD_API_KEY_INTERNAL, - // Internal email domain (defaults to @runllama.ai) - INTERNAL_EMAIL_DOMAIN: process.env.INTERNAL_EMAIL_DOMAIN, - }, - // Other Next.js config options - reactStrictMode: true, -} - -module.exports = nextConfig; \ No newline at end of file diff --git a/next.config.ts b/next.config.ts index e9ffa308..904c8b35 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,20 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - /* config options here */ + // Configure environment variables that will be available on both server and client side + env: { + // You will need to set LLAMACLOUD_API_KEY in your deployment environment + // or in a .env.local file that's not committed to version control + LLAMACLOUD_API_KEY: process.env.LLAMACLOUD_API_KEY, + // Internal API key for internal users + LLAMACLOUD_API_KEY_INTERNAL: process.env.LLAMACLOUD_API_KEY_INTERNAL, + // Internal email domain (defaults to @runllama.ai) + INTERNAL_EMAIL_DOMAIN: process.env.INTERNAL_EMAIL_DOMAIN, + }, + // Other Next.js config options + reactStrictMode: true, + // Enable standalone output for Docker deployment + output: 'standalone', }; export default nextConfig; diff --git a/package.json b/package.json index 57ff9dc2..d5da03c3 100644 --- a/package.json +++ b/package.json @@ -3,11 +3,16 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack", "build": "next build", - "start": "next start", + "dev": "next dev --turbopack", + "docker-build": "docker build --tag auto-rfp .", + "docker-log": "docker logs --follow auto-rfp", + "docker-run": "docker run --rm --detach --env-file .env --name auto-rfp auto-rfp", + "docker-stop": "docker stop auto-rfp", + "health-check": "curl http://localhost:3000/api/health", "lint": "next lint", - "postinstall": "prisma generate" + "postinstall": "prisma generate", + "start": "next start" }, "dependencies": { "@ai-sdk/openai": "^1.3.22",