Initial commit: MOPC platform with Docker deployment setup
Full Next.js 15 platform with tRPC, Prisma, PostgreSQL, NextAuth. Includes production Dockerfile (multi-stage, port 7600), docker-compose with registry-based image pull, Gitea Actions CI workflow, nginx config for portal.monaco-opc.com, deployment scripts, and DEPLOYMENT.md guide. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
60
docker/.env.production
Normal file
60
docker/.env.production
Normal file
@@ -0,0 +1,60 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Production Environment Variables
|
||||
# =============================================================================
|
||||
# Copy this file to docker/.env and fill in real values:
|
||||
# cp docker/.env.production docker/.env
|
||||
#
|
||||
# Generate secrets with:
|
||||
# openssl rand -base64 32
|
||||
|
||||
# =============================================================================
|
||||
# DATABASE
|
||||
# =============================================================================
|
||||
DB_PASSWORD=CHANGE_ME_use_openssl_rand
|
||||
|
||||
# =============================================================================
|
||||
# AUTHENTICATION (NextAuth.js / Auth.js)
|
||||
# =============================================================================
|
||||
NEXTAUTH_URL=https://portal.monaco-opc.com
|
||||
NEXTAUTH_SECRET=CHANGE_ME_use_openssl_rand
|
||||
|
||||
# =============================================================================
|
||||
# FILE STORAGE (MinIO - external stack)
|
||||
# =============================================================================
|
||||
# Internal endpoint (server-to-server, within Docker host)
|
||||
MINIO_ENDPOINT=http://localhost:9000
|
||||
|
||||
# Public endpoint for browser-accessible pre-signed URLs
|
||||
# Set this when MinIO is behind a reverse proxy
|
||||
# MINIO_PUBLIC_ENDPOINT=https://storage.monaco-opc.com
|
||||
|
||||
MINIO_ACCESS_KEY=CHANGE_ME
|
||||
MINIO_SECRET_KEY=CHANGE_ME
|
||||
MINIO_BUCKET=mopc-files
|
||||
|
||||
# =============================================================================
|
||||
# EMAIL (SMTP via Poste.io - external stack)
|
||||
# =============================================================================
|
||||
SMTP_HOST=localhost
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=noreply@monaco-opc.com
|
||||
SMTP_PASS=CHANGE_ME
|
||||
EMAIL_FROM=MOPC Platform <noreply@monaco-opc.com>
|
||||
|
||||
# =============================================================================
|
||||
# AI (OpenAI - optional)
|
||||
# =============================================================================
|
||||
OPENAI_API_KEY=
|
||||
OPENAI_MODEL=gpt-4o
|
||||
|
||||
# =============================================================================
|
||||
# DOCKER REGISTRY (Gitea container registry)
|
||||
# =============================================================================
|
||||
# The Gitea registry URL where the CI pushes built images
|
||||
# Example: gitea.example.com/your-org
|
||||
REGISTRY_URL=code.letsbe.solutions/letsbe
|
||||
|
||||
# =============================================================================
|
||||
# APPLICATION
|
||||
# =============================================================================
|
||||
MAX_FILE_SIZE=524288000
|
||||
71
docker/Dockerfile
Normal file
71
docker/Dockerfile
Normal file
@@ -0,0 +1,71 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Production Dockerfile
|
||||
# =============================================================================
|
||||
# Multi-stage build for optimized production image
|
||||
|
||||
FROM node:22-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Copy package files
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm ci
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Generate Prisma client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Build Next.js
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV=production
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
|
||||
# Create non-root user for security
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# Install runtime dependencies for migrations and seeding
|
||||
RUN apk add --no-cache libc6-compat
|
||||
|
||||
# Copy built files
|
||||
COPY --from=builder /app/public ./public
|
||||
COPY --from=builder /app/.next/standalone ./
|
||||
COPY --from=builder /app/.next/static ./.next/static
|
||||
COPY --from=builder /app/prisma ./prisma
|
||||
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||
COPY --from=builder /app/node_modules/@prisma ./node_modules/@prisma
|
||||
COPY --from=builder /app/node_modules/prisma ./node_modules/prisma
|
||||
|
||||
# Copy CSV data file for manual seeding
|
||||
COPY --from=builder /app/docs/candidatures_2026.csv ./docs/candidatures_2026.csv
|
||||
|
||||
# Copy entrypoint script
|
||||
COPY docker/docker-entrypoint.sh /app/docker-entrypoint.sh
|
||||
RUN chmod +x /app/docker-entrypoint.sh
|
||||
|
||||
# Set correct permissions
|
||||
RUN chown -R nextjs:nodejs /app
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 7600
|
||||
|
||||
ENV PORT=7600
|
||||
ENV HOSTNAME="0.0.0.0"
|
||||
|
||||
# Run via entrypoint (migrate then start)
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
||||
34
docker/Dockerfile.dev
Normal file
34
docker/Dockerfile.dev
Normal file
@@ -0,0 +1,34 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Development Dockerfile (Pre-built)
|
||||
# =============================================================================
|
||||
|
||||
FROM node:22-alpine
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies for Prisma and development
|
||||
RUN apk add --no-cache libc6-compat openssl
|
||||
|
||||
# Copy package files
|
||||
COPY package.json package-lock.json* ./
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install && npm install tailwindcss-animate
|
||||
|
||||
# Copy prisma schema for generation
|
||||
COPY prisma ./prisma
|
||||
|
||||
# Generate Prisma client
|
||||
RUN npx prisma generate
|
||||
|
||||
# Copy the rest of the application
|
||||
COPY . .
|
||||
|
||||
# Build the application at build time (pre-compile everything)
|
||||
RUN npm run build
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Start production server (uses pre-built files)
|
||||
CMD ["npm", "start"]
|
||||
97
docker/docker-compose.dev.yml
Normal file
97
docker/docker-compose.dev.yml
Normal file
@@ -0,0 +1,97 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Development Docker Compose
|
||||
# =============================================================================
|
||||
# Use this for local development. Includes PostgreSQL, MinIO, and the app.
|
||||
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: mopc-postgres-dev
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
- POSTGRES_USER=${POSTGRES_USER:-mopc}
|
||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-devpassword}
|
||||
- POSTGRES_DB=${POSTGRES_DB:-mopc}
|
||||
volumes:
|
||||
- postgres_dev_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mopc"]
|
||||
interval: 5s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
|
||||
minio:
|
||||
image: minio/minio
|
||||
container_name: mopc-minio-dev
|
||||
ports:
|
||||
- "9000:9000"
|
||||
- "9001:9001"
|
||||
environment:
|
||||
- MINIO_ROOT_USER=${MINIO_ACCESS_KEY:-minioadmin}
|
||||
- MINIO_ROOT_PASSWORD=${MINIO_SECRET_KEY:-minioadmin}
|
||||
volumes:
|
||||
- minio_dev_data:/data
|
||||
command: server /data --console-address ":9001"
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
|
||||
interval: 30s
|
||||
timeout: 20s
|
||||
retries: 3
|
||||
|
||||
# MinIO client to create default bucket on startup
|
||||
createbuckets:
|
||||
image: minio/mc
|
||||
depends_on:
|
||||
minio:
|
||||
condition: service_healthy
|
||||
environment:
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
|
||||
- MINIO_BUCKET=${MINIO_BUCKET:-mopc-files}
|
||||
entrypoint: >
|
||||
/bin/sh -c "
|
||||
mc alias set myminio http://minio:9000 $${MINIO_ACCESS_KEY} $${MINIO_SECRET_KEY};
|
||||
mc mb --ignore-existing myminio/$${MINIO_BUCKET};
|
||||
mc anonymous set download myminio/$${MINIO_BUCKET};
|
||||
echo 'Bucket created successfully';
|
||||
"
|
||||
|
||||
# Next.js application
|
||||
app:
|
||||
build:
|
||||
context: ..
|
||||
dockerfile: docker/Dockerfile.dev
|
||||
container_name: mopc-app-dev
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- ../.env
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://${POSTGRES_USER:-mopc}:${POSTGRES_PASSWORD:-devpassword}@postgres:5432/${POSTGRES_DB:-mopc}
|
||||
- NEXTAUTH_URL=${NEXTAUTH_URL:-http://localhost:3000}
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET:-dev-secret-key-for-local-development-only}
|
||||
- AUTH_SECRET=${AUTH_SECRET:-dev-secret-key-for-local-development-only}
|
||||
- MINIO_ENDPOINT=http://minio:9000
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY:-minioadmin}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY:-minioadmin}
|
||||
- MINIO_BUCKET=${MINIO_BUCKET:-mopc-files}
|
||||
- NODE_ENV=development
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_PORT=${SMTP_PORT:-587}
|
||||
- SMTP_USER=${SMTP_USER}
|
||||
- SMTP_PASS=${SMTP_PASS}
|
||||
- EMAIL_FROM=${EMAIL_FROM}
|
||||
volumes:
|
||||
- ../src:/app/src
|
||||
- ../public:/app/public
|
||||
- ../prisma:/app/prisma
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
minio:
|
||||
condition: service_healthy
|
||||
|
||||
volumes:
|
||||
postgres_dev_data:
|
||||
minio_dev_data:
|
||||
75
docker/docker-compose.yml
Normal file
75
docker/docker-compose.yml
Normal file
@@ -0,0 +1,75 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Production Docker Compose
|
||||
# =============================================================================
|
||||
# This stack contains only the Next.js app and PostgreSQL.
|
||||
# MinIO and Poste.io are external services connected via environment variables.
|
||||
#
|
||||
# The app image is built by Gitea CI and pushed to the container registry.
|
||||
# To pull the latest image: docker compose pull app
|
||||
# To deploy: docker compose up -d
|
||||
|
||||
services:
|
||||
app:
|
||||
image: ${REGISTRY_URL}/mopc-app:latest
|
||||
container_name: mopc-app
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:7600:7600"
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- DATABASE_URL=postgresql://mopc:${DB_PASSWORD}@postgres:5432/mopc
|
||||
- NEXTAUTH_URL=${NEXTAUTH_URL}
|
||||
- NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
- AUTH_SECRET=${NEXTAUTH_SECRET}
|
||||
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
|
||||
- MINIO_PUBLIC_ENDPOINT=${MINIO_PUBLIC_ENDPOINT:-}
|
||||
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
|
||||
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
- MINIO_BUCKET=${MINIO_BUCKET}
|
||||
- SMTP_HOST=${SMTP_HOST}
|
||||
- SMTP_PORT=${SMTP_PORT}
|
||||
- SMTP_USER=${SMTP_USER}
|
||||
- SMTP_PASS=${SMTP_PASS}
|
||||
- EMAIL_FROM=${EMAIL_FROM}
|
||||
- OPENAI_API_KEY=${OPENAI_API_KEY:-}
|
||||
- OPENAI_MODEL=${OPENAI_MODEL:-gpt-4o}
|
||||
- MAX_FILE_SIZE=${MAX_FILE_SIZE:-524288000}
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- mopc-network
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost:7600/api/health"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
start_period: 40s
|
||||
|
||||
postgres:
|
||||
image: postgres:16-alpine
|
||||
container_name: mopc-postgres
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- POSTGRES_USER=mopc
|
||||
- POSTGRES_PASSWORD=${DB_PASSWORD}
|
||||
- POSTGRES_DB=mopc
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U mopc"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- mopc-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
mopc-network:
|
||||
driver: bridge
|
||||
8
docker/docker-entrypoint.sh
Normal file
8
docker/docker-entrypoint.sh
Normal file
@@ -0,0 +1,8 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
echo "==> Running database migrations..."
|
||||
npx prisma migrate deploy
|
||||
|
||||
echo "==> Starting application..."
|
||||
exec node server.js
|
||||
78
docker/nginx/mopc-platform.conf
Normal file
78
docker/nginx/mopc-platform.conf
Normal file
@@ -0,0 +1,78 @@
|
||||
# =============================================================================
|
||||
# MOPC Platform - Nginx Reverse Proxy Configuration
|
||||
# =============================================================================
|
||||
# Install: sudo ln -s /opt/mopc/docker/nginx/mopc-platform.conf /etc/nginx/sites-enabled/
|
||||
# Test: sudo nginx -t
|
||||
# Reload: sudo systemctl reload nginx
|
||||
|
||||
# Rate limiting zone
|
||||
limit_req_zone $binary_remote_addr zone=mopc_limit:10m rate=10r/s;
|
||||
|
||||
# MOPC Platform - HTTPS
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
server_name portal.monaco-opc.com;
|
||||
|
||||
# SSL certificates (managed by Certbot)
|
||||
ssl_certificate /etc/letsencrypt/live/portal.monaco-opc.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/portal.monaco-opc.com/privkey.pem;
|
||||
include /etc/letsencrypt/options-ssl-nginx.conf;
|
||||
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
|
||||
|
||||
# Security headers
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
add_header X-Content-Type-Options "nosniff" always;
|
||||
add_header X-XSS-Protection "1; mode=block" always;
|
||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
||||
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self';" always;
|
||||
|
||||
# File upload size (500MB for videos)
|
||||
client_max_body_size 500M;
|
||||
|
||||
# Rate limiting
|
||||
limit_req zone=mopc_limit burst=20 nodelay;
|
||||
|
||||
# Logging
|
||||
access_log /var/log/nginx/mopc-access.log;
|
||||
error_log /var/log/nginx/mopc-error.log;
|
||||
|
||||
# Next.js application
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:7600;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade $http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
proxy_cache_bypass $http_upgrade;
|
||||
|
||||
# Timeouts for large file uploads
|
||||
proxy_connect_timeout 300s;
|
||||
proxy_send_timeout 300s;
|
||||
proxy_read_timeout 300s;
|
||||
}
|
||||
|
||||
# Static files caching
|
||||
location /_next/static {
|
||||
proxy_pass http://127.0.0.1:7600;
|
||||
proxy_cache_valid 200 365d;
|
||||
add_header Cache-Control "public, max-age=31536000, immutable";
|
||||
}
|
||||
|
||||
# Health check endpoint (no access log noise)
|
||||
location /api/health {
|
||||
proxy_pass http://127.0.0.1:7600;
|
||||
access_log off;
|
||||
}
|
||||
}
|
||||
|
||||
# HTTP to HTTPS redirect
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
server_name portal.monaco-opc.com;
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
Reference in New Issue
Block a user