diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 0000000..90d63c3 --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,241 @@ +name: Docker Build and Push + +on: + push: + branches: + - main # Production builds + - develop # Staging builds + tags: + - 'v*' # Version tags (v1.0.0, v1.2.3, etc.) + workflow_dispatch: # Manual trigger + +env: + REGISTRY: docker.io + REPO: xpeditis + +jobs: + # ================================================================ + # Determine Environment + # ================================================================ + prepare: + name: Prepare Build + runs-on: ubuntu-latest + outputs: + environment: ${{ steps.set-env.outputs.environment }} + backend_tag: ${{ steps.set-tags.outputs.backend_tag }} + frontend_tag: ${{ steps.set-tags.outputs.frontend_tag }} + should_push: ${{ steps.set-push.outputs.should_push }} + + steps: + - name: Determine environment + id: set-env + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" || "${{ github.ref }}" == refs/tags/v* ]]; then + echo "environment=production" >> $GITHUB_OUTPUT + else + echo "environment=staging" >> $GITHUB_OUTPUT + fi + + - name: Determine tags + id: set-tags + run: | + if [[ "${{ github.ref }}" == refs/tags/v* ]]; then + VERSION=${GITHUB_REF#refs/tags/v} + echo "backend_tag=${VERSION}" >> $GITHUB_OUTPUT + echo "frontend_tag=${VERSION}" >> $GITHUB_OUTPUT + elif [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "backend_tag=latest" >> $GITHUB_OUTPUT + echo "frontend_tag=latest" >> $GITHUB_OUTPUT + else + echo "backend_tag=staging-latest" >> $GITHUB_OUTPUT + echo "frontend_tag=staging-latest" >> $GITHUB_OUTPUT + fi + + - name: Determine push + id: set-push + run: | + # Push only on main, develop, or tags (not on PRs) + if [[ "${{ github.event_name }}" == "push" || "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "should_push=true" >> $GITHUB_OUTPUT + else + echo "should_push=false" >> $GITHUB_OUTPUT + fi + + # ================================================================ + # Build and Push Backend Image + # ================================================================ + build-backend: + name: Build Backend Docker Image + runs-on: ubuntu-latest + needs: prepare + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: needs.prepare.outputs.should_push == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.REPO }}/backend + tags: | + type=raw,value=${{ needs.prepare.outputs.backend_tag }} + type=raw,value=build-${{ github.run_number }} + type=sha,prefix={{branch}}- + + - name: Build and push Backend + uses: docker/build-push-action@v5 + with: + context: ./apps/backend + file: ./apps/backend/Dockerfile + platforms: linux/amd64 + push: ${{ needs.prepare.outputs.should_push == 'true' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NODE_ENV=${{ needs.prepare.outputs.environment }} + + - name: Image digest + run: echo "Backend image digest ${{ steps.build.outputs.digest }}" + + # ================================================================ + # Build and Push Frontend Image + # ================================================================ + build-frontend: + name: Build Frontend Docker Image + runs-on: ubuntu-latest + needs: prepare + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to Docker Hub + if: needs.prepare.outputs.should_push == 'true' + uses: docker/login-action@v3 + with: + registry: ${{ env.REGISTRY }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Set environment variables + id: env-vars + run: | + if [[ "${{ needs.prepare.outputs.environment }}" == "production" ]]; then + echo "api_url=https://api.xpeditis.com" >> $GITHUB_OUTPUT + echo "app_url=https://xpeditis.com" >> $GITHUB_OUTPUT + echo "sentry_env=production" >> $GITHUB_OUTPUT + else + echo "api_url=https://api-staging.xpeditis.com" >> $GITHUB_OUTPUT + echo "app_url=https://staging.xpeditis.com" >> $GITHUB_OUTPUT + echo "sentry_env=staging" >> $GITHUB_OUTPUT + fi + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.REGISTRY }}/${{ env.REPO }}/frontend + tags: | + type=raw,value=${{ needs.prepare.outputs.frontend_tag }} + type=raw,value=build-${{ github.run_number }} + type=sha,prefix={{branch}}- + + - name: Build and push Frontend + uses: docker/build-push-action@v5 + with: + context: ./apps/frontend + file: ./apps/frontend/Dockerfile + platforms: linux/amd64 + push: ${{ needs.prepare.outputs.should_push == 'true' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + build-args: | + NEXT_PUBLIC_API_URL=${{ steps.env-vars.outputs.api_url }} + NEXT_PUBLIC_APP_URL=${{ steps.env-vars.outputs.app_url }} + NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }} + NEXT_PUBLIC_SENTRY_ENVIRONMENT=${{ steps.env-vars.outputs.sentry_env }} + NEXT_PUBLIC_GA_MEASUREMENT_ID=${{ secrets.NEXT_PUBLIC_GA_MEASUREMENT_ID }} + + - name: Image digest + run: echo "Frontend image digest ${{ steps.build.outputs.digest }}" + + # ================================================================ + # Security Scan (optional but recommended) + # ================================================================ + security-scan: + name: Security Scan + runs-on: ubuntu-latest + needs: [build-backend, build-frontend, prepare] + if: needs.prepare.outputs.should_push == 'true' + + strategy: + matrix: + service: [backend, frontend] + + steps: + - name: Run Trivy vulnerability scanner + uses: aquasecurity/trivy-action@master + with: + image-ref: ${{ env.REGISTRY }}/${{ env.REPO }}/${{ matrix.service }}:${{ matrix.service == 'backend' && needs.prepare.outputs.backend_tag || needs.prepare.outputs.frontend_tag }} + format: 'sarif' + output: 'trivy-results-${{ matrix.service }}.sarif' + + - name: Upload Trivy results to GitHub Security + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: 'trivy-results-${{ matrix.service }}.sarif' + + # ================================================================ + # Summary + # ================================================================ + summary: + name: Build Summary + runs-on: ubuntu-latest + needs: [prepare, build-backend, build-frontend] + if: always() + + steps: + - name: Build summary + run: | + echo "## 🐳 Docker Build Summary" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Environment**: ${{ needs.prepare.outputs.environment }}" >> $GITHUB_STEP_SUMMARY + echo "**Branch**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY + echo "**Commit**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Images Built" >> $GITHUB_STEP_SUMMARY + echo "- Backend: \`${{ env.REGISTRY }}/${{ env.REPO }}/backend:${{ needs.prepare.outputs.backend_tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "- Frontend: \`${{ env.REGISTRY }}/${{ env.REPO }}/frontend:${{ needs.prepare.outputs.frontend_tag }}\`" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + + if [[ "${{ needs.prepare.outputs.should_push }}" == "true" ]]; then + echo "✅ Images pushed to Docker Hub" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "### Deploy with Portainer" >> $GITHUB_STEP_SUMMARY + echo "1. Login to Portainer UI" >> $GITHUB_STEP_SUMMARY + echo "2. Go to Stacks → Select \`xpeditis-${{ needs.prepare.outputs.environment }}\`" >> $GITHUB_STEP_SUMMARY + echo "3. Click \"Editor\"" >> $GITHUB_STEP_SUMMARY + echo "4. Update image tags if needed" >> $GITHUB_STEP_SUMMARY + echo "5. Click \"Update the stack\"" >> $GITHUB_STEP_SUMMARY + else + echo "ℹ️ Images built but not pushed (PR or dry-run)" >> $GITHUB_STEP_SUMMARY + fi diff --git a/apps/backend/.dockerignore b/apps/backend/.dockerignore new file mode 100644 index 0000000..9b89c86 --- /dev/null +++ b/apps/backend/.dockerignore @@ -0,0 +1,85 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Build output +dist +build +.next +out + +# Tests +coverage +.nyc_output +*.spec.ts +*.test.ts +**/__tests__ +**/__mocks__ +test +tests +e2e + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo +*.swn +.DS_Store + +# Git +.git +.gitignore +.gitattributes +.github + +# Documentation +*.md +docs +documentation + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Temporary files +tmp +temp +*.tmp +*.bak +*.cache + +# Docker +Dockerfile +.dockerignore +docker-compose*.yml + +# CI/CD +.gitlab-ci.yml +.travis.yml +Jenkinsfile +azure-pipelines.yml + +# Other +.prettierrc +.prettierignore +.eslintrc.js +.eslintignore +tsconfig.build.tsbuildinfo diff --git a/apps/backend/Dockerfile b/apps/backend/Dockerfile new file mode 100644 index 0000000..a529f14 --- /dev/null +++ b/apps/backend/Dockerfile @@ -0,0 +1,79 @@ +# =============================================== +# Stage 1: Dependencies Installation +# =============================================== +FROM node:20-alpine AS dependencies + +# Install build dependencies +RUN apk add --no-cache python3 make g++ libc6-compat + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ +COPY tsconfig*.json ./ + +# Install all dependencies (including dev for build) +RUN npm ci --legacy-peer-deps + +# =============================================== +# Stage 2: Build Application +# =============================================== +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy dependencies from previous stage +COPY --from=dependencies /app/node_modules ./node_modules + +# Copy source code +COPY . . + +# Build the application +RUN npm run build + +# Remove dev dependencies to reduce size +RUN npm prune --production --legacy-peer-deps + +# =============================================== +# Stage 3: Production Image +# =============================================== +FROM node:20-alpine AS production + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nestjs -u 1001 + +# Set working directory +WORKDIR /app + +# Copy built application from builder +COPY --from=builder --chown=nestjs:nodejs /app/dist ./dist +COPY --from=builder --chown=nestjs:nodejs /app/node_modules ./node_modules +COPY --from=builder --chown=nestjs:nodejs /app/package*.json ./ + +# Create logs directory +RUN mkdir -p /app/logs && chown -R nestjs:nodejs /app/logs + +# Switch to non-root user +USER nestjs + +# Expose port +EXPOSE 4000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD node -e "require('http').get('http://localhost:4000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))" + +# Set environment variables +ENV NODE_ENV=production \ + PORT=4000 + +# Use dumb-init to handle signals properly +ENTRYPOINT ["dumb-init", "--"] + +# Start the application +CMD ["node", "dist/main"] diff --git a/apps/frontend/.dockerignore b/apps/frontend/.dockerignore new file mode 100644 index 0000000..692bc6e --- /dev/null +++ b/apps/frontend/.dockerignore @@ -0,0 +1,99 @@ +# Dependencies +node_modules +npm-debug.log +yarn-error.log +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Next.js build output +.next +out +dist +build + +# Tests +coverage +.nyc_output +**/__tests__ +**/__mocks__ +*.spec.ts +*.test.ts +*.spec.tsx +*.test.tsx +e2e +playwright-report +test-results + +# Environment files +.env +.env.local +.env.development +.env.test +.env.production +.env.*.local + +# IDE +.vscode +.idea +*.swp +*.swo +*.swn +.DS_Store + +# Git +.git +.gitignore +.gitattributes +.github + +# Documentation +*.md +README.md +docs +documentation + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Temporary files +tmp +temp +*.tmp +*.bak +*.cache +.turbo + +# Docker +Dockerfile +.dockerignore +docker-compose*.yml + +# CI/CD +.gitlab-ci.yml +.travis.yml +Jenkinsfile +azure-pipelines.yml + +# Vercel +.vercel + +# Other +.prettierrc +.prettierignore +.eslintrc.json +.eslintignore +postcss.config.js +tailwind.config.js +next-env.d.ts +tsconfig.tsbuildinfo + +# Storybook +storybook-static +.storybook diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile new file mode 100644 index 0000000..6bfa4a6 --- /dev/null +++ b/apps/frontend/Dockerfile @@ -0,0 +1,87 @@ +# =============================================== +# Stage 1: Dependencies Installation +# =============================================== +FROM node:20-alpine AS dependencies + +# Install build dependencies +RUN apk add --no-cache libc6-compat + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install all dependencies (including dev for build) +RUN npm ci --legacy-peer-deps + +# =============================================== +# Stage 2: Build Application +# =============================================== +FROM node:20-alpine AS builder + +WORKDIR /app + +# Copy dependencies from previous stage +COPY --from=dependencies /app/node_modules ./node_modules + +# Copy source code +COPY . . + +# Set build-time environment variables +ARG NEXT_PUBLIC_API_URL +ARG NEXT_PUBLIC_APP_URL +ARG NEXT_PUBLIC_SENTRY_DSN +ARG NEXT_PUBLIC_SENTRY_ENVIRONMENT +ARG NEXT_PUBLIC_GA_MEASUREMENT_ID + +ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \ + NEXT_PUBLIC_APP_URL=$NEXT_PUBLIC_APP_URL \ + NEXT_PUBLIC_SENTRY_DSN=$NEXT_PUBLIC_SENTRY_DSN \ + NEXT_PUBLIC_SENTRY_ENVIRONMENT=$NEXT_PUBLIC_SENTRY_ENVIRONMENT \ + NEXT_PUBLIC_GA_MEASUREMENT_ID=$NEXT_PUBLIC_GA_MEASUREMENT_ID \ + NEXT_TELEMETRY_DISABLED=1 + +# Build the Next.js application +RUN npm run build + +# =============================================== +# Stage 3: Production Image +# =============================================== +FROM node:20-alpine AS production + +# Install dumb-init for proper signal handling +RUN apk add --no-cache dumb-init curl + +# Create non-root user +RUN addgroup -g 1001 -S nodejs && \ + adduser -S nextjs -u 1001 + +# Set working directory +WORKDIR /app + +# Copy built application from builder +COPY --from=builder --chown=nextjs:nodejs /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +# Switch to non-root user +USER nextjs + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \ + CMD curl -f http://localhost:3000/api/health || exit 1 + +# Set environment variables +ENV NODE_ENV=production \ + PORT=3000 \ + HOSTNAME="0.0.0.0" + +# Use dumb-init to handle signals properly +ENTRYPOINT ["dumb-init", "--"] + +# Start the Next.js application +CMD ["node", "server.js"] diff --git a/apps/frontend/next.config.js b/apps/frontend/next.config.js index 4ce64ec..1c2670c 100644 --- a/apps/frontend/next.config.js +++ b/apps/frontend/next.config.js @@ -2,6 +2,10 @@ const nextConfig = { reactStrictMode: true, swcMinify: true, + + // Standalone output for Docker (creates optimized server.js) + output: 'standalone', + experimental: { serverActions: { bodySizeLimit: '2mb', @@ -11,7 +15,14 @@ const nextConfig = { NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000', }, images: { - domains: ['localhost'], + domains: ['localhost', 'xpeditis.com', 'staging.xpeditis.com'], + // Allow S3 images in production + remotePatterns: [ + { + protocol: 'https', + hostname: '**.amazonaws.com', + }, + ], }, }; diff --git a/docker/DOCKER_BUILD_GUIDE.md b/docker/DOCKER_BUILD_GUIDE.md new file mode 100644 index 0000000..7603b43 --- /dev/null +++ b/docker/DOCKER_BUILD_GUIDE.md @@ -0,0 +1,444 @@ +# Guide de Construction des Images Docker - Xpeditis + +Ce guide explique comment construire les images Docker pour backend et frontend. + +--- + +## 📋 Prérequis + +### 1. Docker Installé +```bash +docker --version +# Docker version 24.0.0 ou supérieur +``` + +### 2. Docker Registry Access +- **Docker Hub**: Créer un compte sur https://hub.docker.com +- **Ou** GitHub Container Registry (GHCR) +- **Ou** Registry privé + +### 3. Login au Registry +```bash +# Docker Hub +docker login + +# GitHub Container Registry +echo $GITHUB_TOKEN | docker login ghcr.io -u USERNAME --password-stdin + +# Registry privé +docker login registry.example.com +``` + +--- + +## 🚀 Méthode 1: Script Automatique (Recommandé) + +### Build Staging + +```bash +# Build seulement (pas de push) +./docker/build-images.sh staging + +# Build ET push vers Docker Hub +./docker/build-images.sh staging --push +``` + +### Build Production + +```bash +# Build seulement +./docker/build-images.sh production + +# Build ET push +./docker/build-images.sh production --push +``` + +### Configuration du Registry + +Par défaut, le script utilise `docker.io/xpeditis` comme registry. + +Pour changer: +```bash +export DOCKER_REGISTRY=ghcr.io +export DOCKER_REPO=your-org +./docker/build-images.sh staging --push +``` + +--- + +## 🛠️ Méthode 2: Build Manuel + +### Backend Image + +```bash +cd apps/backend + +# Staging +docker build \ + --file Dockerfile \ + --tag xpeditis/backend:staging-latest \ + --platform linux/amd64 \ + . + +# Production +docker build \ + --file Dockerfile \ + --tag xpeditis/backend:latest \ + --platform linux/amd64 \ + . +``` + +### Frontend Image + +```bash +cd apps/frontend + +# Staging +docker build \ + --file Dockerfile \ + --tag xpeditis/frontend:staging-latest \ + --build-arg NEXT_PUBLIC_API_URL=https://api-staging.xpeditis.com \ + --build-arg NEXT_PUBLIC_APP_URL=https://staging.xpeditis.com \ + --build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=staging \ + --platform linux/amd64 \ + . + +# Production +docker build \ + --file Dockerfile \ + --tag xpeditis/frontend:latest \ + --build-arg NEXT_PUBLIC_API_URL=https://api.xpeditis.com \ + --build-arg NEXT_PUBLIC_APP_URL=https://xpeditis.com \ + --build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=production \ + --platform linux/amd64 \ + . +``` + +### Push Images + +```bash +# Backend +docker push xpeditis/backend:staging-latest +docker push xpeditis/backend:latest + +# Frontend +docker push xpeditis/frontend:staging-latest +docker push xpeditis/frontend:latest +``` + +--- + +## 🧪 Tester les Images Localement + +### 1. Créer un network Docker + +```bash +docker network create xpeditis-test +``` + +### 2. Lancer PostgreSQL + +```bash +docker run -d \ + --name postgres-test \ + --network xpeditis-test \ + -e POSTGRES_DB=xpeditis_test \ + -e POSTGRES_USER=xpeditis \ + -e POSTGRES_PASSWORD=test123 \ + -p 5432:5432 \ + postgres:15-alpine +``` + +### 3. Lancer Redis + +```bash +docker run -d \ + --name redis-test \ + --network xpeditis-test \ + -p 6379:6379 \ + redis:7-alpine \ + redis-server --requirepass test123 +``` + +### 4. Lancer Backend + +```bash +docker run -d \ + --name backend-test \ + --network xpeditis-test \ + -e NODE_ENV=development \ + -e PORT=4000 \ + -e DATABASE_HOST=postgres-test \ + -e DATABASE_PORT=5432 \ + -e DATABASE_NAME=xpeditis_test \ + -e DATABASE_USER=xpeditis \ + -e DATABASE_PASSWORD=test123 \ + -e REDIS_HOST=redis-test \ + -e REDIS_PORT=6379 \ + -e REDIS_PASSWORD=test123 \ + -e JWT_SECRET=test-secret-key-256-bits-minimum-length-required \ + -e CORS_ORIGIN=http://localhost:3000 \ + -p 4000:4000 \ + xpeditis/backend:staging-latest +``` + +### 5. Lancer Frontend + +```bash +docker run -d \ + --name frontend-test \ + --network xpeditis-test \ + -e NODE_ENV=development \ + -e NEXT_PUBLIC_API_URL=http://localhost:4000 \ + -e NEXT_PUBLIC_APP_URL=http://localhost:3000 \ + -e API_URL=http://backend-test:4000 \ + -p 3000:3000 \ + xpeditis/frontend:staging-latest +``` + +### 6. Vérifier + +```bash +# Backend health check +curl http://localhost:4000/health + +# Frontend +curl http://localhost:3000/api/health + +# Ouvrir dans navigateur +open http://localhost:3000 +``` + +### 7. Voir les logs + +```bash +docker logs -f backend-test +docker logs -f frontend-test +``` + +### 8. Nettoyer + +```bash +docker stop backend-test frontend-test postgres-test redis-test +docker rm backend-test frontend-test postgres-test redis-test +docker network rm xpeditis-test +``` + +--- + +## 📊 Optimisation des Images + +### Tailles d'Images Typiques + +- **Backend**: ~150-200 MB (après compression) +- **Frontend**: ~120-150 MB (après compression) +- **Total**: ~300 MB (pour les 2 images) + +### Multi-Stage Build + +Les Dockerfiles utilisent des builds multi-stage: + +1. **Stage Dependencies**: Installation des dépendances +2. **Stage Builder**: Compilation TypeScript/Next.js +3. **Stage Production**: Image finale (seulement le nécessaire) + +Avantages: +- ✅ Images légères (pas de dev dependencies) +- ✅ Build rapide (cache des layers) +- ✅ Sécurisé (pas de code source dans prod) + +### Build Cache + +Pour accélérer les builds: + +```bash +# Build avec cache +docker build --cache-from xpeditis/backend:staging-latest -t xpeditis/backend:staging-latest . + +# Ou avec BuildKit (plus rapide) +DOCKER_BUILDKIT=1 docker build -t xpeditis/backend:staging-latest . +``` + +### Scan de Vulnérabilités + +```bash +# Scan avec Docker Scout (gratuit) +docker scout cves xpeditis/backend:staging-latest + +# Scan avec Trivy +trivy image xpeditis/backend:staging-latest +``` + +--- + +## 🔄 CI/CD Integration + +### GitHub Actions Example + +Voir `.github/workflows/docker-build.yml` (à créer): + +```yaml +name: Build and Push Docker Images + +on: + push: + branches: + - main + - develop + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and Push + run: | + if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then + ./docker/build-images.sh production --push + else + ./docker/build-images.sh staging --push + fi +``` + +--- + +## 🐛 Dépannage + +### Problème 1: Build échoue avec erreur "npm ci" + +**Symptôme**: `npm ci` failed with exit code 1 + +**Solution**: +```bash +# Nettoyer le cache Docker +docker builder prune -a + +# Rebuild sans cache +docker build --no-cache -t xpeditis/backend:staging-latest apps/backend/ +``` + +### Problème 2: Image trop grosse (>500 MB) + +**Symptôme**: Image très volumineuse + +**Solution**: +- Vérifier que `.dockerignore` est présent +- Vérifier que `node_modules` n'est pas copié +- Utiliser `npm ci` au lieu de `npm install` + +```bash +# Analyser les layers +docker history xpeditis/backend:staging-latest +``` + +### Problème 3: Next.js standalone build échoue + +**Symptôme**: `Error: Cannot find module './standalone/server.js'` + +**Solution**: +- Vérifier que `next.config.js` a `output: 'standalone'` +- Rebuild frontend: +```bash +cd apps/frontend +npm run build +# Vérifier que .next/standalone existe +ls -la .next/standalone +``` + +### Problème 4: CORS errors en production + +**Symptôme**: Frontend ne peut pas appeler le backend + +**Solution**: +- Vérifier `CORS_ORIGIN` dans backend +- Vérifier `NEXT_PUBLIC_API_URL` dans frontend +- Tester avec curl: +```bash +curl -H "Origin: https://staging.xpeditis.com" \ + -H "Access-Control-Request-Method: GET" \ + -X OPTIONS \ + https://api-staging.xpeditis.com/health +``` + +### Problème 5: Health check fails + +**Symptôme**: Container restart en boucle + +**Solution**: +```bash +# Voir les logs +docker logs backend-test + +# Tester health check manuellement +docker exec backend-test curl -f http://localhost:4000/health + +# Si curl manque, installer: +docker exec backend-test apk add curl +``` + +--- + +## 📚 Ressources + +- **Dockerfile Best Practices**: https://docs.docker.com/develop/dev-best-practices/ +- **Next.js Docker**: https://nextjs.org/docs/deployment#docker-image +- **NestJS Docker**: https://docs.nestjs.com/recipes/docker +- **Docker Build Reference**: https://docs.docker.com/engine/reference/commandline/build/ + +--- + +## 🔐 Sécurité + +### Ne PAS Inclure dans les Images + +❌ Secrets (JWT_SECRET, API keys) +❌ Fichiers `.env` +❌ Code source TypeScript (seulement JS compilé) +❌ node_modules de dev +❌ Tests et mocks +❌ Documentation + +### Utiliser + +✅ Variables d'environnement au runtime +✅ Docker secrets (si Swarm) +✅ Kubernetes secrets (si K8s) +✅ AWS Secrets Manager / Vault +✅ Non-root user dans container +✅ Health checks +✅ Resource limits + +--- + +## 📈 Métriques de Build + +Après chaque build, vérifier: + +```bash +# Taille des images +docker images | grep xpeditis + +# Layers count +docker history xpeditis/backend:staging-latest | wc -l + +# Scan vulnérabilités +docker scout cves xpeditis/backend:staging-latest +``` + +**Objectifs**: +- ✅ Backend < 200 MB +- ✅ Frontend < 150 MB +- ✅ Build time < 5 min +- ✅ Zéro vulnérabilité critique + +--- + +*Dernière mise à jour*: 2025-10-14 +*Version*: 1.0.0 diff --git a/docker/build-images.sh b/docker/build-images.sh new file mode 100644 index 0000000..c36609b --- /dev/null +++ b/docker/build-images.sh @@ -0,0 +1,154 @@ +#!/bin/bash + +# ================================================================ +# Docker Image Build Script - Xpeditis +# ================================================================ +# This script builds and optionally pushes Docker images for +# backend and frontend to a Docker registry. +# +# Usage: +# ./build-images.sh [staging|production] [--push] +# +# Examples: +# ./build-images.sh staging # Build staging images only +# ./build-images.sh production --push # Build and push production images +# ================================================================ + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Default values +ENVIRONMENT=${1:-staging} +PUSH_IMAGES=${2:-} +REGISTRY=${DOCKER_REGISTRY:-docker.io} +REPO=${DOCKER_REPO:-xpeditis} + +# Validate environment +if [[ "$ENVIRONMENT" != "staging" && "$ENVIRONMENT" != "production" ]]; then + echo -e "${RED}Error: Environment must be 'staging' or 'production'${NC}" + echo "Usage: $0 [staging|production] [--push]" + exit 1 +fi + +# Set tags based on environment +if [[ "$ENVIRONMENT" == "staging" ]]; then + BACKEND_TAG="staging-latest" + FRONTEND_TAG="staging-latest" + API_URL="https://api-staging.xpeditis.com" + APP_URL="https://staging.xpeditis.com" + SENTRY_ENV="staging" +else + BACKEND_TAG="latest" + FRONTEND_TAG="latest" + API_URL="https://api.xpeditis.com" + APP_URL="https://xpeditis.com" + SENTRY_ENV="production" +fi + +echo -e "${BLUE}================================================${NC}" +echo -e "${BLUE} Building Xpeditis Docker Images${NC}" +echo -e "${BLUE}================================================${NC}" +echo -e "Environment: ${YELLOW}$ENVIRONMENT${NC}" +echo -e "Registry: ${YELLOW}$REGISTRY${NC}" +echo -e "Repository: ${YELLOW}$REPO${NC}" +echo -e "Backend Tag: ${YELLOW}$BACKEND_TAG${NC}" +echo -e "Frontend Tag: ${YELLOW}$FRONTEND_TAG${NC}" +echo -e "Push: ${YELLOW}${PUSH_IMAGES:-No}${NC}" +echo -e "${BLUE}================================================${NC}" +echo "" + +# Navigate to project root +cd "$(dirname "$0")/.." + +# ================================================================ +# Build Backend Image +# ================================================================ +echo -e "${GREEN}[1/2] Building Backend Image...${NC}" +echo "Image: $REGISTRY/$REPO/backend:$BACKEND_TAG" + +docker build \ + --file apps/backend/Dockerfile \ + --tag $REGISTRY/$REPO/backend:$BACKEND_TAG \ + --tag $REGISTRY/$REPO/backend:$(date +%Y%m%d-%H%M%S) \ + --build-arg NODE_ENV=$ENVIRONMENT \ + --platform linux/amd64 \ + apps/backend/ + +echo -e "${GREEN}✓ Backend image built successfully${NC}" +echo "" + +# ================================================================ +# Build Frontend Image +# ================================================================ +echo -e "${GREEN}[2/2] Building Frontend Image...${NC}" +echo "Image: $REGISTRY/$REPO/frontend:$FRONTEND_TAG" + +docker build \ + --file apps/frontend/Dockerfile \ + --tag $REGISTRY/$REPO/frontend:$FRONTEND_TAG \ + --tag $REGISTRY/$REPO/frontend:$(date +%Y%m%d-%H%M%S) \ + --build-arg NEXT_PUBLIC_API_URL=$API_URL \ + --build-arg NEXT_PUBLIC_APP_URL=$APP_URL \ + --build-arg NEXT_PUBLIC_SENTRY_ENVIRONMENT=$SENTRY_ENV \ + --platform linux/amd64 \ + apps/frontend/ + +echo -e "${GREEN}✓ Frontend image built successfully${NC}" +echo "" + +# ================================================================ +# Push Images (if --push flag provided) +# ================================================================ +if [[ "$PUSH_IMAGES" == "--push" ]]; then + echo -e "${BLUE}================================================${NC}" + echo -e "${BLUE} Pushing Images to Registry${NC}" + echo -e "${BLUE}================================================${NC}" + + echo -e "${YELLOW}Pushing backend image...${NC}" + docker push $REGISTRY/$REPO/backend:$BACKEND_TAG + + echo -e "${YELLOW}Pushing frontend image...${NC}" + docker push $REGISTRY/$REPO/frontend:$FRONTEND_TAG + + echo -e "${GREEN}✓ Images pushed successfully${NC}" + echo "" +fi + +# ================================================================ +# Summary +# ================================================================ +echo -e "${BLUE}================================================${NC}" +echo -e "${BLUE} Build Complete!${NC}" +echo -e "${BLUE}================================================${NC}" +echo "" +echo -e "Images built:" +echo -e " • Backend: ${GREEN}$REGISTRY/$REPO/backend:$BACKEND_TAG${NC}" +echo -e " • Frontend: ${GREEN}$REGISTRY/$REPO/frontend:$FRONTEND_TAG${NC}" +echo "" + +if [[ "$PUSH_IMAGES" != "--push" ]]; then + echo -e "${YELLOW}To push images to registry, run:${NC}" + echo -e " $0 $ENVIRONMENT --push" + echo "" +fi + +echo -e "To test images locally:" +echo -e " docker run -p 4000:4000 $REGISTRY/$REPO/backend:$BACKEND_TAG" +echo -e " docker run -p 3000:3000 $REGISTRY/$REPO/frontend:$FRONTEND_TAG" +echo "" + +echo -e "To deploy with Portainer:" +echo -e " 1. Login to Portainer UI" +echo -e " 2. Go to Stacks → Add Stack" +echo -e " 3. Use ${YELLOW}docker/portainer-stack-$ENVIRONMENT.yml${NC}" +echo -e " 4. Fill environment variables from ${YELLOW}docker/.env.$ENVIRONMENT.example${NC}" +echo -e " 5. Deploy!" +echo "" + +echo -e "${GREEN}✓ All done!${NC}"