3-bo'lim mashqlari — Node.js API uchun Dockerfile

~35 daq · Docker · Dockerfile — o'z image ingiz

Vazifa

Kichik TypeScript + Express API ni noldan konteynerlashtiramiz va bosqichma-bosqich yaxshilaymiz: avval "ishlaydigan", keyin "tez build", keyin "kichik va xavfsiz".


Tayyorgarlik: ilova

mkdir kitoblar-api && cd kitoblar-api
npm init -y
npm install express
npm install -D typescript @types/express @types/node tsx
npx tsc --init --outDir dist --rootDir src --module commonjs --target es2022 --esModuleInterop

package.json ga:

"scripts": {
  "dev": "tsx watch src/index.ts",
  "build": "tsc",
  "start": "node dist/index.js"
}

src/index.ts:

import express from 'express';

const app = express();
const PORT = Number(process.env.PORT ?? 3000);

const kitoblar = [
  { id: 1, nom: "O'tkan kunlar", muallif: 'Abdulla Qodiriy' },
  { id: 2, nom: 'Sariq devni minib', muallif: "Xudoyberdi To'xtaboyev" },
];

app.get('/health', (_req, res) => res.json({ holat: 'ok' }));
app.get('/kitoblar', (_req, res) => res.json(kitoblar));
app.get('/versiya', (_req, res) => res.json({ versiya: process.env.APP_VERSION ?? 'dev' }));

const server = app.listen(PORT, '0.0.0.0', () => console.log(`API ${PORT}-portda`));

// Graceful shutdown (13-dars)
process.on('SIGTERM', () => {
  console.log('SIGTERM — yopilmoqda');
  server.close(() => process.exit(0));
});

1-mashq: Ishlaydigan Dockerfile ⭐

Eng oddiy Dockerfile yozing: image yasalsin va curl localhost:3000/kitoblar ishlasin. Hajmini yozib qo'ying.

Yechim
FROM node:22
WORKDIR /app
COPY . .
RUN npm install
RUN npm run build
CMD ["npm", "start"]
docker build -t kitoblar:v1 .
docker run -d --name k1 -p 3000:3000 kitoblar:v1
curl localhost:3000/kitoblar
docker images kitoblar:v1      # ~1.2 GB

Ishlaydi. Endi muammolarini topamiz.


2-mashq: Muammolarni toping ⭐⭐

1-mashqdagi Dockerfile da kamida 5 ta muammoni toping (14-17 darslar).

Javob
  1. .dockerignore yo'q — kompyuterdagi node_modules va .git kontekstga va image ga tushadi
  2. COPY . . → RUN npm install — har kod o'zgarishida paketlar qaytadan o'rnatiladi (14-dars)
  3. npm install, npm ci emas — lock fayl kafolatlanmaydi
  4. Dev paketlar (typescript, tsx) va manba kodi production image da (16-dars)
  5. node:22 — to'liq Debian, 1 GB+ (17-dars)
  6. CMD ["npm", "start"] — PID 1 — npm, u SIGTERM ni Node ga ishonchli uzatmaydi; docker stop 10 soniya kutadi (13-dars)
  7. Root sifatida ishlaydi (13-dars)

Tekshirish — 6-band:

time docker stop k1        # ~10 soniya — "SIGTERM — yopilmoqda" logda yo'q
docker logs k1

3-mashq: To'g'ri Dockerfile ⭐⭐⭐

Barcha muammolarni tuzating:

  • .dockerignore
  • Multi-stage: build bosqichi va production bosqichi
  • Kesh uchun to'g'ri tartib
  • alpine, npm ci --omit=dev
  • USER node, EXPOSE, HEALTHCHECK
  • Exec shaklidagi CMD — node to'g'ridan-to'g'ri
  • APP_VERSION build argumenti orqali (/versiya endpointi uni qaytarsin)
.dockerignore
node_modules
dist
.git
.env*
!.env.example
Dockerfile*
.dockerignore
*.log
Dockerfile
# syntax=docker/dockerfile:1

# ===== build =====
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY tsconfig.json ./
COPY src ./src
RUN npm run build

# ===== production =====
FROM node:22-alpine
ARG APP_VERSION=dev
ENV NODE_ENV=production \
    PORT=3000 \
    APP_VERSION=${APP_VERSION}
WORKDIR /app

COPY --chown=node:node package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci --omit=dev
COPY --chown=node:node --from=build /app/dist ./dist

USER node
EXPOSE 3000

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget -qO- http://localhost:3000/health || exit 1

CMD ["node", "dist/index.js"]
Tekshirish
docker rm -f k1
docker build --build-arg APP_VERSION=1.0.0 -t kitoblar:1.0.0 .
docker images kitoblar          # v1 ~1.2 GB  →  1.0.0 ~150-170 MB

docker run -d --name k2 -p 3000:3000 kitoblar:1.0.0
curl localhost:3000/versiya     # {"versiya":"1.0.0"}

docker ps                       # bir oz kutib: (healthy)
docker exec k2 whoami           # node

time docker stop k2             # ~1 soniyadan kam
docker logs k2                  # "SIGTERM — yopilmoqda"

Kesh tekshiruvi — src/index.ts da bitta qatorni o'zgartirib qayta build:

 => CACHED [build 3/6] RUN npm ci
 => [build 5/6] COPY src ./src
 => [build 6/6] RUN npm run build
 => CACHED [stage-1 4/5] RUN npm ci --omit=dev

npm ci ikkala bosqichda ham keshdan.


4-mashq: Ishlab chiqish bosqichi ⭐⭐⭐

Xuddi shu Dockerfile ga dev bosqichini qo'shing: npm run dev (tsx watch) bilan ishlasin. --target dev bilan yasab, kod papkasini ulab ishga tushiring — fayl o'zgarganda server avtomatik qayta yuklansin.

Yechim

Dockerfile ga (build bosqichidan keyin):

# ===== dev =====
FROM node:22-alpine AS dev
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
CMD ["npm", "run", "dev"]
docker build --target dev -t kitoblar:dev .
docker run --rm -it -p 3000:3000 \
  -v "$(pwd)/src":/app/src \
  kitoblar:dev

src/index.ts ni o'zgartiring — terminalda tsx serverni qayta ishga tushiradi. Bu — bind mount (20-dars) bilan ishlab chiqish; 30-darsda Compose bilan qulayroq qilamiz.

Eslatma: dev bosqichi production bosqichidan oldin bo'lishi kerak, aks holda --target siz build oxirgi bosqich — dev ni yasaydi.


O'zingizni tekshiring

  • RUN va CMD farqi?
  • Nima uchun package.json kod dan oldin ko'chiriladi?
  • .dockerignore siz node_modules qanday muammolar keltiradi?
  • Multi-stage image hajmini qanday kamaytiradi?
  • Nima uchun CMD ["npm", "start"] emas, CMD ["node", "..."]?
  • ARG va ENV farqi?

Keyingi bo'lim

4-bo'lim: Ma'lumotlarni saqlash — volume, bind mount va konteynerdagi bazalar.