Docker / Эцсийн төсөл

Эцсийн төсөл

Курсын туршид үзсэн бүх зүйлийг нэг дор ашиглана — Node.js апп + PostgreSQL + Nginx stack-ийг Docker Compose-оор ажиллуулах бүрэн төсөл.

Төслийн бүтэц

код
final-project/
├── docker-compose.yml
├── .env
├── .env.example
├── nginx/
│   └── nginx.conf
├── app/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── package.json
│   ├── tsconfig.json
│   └── src/
│       └── index.ts
└── db/
    └── init.sql

Апп бэлдэх

app/src/index.ts

typescript
import express from "express";
import { Pool } from "pg";

const app = express();
app.use(express.json());

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

app.get("/health", (_req, res) => {
  res.json({ status: "ok", timestamp: new Date().toISOString() });
});

app.get("/users", async (_req, res) => {
  try {
    const result = await pool.query(
      "SELECT id, username, created_at FROM users ORDER BY created_at DESC",
    );
    res.json(result.rows);
  } catch (err) {
    res
      .status(500)
      .json({ error: "Өгөгдлийн сантай холбогдоход алдаа гарлаа" });
  }
});

app.post("/users", async (req, res) => {
  const { username } = req.body as { username: string };
  if (!username) {
    res.status(400).json({ error: "username шаардлагатай" });
    return;
  }
  try {
    const result = await pool.query(
      "INSERT INTO users (username) VALUES ($1) RETURNING id, username, created_at",
      [username],
    );
    res.status(201).json(result.rows[0]);
  } catch (err) {
    res.status(500).json({ error: "Хэрэглэгч нэмэхэд алдаа гарлаа" });
  }
});

const PORT = Number(process.env.PORT ?? 3000);
app.listen(PORT, () => {
  console.log(`Сервер ${PORT} порт дээр ажиллаж байна`);
});

app/package.json

json
{
  "name": "final-project-app",
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "ts-node-dev src/index.ts"
  },
  "dependencies": {
    "express": "^4.18.2",
    "pg": "^8.11.3"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/pg": "^8.11.0",
    "typescript": "^5.3.3",
    "ts-node-dev": "^2.0.0"
  }
}

app/Dockerfile

dockerfile
# ── Stage 1: builder ──────────────────────────
FROM node:20-alpine AS builder

WORKDIR /app

COPY package*.json ./
RUN npm ci

COPY tsconfig.json ./
COPY src/ ./src/
RUN npm run build

# ── Stage 2: production ───────────────────────
FROM node:20-alpine AS production

RUN addgroup -S appgroup && adduser -S appuser -G appgroup

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production && npm cache clean --force

COPY --from=builder /app/dist ./dist
RUN chown -R appuser:appgroup /app

USER appuser

EXPOSE 3000

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

CMD ["node", "dist/index.js"]

app/.dockerignore

код
node_modules
dist
.env
*.md

db/init.sql

sql
CREATE TABLE IF NOT EXISTS users (
  id         SERIAL PRIMARY KEY,
  username   TEXT UNIQUE NOT NULL,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

INSERT INTO users (username) VALUES
  ('boldbaatar'),
  ('munkhjargal')
ON CONFLICT (username) DO NOTHING;

nginx/nginx.conf

nginx
events {
  worker_connections 1024;
}

http {
  upstream app {
    server app:3000;
  }

  server {
    listen 80;

    location / {
      proxy_pass         http://app;
      proxy_set_header   Host $host;
      proxy_set_header   X-Real-IP $remote_addr;
      proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    location /health {
      proxy_pass http://app;
      access_log off;
    }
  }
}

.env.example

bash
DB_USER=myuser
DB_PASSWORD=changeme
DB_NAME=myapp
DATABASE_URL=postgresql://myuser:changeme@db:5432/myapp

.env файл:

bash
cp .env.example .env
# .env дотор нууц үгийг өөрчилнө

docker-compose.yml

yaml
services:
  db:
    image: postgres:16-alpine
    restart: unless-stopped
    mem_limit: 512m
    environment:
      POSTGRES_USER: ${DB_USER}
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: ${DB_NAME}
    volumes:
      - pgdata:/var/lib/postgresql/data
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 10s
      timeout: 5s
      start_period: 10s
      retries: 5
    networks:
      - backend

  app:
    build:
      context: ./app
      dockerfile: Dockerfile
    restart: unless-stopped
    mem_limit: 256m
    cpus: 0.5
    environment:
      NODE_ENV: production
      DATABASE_URL: ${DATABASE_URL}
      PORT: 3000
    depends_on:
      db:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 30s
      timeout: 5s
      start_period: 20s
      retries: 3
    networks:
      - backend
      - frontend

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    mem_limit: 64m
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      app:
        condition: service_healthy
    networks:
      - frontend

volumes:
  pgdata:

networks:
  backend:
    driver: bridge
  frontend:
    driver: bridge

Network тусгаарлалт:

  • backend network: dbapp — Nginx энэ networkт байхгүй тул database-д шууд хандаж чадахгүй
  • frontend network: appnginx — Database энэ networkт байхгүй

Ажиллуулах

bash
# 1. .env файл үүсгэх
cp .env.example .env

# 2. Бүгдийг build хийж ажиллуулах
docker compose up --build

# 3. Background-д ажиллуулах
docker compose up --build -d

# 4. Шалгах
curl http://localhost/health
curl http://localhost/users
curl -X POST http://localhost/users \
  -H "Content-Type: application/json" \
  -d '{"username": "tsetseg"}'

Ажиллаж байгааг шалгах

bash
# Бүх container-ийн төлөв
docker compose ps

# Log харах
docker compose logs -f

# Нөөцийн хэрэглээ
docker stats

# Database-д шууд холбогдох
docker compose exec db psql -U myuser -d myapp -c "SELECT * FROM users;"

Зогсоох ба цэвэрлэх

bash
# Зогсоох
docker compose down

# Volume-тай хамт бүгдийг устгах
docker compose down -v

# Image-тай хамт
docker compose down -v --rmi all

Та юу сурсан бэ?

Энэ курсын туршид:

Үндэс — Container, image, registry-ийн ойлголт. Docker-ийн үндсэн команд.

Dockerfile — Layer, cache, multi-stage build. Node.js, Python аппликейшн containerize хийх.

Compose — Олон сервис хамтад нь ажиллуулах. Volume, network, healthcheck, depends_on.

Production бэлдэлт — Non-root user, resource limits, log удирдлага, аюулгүй байдал, image scan.

CI/CD — GitHub Actions-аар автоматаар build ба push.

Docker нь орчин үеийн backend хөгжүүлэлтийн суурь хэрэгсэл болсон. Энэ мэдлэгийн дээр Kubernetes, AWS ECS, Google Cloud Run зэрэг container orchestration платформуудыг судалж болно.