From e395d60721c1784dd0316c67030c87945c29359a Mon Sep 17 00:00:00 2001 From: Hossam Mohsen <6911267+HossamMohsen7@users.noreply.github.com> Date: Wed, 27 Aug 2025 03:43:10 +0300 Subject: [PATCH 1/2] feat: add Docker configuration and setup for web, API, and recommendation services with .dockerignore files --- .dockerignore | 52 ++++++++ Makefile | 176 ++++++++++++++++++++++++++ apps/api/.dockerignore | 120 ++++++++++++++++++ apps/api/Dockerfile | 52 ++++++++ apps/api/start.sh | 13 ++ apps/recommendation-api/.dockerignore | 164 ++++++++++++++++++++++++ apps/recommendation-api/Dockerfile | 58 +++++++++ apps/web/.dockerignore | 117 +++++++++++++++++ apps/web/Dockerfile | 29 +++++ apps/web/nginx.conf | 39 ++++++ docker-compose.dev.yml | 96 ++++++++++++++ docker-compose.yml | 108 ++++++++++++++++ 12 files changed, 1024 insertions(+) create mode 100644 .dockerignore create mode 100644 Makefile create mode 100644 apps/api/.dockerignore create mode 100644 apps/api/Dockerfile create mode 100644 apps/api/start.sh create mode 100644 apps/recommendation-api/.dockerignore create mode 100644 apps/recommendation-api/Dockerfile create mode 100644 apps/web/.dockerignore create mode 100644 apps/web/Dockerfile create mode 100644 apps/web/nginx.conf create mode 100644 docker-compose.dev.yml create mode 100644 docker-compose.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eaa6e0f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,52 @@ +# Exclude everything by default +* + +# Include workspace files +!pnpm-lock.yaml +!pnpm-workspace.yaml +!package.json + +# Include web service +!apps/web/ + +# Include API service +!apps/api/ + +# Include recommendation API service +!apps/recommendation-api/ + +# Exclude node_modules and build artifacts +**/node_modules/ +**/dist/ +**/.vite/ +**/coverage/ +**/.nyc_output/ + +# But include generated Prisma client +!**/src/generated/ + +# Exclude logs and temporary files +*.log +*.tsbuildinfo +.cache/ +.temp/ + +# Exclude git and editor files +.git/ +.vscode/ +.idea/ +*.swp +*.swo + +# Exclude OS files +.DS_Store +Thumbs.db + +# Exclude docker files +docker-compose*.yml +Dockerfile* +.dockerignore +Makefile + +# But include startup scripts +!**/start.sh diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1bb0b51 --- /dev/null +++ b/Makefile @@ -0,0 +1,176 @@ +.PHONY: help dev prod build clean logs shell db-migrate db-reset test + +# Default target +help: + @echo "Available commands:" + @echo " dev - Start development environment" + @echo " prod - Start production environment" + @echo " build - Build all services" + @echo " build-dev - Build development environment" + @echo " build-prod - Build production environment" + @echo " clean - Stop and remove all containers and volumes" + @echo " logs - Show logs for all services" + @echo " logs-web - Show logs for web service" + @echo " logs-api - Show logs for API service" + @echo " logs-rec - Show logs for recommendation API service" + @echo " shell-web - Access web service shell" + @echo " shell-api - Access API service shell" + @echo " shell-rec - Access recommendation API service shell" + @echo " db-migrate - Run database migrations" + @echo " db-reset - Reset database (WARNING: This will delete all data)" + @echo " db-shell - Access database shell" + @echo " test - Run tests for all services" + @echo " setup - Initial setup (copy env file and build)" + +# Development environment +dev: + @echo "Starting development environment..." + docker-compose -f docker-compose.dev.yml up --build + +dev-detached: + @echo "Starting development environment in background..." + docker-compose -f docker-compose.dev.yml up -d --build + +# Production environment +prod: + @echo "Starting production environment..." + docker-compose up --build + +prod-detached: + @echo "Starting production environment in background..." + docker-compose up -d --build + +# Build commands +build: + @echo "Building all services..." + docker-compose build + +build-dev: + @echo "Building development environment..." + docker-compose -f docker-compose.dev.yml build + +build-prod: + @echo "Building production environment..." + docker-compose build + +# Cleanup +clean: + @echo "Stopping and removing all containers and volumes..." + docker-compose down -v + docker-compose -f docker-compose.dev.yml down -v + docker system prune -f + +# Logs +logs: + @echo "Showing logs for all services..." + docker-compose logs -f + +logs-web: + @echo "Showing logs for web service..." + docker-compose logs -f web + +logs-api: + @echo "Showing logs for API service..." + docker-compose logs -f api + +logs-rec: + @echo "Showing logs for recommendation API service..." + docker-compose logs -f recommendation-api + +# Shell access +shell-web: + @echo "Accessing web service shell..." + docker-compose exec web sh + +shell-api: + @echo "Accessing API service shell..." + docker-compose exec api sh + +shell-rec: + @echo "Accessing recommendation API service shell..." + docker-compose exec recommendation-api sh + +# Database operations +db-migrate: + @echo "Running database migrations..." + docker-compose exec api pnpm prisma migrate deploy + +db-generate: + @echo "Generating Prisma client..." + docker-compose exec api pnpm prisma generate + +db-reset: + @echo "WARNING: This will delete all database data!" + @read -p "Are you sure? (y/N): " confirm && [ "$$confirm" = "y" ] || exit 1 + docker-compose exec api pnpm prisma migrate reset --force + +db-shell: + @echo "Accessing database shell..." + docker-compose exec postgres psql -U postgres -d openlearn + +# Testing +test: + @echo "Running tests for all services..." + @echo "Testing web service..." + docker-compose exec web pnpm test + @echo "Testing API service..." + docker-compose exec api pnpm test + +# Setup +setup: + @echo "Setting up development environment..." + @if [ ! -f .env ]; then \ + cp env.example .env; \ + echo "Created .env file from env.example"; \ + else \ + echo ".env file already exists"; \ + fi + @echo "Building development environment..." + docker-compose -f docker-compose.dev.yml build + +# Health checks +health: + @echo "Checking service health..." + @echo "Web service:" + @curl -f http://localhost:5173/health || echo "Web service not responding" + @echo "API service:" + @curl -f http://localhost:3000/health || echo "API service not responding" + @echo "Recommendation API service:" + @curl -f http://localhost:8000/ || echo "Recommendation API service not responding" + +# Development shortcuts +restart-dev: + @echo "Restarting development environment..." + docker-compose -f docker-compose.dev.yml restart + +stop-dev: + @echo "Stopping development environment..." + docker-compose -f docker-compose.dev.yml down + +stop-prod: + @echo "Stopping production environment..." + docker-compose down + +# Monitoring +ps: + @echo "Container status:" + docker-compose ps + +ps-dev: + @echo "Development container status:" + docker-compose -f docker-compose.dev.yml ps + +# Resource usage +stats: + @echo "Container resource usage:" + docker stats --no-stream + +# Backup and restore +backup: + @echo "Creating database backup..." + docker-compose exec postgres pg_dump -U postgres openlearn > backup_$(shell date +%Y%m%d_%H%M%S).sql + +restore: + @echo "Restoring database from backup..." + @read -p "Enter backup file name: " backup_file && \ + docker-compose exec -T postgres psql -U postgres openlearn < $$backup_file diff --git a/apps/api/.dockerignore b/apps/api/.dockerignore new file mode 100644 index 0000000..9b73a09 --- /dev/null +++ b/apps/api/.dockerignore @@ -0,0 +1,120 @@ +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# 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 + +# dotenv environment variables file +.env +.env.test +.env.production +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Logs +logs +*.log + +# Editor directories and files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose.yml + +# Prisma +prisma/migrations/ diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile new file mode 100644 index 0000000..b3b37f4 --- /dev/null +++ b/apps/api/Dockerfile @@ -0,0 +1,52 @@ +FROM node:22-alpine AS base + +RUN npm install -g pnpm + +WORKDIR /app + +COPY apps/api/package.json ./ +COPY pnpm-lock.yaml ./ +COPY pnpm-workspace.yaml ./ + +RUN pnpm install --no-frozen-lockfile + +COPY apps/api/ . + +FROM base AS development +EXPOSE 3000 +CMD ["pnpm", "dev"] + +FROM base AS build +RUN pnpm prisma generate +RUN pnpm build + +FROM node:22-alpine AS production + +RUN npm install -g pnpm && \ + apk add --no-cache netcat-openbsd + +RUN addgroup -g 1001 -S nodejs +RUN adduser -S nodejs -u 1001 + +WORKDIR /app + +COPY apps/api/package.json ./ +COPY pnpm-lock.yaml ./ +COPY pnpm-workspace.yaml ./ + +RUN pnpm install --prod --no-frozen-lockfile + +COPY --from=build /app/dist ./dist + +COPY --from=build /app/prisma ./prisma +COPY --from=build /app/src/generated ./src/generated + +RUN chown -R nodejs:nodejs /app +USER nodejs + +EXPOSE 3000 + +COPY --from=build /app/start.sh ./start.sh +RUN chmod +x start.sh + +CMD ["./start.sh"] diff --git a/apps/api/start.sh b/apps/api/start.sh new file mode 100644 index 0000000..bd73531 --- /dev/null +++ b/apps/api/start.sh @@ -0,0 +1,13 @@ +#!/bin/sh + +echo "Waiting for database to be ready..." +while ! nc -z postgres 5432; do + sleep 1 +done +echo "Database is ready!" + +echo "Running Prisma migrations..." +pnpm prisma migrate deploy + +echo "Starting application..." +exec pnpm start diff --git a/apps/recommendation-api/.dockerignore b/apps/recommendation-api/.dockerignore new file mode 100644 index 0000000..b94e474 --- /dev/null +++ b/apps/recommendation-api/.dockerignore @@ -0,0 +1,164 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +Pipfile.lock + +# poetry +poetry.lock + +# pdm +.pdm.toml + +# PEP 582 +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +.idea/ + +# VS Code +.vscode/ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose.yml + +# Node.js (for the bin scripts) +node_modules/ +npm-debug.log* +package-lock.json + +# Virtual environment +.venv/ diff --git a/apps/recommendation-api/Dockerfile b/apps/recommendation-api/Dockerfile new file mode 100644 index 0000000..23a1bcf --- /dev/null +++ b/apps/recommendation-api/Dockerfile @@ -0,0 +1,58 @@ +# Multi-stage build for Python/FastAPI recommendation service +FROM python:3.11-slim AS base + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app + +# Install system dependencies +RUN apt-get update && apt-get install -y \ + gcc \ + g++ \ + && rm -rf /var/lib/apt/lists/* + +# Set working directory +WORKDIR /app + +# Copy requirements for dependency caching +COPY requirements.txt . + +# Install Python dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Copy source code +COPY . . + +# Development stage +FROM base AS development +EXPOSE 8000 +CMD ["uvicorn", "recommender:app", "--host", "0.0.0.0", "--port", "8000", "--reload"] + +# Production stage +FROM python:3.11-slim AS production + +# Set environment variables +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app + +# Create app user +RUN groupadd -r appuser && useradd -r -g appuser appuser + +# Set working directory +WORKDIR /app + +# Copy requirements and install dependencies +COPY requirements.txt . +RUN pip install --no-cache-dir -r requirements.txt + +# Copy application code +COPY . . + +# Change ownership to appuser +RUN chown -R appuser:appuser /app +USER appuser + +EXPOSE 8000 +CMD ["uvicorn", "recommender:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/apps/web/.dockerignore b/apps/web/.dockerignore new file mode 100644 index 0000000..b6634d7 --- /dev/null +++ b/apps/web/.dockerignore @@ -0,0 +1,117 @@ +node_modules +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript cache +*.tsbuildinfo + +# 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 + +# dotenv environment variables file +.env +.env.test +.env.production +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ + +# Logs +logs +*.log + +# Editor directories and files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Git +.git +.gitignore + +# Docker +Dockerfile +.dockerignore +docker-compose.yml diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..01172ee --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,29 @@ +FROM node:22-alpine AS base + +RUN npm install -g pnpm + +WORKDIR /app + +COPY apps/web/package.json ./ +COPY pnpm-lock.yaml ./ +COPY pnpm-workspace.yaml ./ + +RUN pnpm install --no-frozen-lockfile + +COPY apps/web/ . + +FROM base AS development +EXPOSE 5173 +CMD ["pnpm", "dev", "--host", "0.0.0.0"] + +FROM base AS build +RUN pnpm build + +FROM nginx:alpine AS production + +COPY --from=build /app/dist /usr/share/nginx/html + +COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf + +EXPOSE 5173 +CMD ["nginx", "-g", "daemon off;"] diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf new file mode 100644 index 0000000..47f73ff --- /dev/null +++ b/apps/web/nginx.conf @@ -0,0 +1,39 @@ +server { + listen 5173; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Handle static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # Handle React Router (SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } +} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml new file mode 100644 index 0000000..9276968 --- /dev/null +++ b/docker-compose.dev.yml @@ -0,0 +1,96 @@ +services: + web: + build: + context: . + dockerfile: apps/web/Dockerfile + target: development + ports: + - "5173:5173" + environment: + - NODE_ENV=development + - VITE_API_URL=http://localhost:3000 + - VITE_RECOMMENDATION_API_URL=http://localhost:8000 + volumes: + - ./apps/web:/app + - /app/node_modules + networks: + - openlearn-network + restart: unless-stopped + + api: + build: + context: . + dockerfile: apps/api/Dockerfile + target: development + ports: + - "3000:3000" + environment: + - NODE_ENV=development + - DATABASE_URL=${DATABASE_URL:-postgresql://postgres:password@postgres:5432/openlearn} + - JWT_SECRET=${JWT_SECRET:-dev-secret-key} + - ADMIN_EMAIL=${ADMIN_EMAIL:-admin@example.com} + - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123} + volumes: + - ./apps/api:/app + - /app/node_modules + depends_on: + postgres: + condition: service_healthy + networks: + - openlearn-network + restart: unless-stopped + + recommendation-api: + build: + context: ./apps/recommendation-api + dockerfile: Dockerfile + target: development + ports: + - "8000:8000" + environment: + - PYTHONPATH=/app + - HOST=0.0.0.0 + - PORT=8000 + - DEBUG=true + volumes: + - ./apps/recommendation-api:/app + networks: + - openlearn-network + restart: unless-stopped + + postgres: + image: postgres:15-alpine + ports: + - "5432" + environment: + - POSTGRES_DB=${POSTGRES_DB:-openlearn} + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - openlearn-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + ports: + - "6379" + volumes: + - redis-data:/data + networks: + - openlearn-network + restart: unless-stopped + +networks: + openlearn-network: + driver: bridge + +volumes: + postgres-data: + redis-data: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..811ccee --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,108 @@ +services: + web: + build: + context: . + dockerfile: apps/web/Dockerfile + cache_from: + - openlearnplatform-web:cache + target: production + ports: + - "20251:5173" + environment: + - NODE_ENV=production + - VITE_API_URL=http://api:3000 + - VITE_RECOMMENDATION_API_URL=http://recommendation-api:8000 + depends_on: + - api + - recommendation-api + networks: + - openlearn-network + volumes: + - web-node-modules:/app/node_modules + restart: unless-stopped + + api: + build: + context: . + dockerfile: apps/api/Dockerfile + cache_from: + - openlearnplatform-api:cache + target: production + ports: + - "20252:3000" + environment: + - NODE_ENV=production + - DATABASE_URL=${DATABASE_URL} + - JWT_SECRET=${JWT_SECRET} + - ADMIN_EMAIL=${ADMIN_EMAIL} + - ADMIN_PASSWORD=${ADMIN_PASSWORD} + depends_on: + postgres: + condition: service_healthy + networks: + - openlearn-network + volumes: + - api-node-modules:/app/node_modules + - api-dist:/app/dist + restart: unless-stopped + + recommendation-api: + build: + context: ./apps/recommendation-api + dockerfile: Dockerfile + cache_from: + - openlearnplatform-recommendation-api:cache + target: production + ports: + - "20253:8000" + environment: + - PYTHONPATH=/app + - HOST=0.0.0.0 + - PORT=8000 + - DEBUG=false + networks: + - openlearn-network + volumes: + - recommendation-api-venv:/app/.venv + restart: unless-stopped + + postgres: + image: postgres:15-alpine + ports: + - "5432" + environment: + - POSTGRES_DB=${POSTGRES_DB:-openlearn} + - POSTGRES_USER=${POSTGRES_USER:-postgres} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-password} + volumes: + - postgres-data:/var/lib/postgresql/data + networks: + - openlearn-network + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + ports: + - "6379" + volumes: + - redis-data:/data + networks: + - openlearn-network + restart: unless-stopped + +networks: + openlearn-network: + driver: bridge + +volumes: + postgres-data: + redis-data: + web-node-modules: + api-node-modules: + api-dist: + recommendation-api-venv: From 91f9ff099b22922024fd69e47f19852c655f1c8e Mon Sep 17 00:00:00 2001 From: Hossam Mohsen <6911267+HossamMohsen7@users.noreply.github.com> Date: Thu, 28 Aug 2025 03:42:02 +0300 Subject: [PATCH 2/2] fix(Dockerfile): copy node_modules and update start.sh permissions in API Docker configuration --- apps/api/Dockerfile | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile index b3b37f4..a44ab6d 100644 --- a/apps/api/Dockerfile +++ b/apps/api/Dockerfile @@ -41,12 +41,15 @@ COPY --from=build /app/dist ./dist COPY --from=build /app/prisma ./prisma COPY --from=build /app/src/generated ./src/generated +# Copy node_modules from build stage to get Prisma CLI +COPY --from=build /app/node_modules ./node_modules + +COPY --from=build /app/start.sh ./start.sh +RUN chmod +x start.sh + RUN chown -R nodejs:nodejs /app USER nodejs EXPOSE 3000 -COPY --from=build /app/start.sh ./start.sh -RUN chmod +x start.sh - CMD ["./start.sh"]