216 lines
4.9 KiB
Markdown
216 lines
4.9 KiB
Markdown
# 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
|
|
```
|
|
|
|
---
|