Node.js / NestJS loyihasini Docker ga o'tkazish
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 generateproduction bosqichida ham - Migratsiyalar — alohida xizmat +
service_completed_successfully localhost→ xizmat nomi (baza)- Native modullar Alpine da —
bcryptjsyoki-slim NODE_OPTIONS=--max-old-space-size— konteyner xotirasiga mos
Keyingi dars
34-dars: PHP va Laravel — PHP-FPM, Nginx, Composer