Node.js / NestJS loyihasini Docker ga o'tkazish

~20 daq · Docker · Real loyihalar

Muammodan boshlaylik

Mavjud NestJS loyihasi: TypeScript, Prisma, PostgreSQL, Redis, .env fayl. U yillar davomida dasturchilar kompyuterida va serverda npm run start:prod bilan ishlab kelgan. Endi uni Docker ga o'tkazish kerak — va hech narsa buzilmasligi kerak.

Bu dars — mavjud Node.js loyihasini konteynerlashtirish uchun amaliy yo'riqnoma, odatiy tuzoqlar bilan.


1. Tayyorgarlik: ilovani tekshirish

Konteyner uchun ilova quyidagi talablarga javob berishi kerak:

Talab Nima uchun Tekshirish
0.0.0.0 ni tinglash Konteyner tashqarisidan ko'rinishi uchun (9-dars) app.listen(port, '0.0.0.0')
Sozlamalar — muhit o'zgaruvchilaridan Bitta image — hamma muhit (10-dars) Kodda qattiq yozilgan manzillar yo'q
Loglar — konsolga docker logs ko'rishi uchun (8-dars) Fayl loggerlari o'chirilgan
SIGTERM ni qayta ishlash To'g'ri yopilish (13-dars) enableShutdownHooks()
/health endpoint Healthcheck uchun Baza va Redis ni tekshiradi
Fayllar lokal diskda emas Konteyner vaqtinchalik (4-dars) Yuklamalar — volume yoki S3

NestJS da:

// src/main.ts
async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.enableShutdownHooks();                    // SIGTERM → onModuleDestroy
  await app.listen(process.env.PORT ?? 3000, '0.0.0.0');
}

2. Dockerfile

# syntax=docker/dockerfile:1
ARG NODE_VERSION=22

# ===== asos =====
FROM node:${NODE_VERSION}-alpine AS base
WORKDIR /app
RUN apk add --no-cache openssl          # Prisma Alpine da openssl talab qiladi

# ===== bog'liqliklar =====
FROM base AS deps
COPY package.json package-lock.json ./
COPY prisma ./prisma
RUN --mount=type=cache,target=/root/.npm npm ci

# ===== dev =====
FROM deps AS dev
COPY . .
RUN npx prisma generate
CMD ["npm", "run", "start:dev"]

# ===== build =====
FROM deps AS build
COPY . .
RUN npx prisma generate && npm run build

# ===== production =====
FROM base AS production
ENV NODE_ENV=production
COPY package.json package-lock.json ./
COPY prisma ./prisma
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev \
 && npx prisma generate
COPY --from=build /app/dist ./dist
COPY docker-entrypoint.sh /usr/local/bin/
RUN chmod +x /usr/local/bin/docker-entrypoint.sh && chown -R node:node /app
USER node
EXPOSE 3000
HEALTHCHECK --interval=15s --timeout=3s --start-period=20s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1
ENTRYPOINT ["docker-entrypoint.sh"]
CMD ["node", "dist/main.js"]

prisma generate — production bosqichida ham kerak: Prisma Client node_modules ichida yaratiladi va --omit=dev dan keyingi toza node_modules da yo'q.

Migratsiyalar — entrypoint

#!/bin/sh
# docker-entrypoint.sh
set -e
if [ "$RUN_MIGRATIONS" = "true" ]; then
  echo "Migratsiyalar..."
  npx prisma migrate deploy
fi
exec "$@"

Yoki alohida Compose xizmati (28-dars) — bir nechta API nusxasi bo'lsa, ular bir vaqtda migratsiya qilmasligi uchun alohida xizmat yaxshiroq.


3. .dockerignore

node_modules
dist
coverage
.git
.env*
!.env.example
*.log
test
Dockerfile*
compose*.yaml

4. compose.yaml

name: dokon

services:
  api:
    build:
      context: .
      target: production
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD:?}@baza:5432/dokon?schema=public
      REDIS_URL: redis://kesh:6379
      JWT_SECRET: ${JWT_SECRET:?}
    depends_on:
      migrate:
        condition: service_completed_successfully
      kesh:
        condition: service_healthy

  migrate:
    build:
      context: .
      target: production
    command: ["npx", "prisma", "migrate", "deploy"]
    environment:
      DATABASE_URL: postgresql://app:${DB_PASSWORD}@baza:5432/dokon?schema=public
    depends_on:
      baza:
        condition: service_healthy
    restart: "no"

  baza:
    image: postgres:17-alpine
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: dokon
    volumes:
      - pg-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d dokon"]
      interval: 5s
      retries: 20

  kesh:
    image: redis:7-alpine
    restart: unless-stopped
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s

volumes:
  pg-data:
  redis-data:

.env dagi DATABASE_URL=...localhost... — konteynerda ishlamaydi: localhost → baza (xizmat nomi). Ko'p uchraydigan xato.


5. Odatiy muammolar

Error: Cannot find module '@prisma/client' / native modul xatosi — node_modules kontekstga tushgan (.dockerignore ni tekshiring) yoki prisma generate production bosqichida bajarilmagan.

bcrypt / sharp Alpine da o'rnatilmaydi — bcryptjs (sof JS) ga o'ting yoki node:22-slim asosidan foydalaning.

EACCES: permission denied — USER node va fayllar root ga tegishli: COPY --chown=node:node yoki chown -R node:node /app.

docker stop 10 soniya kutadi — CMD ["npm", ...] — node ni to'g'ridan-to'g'ri ishga tushiring; enableShutdownHooks().

Xotira tugaydi (exit 137) — Node standart heap chegarasi konteyner chegarasidan farq qilishi mumkin: NODE_OPTIONS=--max-old-space-size=384 (512 MB chegara uchun).

Vaqt mintaqasi — konteyner UTC da: TZ=Asia/Tashkent yoki (yaxshiroq) ilovada UTC bilan ishlang.


6. Sinov

docker compose up -d --build
docker compose ps                         # hammasi healthy, migrate — Exited (0)
docker compose logs migrate               # migratsiyalar qo'llandi
curl localhost:3000/health
docker compose exec api node -e "console.log(process.version)"
time docker compose stop api              # 1-2 soniya

Nima uchun shunday?

Nima uchun migratsiyalarni API ning o'zi ichida emas, alohida xizmatda bajarish tavsiya etiladi?

Chunki production da API ko'pincha bir nechta nusxada ishlaydi (ko'paytirish uchun). Agar har bir nusxa ishga tushganda migratsiya qilsa — ular bir vaqtda bir xil migratsiyani bajarishga urinadi: blokirovkalar, xatolar va ba'zan buzilgan sxema. Alohida xizmat — migratsiya bir marta bajariladi va API nusxalari faqat u muvaffaqiyatli tugagandan keyin ishga tushadi. Bu "yagona mas'uliyat" — har bir konteyner bitta ish qiladi.


Xulosa

Eslab qoling:

  • Tayyorgarlik: 0.0.0.0, env sozlamalar, konsol loglari, SIGTERM, /health
  • Multi-stage: deps → dev / build → production
  • Prisma: openssl (Alpine), prisma generate production bosqichida ham
  • Migratsiyalar — alohida xizmat + service_completed_successfully
  • localhost → xizmat nomi (baza)
  • Native modullar Alpine da — bcryptjs yoki -slim
  • NODE_OPTIONS=--max-old-space-size — konteyner xotirasiga mos

Keyingi dars

34-dars: PHP va Laravel — PHP-FPM, Nginx, Composer