# 14. Deployment Architecture ## Docker Configuration ```dockerfile # docker/Dockerfile # Build stage FROM node:20-alpine AS builder WORKDIR /app # Install pnpm RUN corepack enable && corepack prepare pnpm@latest --activate # Copy package files COPY package.json pnpm-lock.yaml ./ RUN pnpm install --frozen-lockfile # Copy source and build COPY . . RUN pnpm prisma generate RUN pnpm build # Production stage FROM node:20-alpine AS runner WORKDIR /app # Install pnpm RUN corepack enable && corepack prepare pnpm@latest --activate # Create non-root user RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 app # Copy built application COPY --from=builder --chown=app:nodejs /app/.output ./.output COPY --from=builder --chown=app:nodejs /app/node_modules ./node_modules COPY --from=builder --chown=app:nodejs /app/package.json ./package.json COPY --from=builder --chown=app:nodejs /app/prisma ./prisma USER app EXPOSE 3000 ENV NODE_ENV=production ENV PORT=3000 CMD ["node", ".output/server/index.mjs"] ``` ## Docker Compose (Production) ```yaml # docker/docker-compose.yml services: app: build: context: .. dockerfile: docker/Dockerfile restart: unless-stopped environment: - DATABASE_URL=postgresql://postgres:${DB_PASSWORD}@postgres:5432/dofus_manager - SESSION_SECRET=${SESSION_SECRET} - NODE_ENV=production depends_on: postgres: condition: service_healthy networks: - internal - traefik labels: - "traefik.enable=true" - "traefik.http.routers.dofus.rule=Host(`dofus.example.com`)" - "traefik.http.routers.dofus.entrypoints=websecure" - "traefik.http.routers.dofus.tls.certresolver=letsencrypt" - "traefik.http.services.dofus.loadbalancer.server.port=3000" postgres: image: postgres:16-alpine restart: unless-stopped environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=dofus_manager volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U postgres"] interval: 5s timeout: 5s retries: 5 networks: - internal traefik: image: traefik:v3.0 restart: unless-stopped command: - "--api.dashboard=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" - "--certificatesresolvers.letsencrypt.acme.email=${ACME_EMAIL}" - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - letsencrypt:/letsencrypt networks: - traefik volumes: postgres_data: letsencrypt: networks: internal: traefik: external: true ``` ## GitLab CI/CD Pipeline ```yaml # .gitlab-ci.yml stages: - test - build - deploy variables: DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA # Test stage test: stage: test image: node:20-alpine before_script: - corepack enable - pnpm install --frozen-lockfile script: - pnpm lint - pnpm typecheck - pnpm test cache: key: ${CI_COMMIT_REF_SLUG} paths: - node_modules/ # Build stage build: stage: build image: docker:24 services: - docker:24-dind before_script: - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY script: - docker build -t $DOCKER_IMAGE -f docker/Dockerfile . - docker push $DOCKER_IMAGE - docker tag $DOCKER_IMAGE $CI_REGISTRY_IMAGE:latest - docker push $CI_REGISTRY_IMAGE:latest only: - main - develop # Deploy staging deploy_staging: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | ssh-add - - mkdir -p ~/.ssh - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts script: - ssh $STAGING_USER@$STAGING_HOST "cd /opt/dofus-manager && docker compose pull && docker compose up -d" environment: name: staging url: https://staging.dofus.example.com only: - develop # Deploy production deploy_production: stage: deploy image: alpine:latest before_script: - apk add --no-cache openssh-client - eval $(ssh-agent -s) - echo "$SSH_PRIVATE_KEY" | ssh-add - - mkdir -p ~/.ssh - echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts script: - ssh $PROD_USER@$PROD_HOST "cd /opt/dofus-manager && docker compose pull && docker compose up -d && docker compose exec app pnpm prisma migrate deploy" environment: name: production url: https://dofus.example.com only: - main when: manual ``` ---