diff --git a/.dockerignore b/.dockerignore index 12b7a58..0b98774 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,18 +1,104 @@ -Dockerfile -.dockerignore +# Dependencies node_modules -npm-debug.log -README.md -.next -.git -.pnp -.pnp.js -.turbo -.vercel +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* + +# Production builds .next/ out/ -build -dist -npm-debug.log* +dist/ +build/ + +# Environment files +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS files .DS_Store -*.pem +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile* +docker-compose* +.dockerignore + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Turbo +.turbo + +# Vercel +.vercel diff --git a/DOCKER.md b/DOCKER.md new file mode 100644 index 0000000..792ae07 --- /dev/null +++ b/DOCKER.md @@ -0,0 +1,243 @@ +# Docker Setup for NextPlacement + +This document explains how to use Docker with the NextPlacement monorepo. + +## Overview + +The project contains three Dockerfiles: +- `Dockerfile` - Root-level Dockerfile for the entire monorepo +- `apps/admin/Dockerfile` - Specific Dockerfile for the admin application +- `apps/student/Dockerfile` - Specific Dockerfile for the student application + +## Quick Start + +### Using Docker Compose (Recommended) + +1. **Build and run both applications:** + ```bash + docker-compose up --build + ``` + +2. **Run in detached mode:** + ```bash + docker-compose up -d --build + ``` + +3. **Stop the services:** + ```bash + docker-compose down + ``` + +### Individual Application Builds + +#### Build Admin Application +```bash +docker build -f apps/admin/Dockerfile -t nextplacement-admin . +docker run -p 3001:3001 nextplacement-admin +``` + +#### Build Student Application +```bash +docker build -f apps/student/Dockerfile -t nextplacement-student . +docker run -p 3000:3000 nextplacement-student +``` + +#### Build Entire Monorepo +```bash +docker build -t nextplacement . +docker run -p 3000:3000 nextplacement +``` + +## Ports + +- **Student Application**: `http://localhost:3000` +- **Admin Application**: `http://localhost:3001` + +## Environment Variables + +Create a `.env` file in the root directory with your environment variables: + +```env +# Database +DATABASE_URL="your-database-url" + +# NextAuth +NEXTAUTH_URL="http://localhost:3000" +NEXTAUTH_SECRET="your-secret" + +# Other environment variables... +``` + +Then uncomment the `env_file` section in `docker-compose.yml`: + +```yaml +env_file: + - .env +``` + +## Dockerfile Features + +### Multi-stage Builds +- **deps stage**: Installs dependencies with optimal caching +- **builder stage**: Builds the application +- **runner stage**: Creates the production image + +### Security +- Runs as non-root user (`nextjs`) +- Minimal attack surface with Alpine Linux +- Proper file permissions + +### Performance +- Layer caching optimization +- Standalone Next.js output +- Minimal production image size + +### Monorepo Support +- Handles workspace dependencies +- Builds shared packages (`@workspace/ui`, `@workspace/db`) +- Uses Turbo for efficient builds + +## Development with Docker + +### Development Mode +For development, you can mount the source code: + +```bash +docker run -v $(pwd):/app -p 3000:3000 nextplacement +``` + +### Hot Reload +To enable hot reload in development: + +```bash +docker-compose -f docker-compose.dev.yml up +``` + +## Production Deployment + +### Environment Variables +Ensure all required environment variables are set: + +```bash +docker run -e DATABASE_URL="..." -e NEXTAUTH_SECRET="..." -p 3000:3000 nextplacement +``` + +### Health Checks +The docker-compose configuration includes health checks that verify the applications are running properly. + +### Scaling +You can scale individual services: + +```bash +docker-compose up --scale student=3 --scale admin=2 +``` + +## Troubleshooting + +### Build Issues +1. **Clear Docker cache:** + ```bash + docker system prune -a + ``` + +2. **Rebuild without cache:** + ```bash + docker-compose build --no-cache + ``` + +### Runtime Issues +1. **Check logs:** + ```bash + docker-compose logs student + docker-compose logs admin + ``` + +2. **Access container shell:** + ```bash + docker-compose exec student sh + ``` + +### Port Conflicts +If ports 3000 or 3001 are already in use, modify the `docker-compose.yml`: + +```yaml +ports: + - "3002:3000" # Map to different host port +``` + +## Best Practices + +1. **Always use specific image tags in production** +2. **Set up proper logging and monitoring** +3. **Use secrets management for sensitive data** +4. **Implement proper backup strategies** +5. **Monitor resource usage** + +## Advanced Configuration + +### Custom Dockerfile +You can create custom Dockerfiles for specific environments: + +```dockerfile +# Dockerfile.prod +FROM node:22-alpine AS base +# ... production-specific configuration +``` + +### Docker Compose Overrides +Create `docker-compose.override.yml` for local development: + +```yaml +version: '3.8' +services: + student: + volumes: + - .:/app + - /app/node_modules + environment: + - NODE_ENV=development +``` + +## Monitoring + +### Health Checks +The applications include health check endpoints. Create an API route: + +```typescript +// apps/student/app/api/health/route.ts +export async function GET() { + return Response.json({ status: 'ok' }) +} +``` + +### Logging +Configure proper logging for production: + +```bash +docker-compose up --build 2>&1 | tee app.log +``` + +## Security Considerations + +1. **Never commit `.env` files** +2. **Use Docker secrets for sensitive data** +3. **Regularly update base images** +4. **Scan images for vulnerabilities** +5. **Implement proper network policies** + +## Performance Optimization + +1. **Use multi-stage builds** (already implemented) +2. **Optimize layer caching** (already implemented) +3. **Use `.dockerignore`** (already implemented) +4. **Consider using BuildKit** +5. **Monitor image sizes** + +## Support + +For issues related to Docker setup, check: +1. Docker logs +2. Application logs +3. Network connectivity +4. Environment variables +5. File permissions \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 2d38be6..db12f66 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,29 +1,68 @@ +# Use the official Node.js runtime as the base image FROM node:22-alpine AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk update && apk add --no-cache libc6-compat +WORKDIR /app + +# Install pnpm globally RUN corepack enable && corepack prepare pnpm@latest --activate -FROM base AS builder -RUN apk update && apk add --no-cache libc6-compat -WORKDIR /app -RUN pnpm add -g turbo@^2 -COPY . . -RUN turbo prune admin --docker +# Copy package files +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* ./ +COPY apps/admin/package.json ./apps/admin/ +COPY apps/student/package.json ./apps/student/ +COPY packages/db/package.json ./packages/db/ +COPY packages/ui/package.json ./packages/ui/ -FROM base AS installer -RUN apk update && apk add --no-cache libc6-compat +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder WORKDIR /app -COPY --from=builder /app/out/json/ . -RUN pnpm fetch --frozen-lockfile -RUN pnpm install --offline --frozen-lockfile --prod=false -COPY --from=builder /app/out/full/ . +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Set environment variables for build +ENV NEXT_TELEMETRY_DISABLED 1 +ENV NODE_ENV production + +# Build all applications RUN pnpm turbo run build +# Production image, copy all the files and run next FROM base AS runner WORKDIR /app -RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy the standalone outputs +COPY --from=builder /app/apps/admin/.next/standalone ./apps/admin/ +COPY --from=builder /app/apps/admin/.next/static ./apps/admin/.next/static +COPY --from=builder /app/apps/admin/public ./apps/admin/public + +COPY --from=builder /app/apps/student/.next/standalone ./apps/student/ +COPY --from=builder /app/apps/student/.next/static ./apps/student/.next/static +COPY --from=builder /app/apps/student/public ./apps/student/public + +# Set the correct permission for prerender cache +RUN mkdir -p apps/admin/.next apps/student/.next +RUN chown -R nextjs:nodejs apps/ + USER nextjs -COPY --from=installer --chown=nextjs:nodejs /app/apps/admin/.next/standalone ./ -COPY --from=installer --chown=nextjs:nodejs /app/apps/admin/.next/static ./apps/admin/.next/static -COPY --from=installer --chown=nextjs:nodejs /app/apps/admin/public ./apps/admin/public -CMD node apps/admin/server.js + +EXPOSE 3000 3001 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +# Default to running the student app +# You can override this with CMD ["node", "apps/admin/server.js"] for admin +CMD ["node", "apps/student/server.js"] diff --git a/apps/admin/Dockerfile b/apps/admin/Dockerfile index 9767c4b..d0ec5e7 100644 --- a/apps/admin/Dockerfile +++ b/apps/admin/Dockerfile @@ -1,29 +1,63 @@ +# Use the official Node.js runtime as the base image FROM node:22-alpine AS base -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk update && apk add --no-cache libc6-compat +WORKDIR /app + +# Install pnpm globally RUN corepack enable && corepack prepare pnpm@latest --activate +# Copy package files +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* ./ +COPY apps/admin/package.json ./apps/admin/ +COPY packages/db/package.json ./packages/db/ +COPY packages/ui/package.json ./packages/ui/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Rebuild the source code only when needed FROM base AS builder -RUN apk update && apk add --no-cache libc6-compat WORKDIR /app -RUN pnpm add -g turbo@^2 +COPY --from=deps /app/node_modules ./node_modules COPY . . -RUN turbo prune admin --docker -FROM base AS installer -RUN apk update && apk add --no-cache libc6-compat -WORKDIR /app -COPY --from=builder /app/out/json/ . -RUN pnpm fetch --frozen-lockfile -RUN pnpm install --offline --frozen-lockfile --prod=false -COPY --from=builder /app/out/full/ . -RUN pnpm turbo run build +# Set environment variables for build +ENV NEXT_TELEMETRY_DISABLED 1 +ENV NODE_ENV production +# Build the application +RUN pnpm turbo run build --filter=admin... + +# Production image, copy all the files and run next FROM base AS runner WORKDIR /app -RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +# Copy the standalone output +COPY --from=builder /app/apps/admin/.next/standalone ./ +COPY --from=builder /app/apps/admin/.next/static ./apps/admin/.next/static +COPY --from=builder /app/apps/admin/public ./apps/admin/public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + USER nextjs -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public -CMD node apps/web/server.js + +EXPOSE 3001 + +ENV PORT 3001 +ENV HOSTNAME "0.0.0.0" + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "apps/admin/server.js"] diff --git a/apps/admin/app/api/health/route.ts b/apps/admin/app/api/health/route.ts new file mode 100644 index 0000000..09de058 --- /dev/null +++ b/apps/admin/app/api/health/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server' + +export async function GET() { + try { + // You can add additional health checks here + // For example, database connectivity, external service checks, etc. + + return NextResponse.json( + { + status: 'ok', + timestamp: new Date().toISOString(), + service: 'admin-app' + }, + { status: 200 } + ) + } catch (error) { + return NextResponse.json( + { + status: 'error', + message: 'Health check failed', + timestamp: new Date().toISOString(), + service: 'admin-app' + }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/apps/student/Dockerfile b/apps/student/Dockerfile index c5d53bd..45adef3 100644 --- a/apps/student/Dockerfile +++ b/apps/student/Dockerfile @@ -1,38 +1,63 @@ +# Use the official Node.js runtime as the base image FROM node:22-alpine AS base - -FROM base AS builder -RUN apk update -RUN apk add --no-cache libc6-compat + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk update && apk add --no-cache libc6-compat WORKDIR /app -RUN corepack enable pnpm && pnpm add -g turbo@^2 -COPY . . - -RUN turbo prune student --docker - -FROM base AS installer -RUN apk update -RUN apk add --no-cache libc6-compat +# Install pnpm globally +RUN corepack enable && corepack prepare pnpm@latest --activate + +# Copy package files +COPY package.json pnpm-lock.yaml* pnpm-workspace.yaml* ./ +COPY apps/student/package.json ./apps/student/ +COPY packages/db/package.json ./packages/db/ +COPY packages/ui/package.json ./packages/ui/ + +# Install dependencies +RUN pnpm install --frozen-lockfile + +# Rebuild the source code only when needed +FROM base AS builder WORKDIR /app - -COPY --from=builder /app/out/json/ . -RUN yarn install --frozen-lockfile - -COPY --from=builder /app/out/full/ . -RUN pnpm turbo run build - +COPY --from=deps /app/node_modules ./node_modules +COPY . . + +# Set environment variables for build +ENV NEXT_TELEMETRY_DISABLED 1 +ENV NODE_ENV production + +# Build the application +RUN pnpm turbo run build --filter=student... + +# Production image, copy all the files and run next FROM base AS runner WORKDIR /app - -# Don't run production as root + +ENV NODE_ENV production +ENV NEXT_TELEMETRY_DISABLED 1 + RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs + +# Copy the standalone output +COPY --from=builder /app/apps/student/.next/standalone ./ +COPY --from=builder /app/apps/student/.next/static ./apps/student/.next/static +COPY --from=builder /app/apps/student/public ./apps/student/public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + USER nextjs - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./ -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static -COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public - -CMD node apps/web/server.js \ No newline at end of file + +EXPOSE 3000 + +ENV PORT 3000 +ENV HOSTNAME "0.0.0.0" + +# server.js is created by next build from the standalone output +# https://nextjs.org/docs/pages/api-reference/next-config-js/output +CMD ["node", "apps/student/server.js"] \ No newline at end of file diff --git a/apps/student/app/api/health/route.ts b/apps/student/app/api/health/route.ts new file mode 100644 index 0000000..e8b5709 --- /dev/null +++ b/apps/student/app/api/health/route.ts @@ -0,0 +1,27 @@ +import { NextResponse } from 'next/server' + +export async function GET() { + try { + // You can add additional health checks here + // For example, database connectivity, external service checks, etc. + + return NextResponse.json( + { + status: 'ok', + timestamp: new Date().toISOString(), + service: 'student-app' + }, + { status: 200 } + ) + } catch (error) { + return NextResponse.json( + { + status: 'error', + message: 'Health check failed', + timestamp: new Date().toISOString(), + service: 'student-app' + }, + { status: 500 } + ) + } +} \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..d0734f3 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,58 @@ +version: '3.8' + +services: + # Student application (Development) + student-dev: + build: + context: . + dockerfile: apps/student/Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=development + - PORT=3000 + - HOSTNAME=0.0.0.0 + volumes: + - .:/app + - /app/node_modules + - /app/apps/student/node_modules + - /app/packages/ui/node_modules + - /app/packages/db/node_modules + command: ["pnpm", "dev", "--filter=student"] + restart: unless-stopped + + # Admin application (Development) + admin-dev: + build: + context: . + dockerfile: apps/admin/Dockerfile + ports: + - "3001:3001" + environment: + - NODE_ENV=development + - PORT=3001 + - HOSTNAME=0.0.0.0 + volumes: + - .:/app + - /app/node_modules + - /app/apps/admin/node_modules + - /app/packages/ui/node_modules + - /app/packages/db/node_modules + command: ["pnpm", "dev", "--filter=admin"] + restart: unless-stopped + + # Database (Development) + postgres-dev: + image: postgres:15-alpine + environment: + POSTGRES_DB: nextplacement_dev + POSTGRES_USER: postgres + POSTGRES_PASSWORD: password + volumes: + - postgres_dev_data:/var/lib/postgresql/data + ports: + - "5432:5432" + restart: unless-stopped + +volumes: + postgres_dev_data: \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..36f16b5 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,62 @@ +version: '3.8' + +services: + # Student application + student: + build: + context: . + dockerfile: apps/student/Dockerfile + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - PORT=3000 + - HOSTNAME=0.0.0.0 + # Add your environment variables here + # env_file: + # - .env + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Admin application + admin: + build: + context: . + dockerfile: apps/admin/Dockerfile + ports: + - "3001:3001" + environment: + - NODE_ENV=production + - PORT=3001 + - HOSTNAME=0.0.0.0 + # Add your environment variables here + # env_file: + # - .env + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3001/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Database (if you need one) + # postgres: + # image: postgres:15-alpine + # environment: + # POSTGRES_DB: nextplacement + # POSTGRES_USER: postgres + # POSTGRES_PASSWORD: password + # volumes: + # - postgres_data:/var/lib/postgresql/data + # ports: + # - "5432:5432" + # restart: unless-stopped + +# volumes: +# postgres_data: \ No newline at end of file