О насБлогКонтакты
DevOps14 июня 2021 г. 4 мин 8

Docker и CI/CD для небольшой команды: что мы реально запускаем в продакшене

AunimedaAunimeda
📋 Содержание

Docker и CI/CD для небольшой команды: что мы реально запускаем в продакшене

Kubernetes мощный. Ещё это 40+ YAML-файлов, крутая кривая обучения и операционные накладные расходы, которые не окупаются для большинства команд до 20 инженеров. Вот наш реальный продакшен-стек: Docker Compose, GitHub Actions, Nginx-реверс-прокси и деплой-скрипт. Всё это держит реальный трафик.


Схема инфраструктуры

GitHub (код)
  → GitHub Actions (CI: тесты, сборка, push образа)
  → GitHub Container Registry (хранение образов)
  → VPS: деплой через SSH
     → docker-compose pull + up
     → Nginx маршрутизирует трафик

Один VPS (Hetzner CPX31, 4 vCPU, 8GB RAM, €12/месяц) запускает 6 продакшен-приложений через Docker Compose. До этого мы платили €200+/месяц за эквивалентные ресурсы на облачных провайдерах.


Dockerfile: продакшен Node.js

# Многоэтапная сборка: этап builder не попадает в продакшен
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

# Продакшен-этап: минимальный образ
FROM node:18-alpine AS production
WORKDIR /app

# Непривилегированный пользователь
RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001

COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next
COPY --from=builder --chown=nextjs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nextjs:nodejs /app/package.json ./

USER nextjs
EXPOSE 3000
ENV NODE_ENV=production

CMD ["node_modules/.bin/next", "start"]

Многоэтапная сборка: dev-зависимости и сборочные инструменты остаются в builder-этапе. Результат: образ 185MB вместо 1.2GB.


Docker Compose: продакшен-стек

# docker-compose.yml
version: '3.8'

services:
  frontend:
    image: ghcr.io/yourorg/frontend:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - NEXT_PUBLIC_API_URL=https://api.yoursite.com
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "wget", "-q", "--spider", "http://localhost:3000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  backend:
    image: ghcr.io/yourorg/backend:${IMAGE_TAG:-latest}
    restart: unless-stopped
    environment:
      - NODE_ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      postgres:
        condition: service_healthy
    networks:
      - app-network

  postgres:
    image: postgres:15-alpine
    restart: unless-stopped
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_USER=${DB_USER}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER}"]
      interval: 10s
      timeout: 5s
      retries: 5
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    restart: unless-stopped
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data
    networks:
      - app-network

volumes:
  postgres_data:
  redis_data:

networks:
  app-network:
    driver: bridge

GitHub Actions: сборка и деплой

# .github/workflows/deploy.yml
name: Деплой в продакшен

on:
  push:
    branches: [main]

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    outputs:
      image-tag: ${{ steps.meta.outputs.version }}

    steps:
      - uses: actions/checkout@v4

      - name: Docker meta
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/${{ github.repository }}
          tags: |
            type=sha,prefix=,format=short

      - name: Войти в GHCR
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Собрать и запушить образ
        uses: docker/build-push-action@v5
        with:
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

  deploy:
    needs: build-and-push
    runs-on: ubuntu-latest

    steps:
      - name: Деплой через SSH
        uses: appleboy/ssh-action@v1
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: deploy
          key: ${{ secrets.SSH_PRIVATE_KEY }}
          script: |
            cd /opt/yourapp
            export IMAGE_TAG=${{ needs.build-and-push.outputs.image-tag }}
            docker-compose pull frontend backend
            docker-compose up -d --no-deps frontend backend
            docker image prune -f

Время деплоя: обычно 90 секунд от git push до продакшена. Zero downtime - Docker Compose поднимает новые контейнеры до остановки старых (с --no-deps).


Nginx: реверс-прокси

# /etc/nginx/sites-available/yoursite.com
server {
    listen 443 ssl http2;
    server_name yoursite.com;

    ssl_certificate /etc/letsencrypt/live/yoursite.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yoursite.com/privkey.pem;

    # Заголовки безопасности
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

    location / {
        proxy_pass http://localhost:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_cache_bypass $http_upgrade;
    }

    location /api/ {
        proxy_pass http://localhost:3001;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

server {
    listen 80;
    server_name yoursite.com;
    return 301 https://$host$request_uri;
}

SSL-сертификаты через Certbot (Let's Encrypt). Обновление автоматическое через systemd-таймер.


Резервное копирование

#!/bin/bash
# /opt/scripts/backup.sh - запускается через cron ежедневно

DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR="/backups"

# Дамп PostgreSQL
docker exec postgres pg_dump -U $DB_USER $DB_NAME | \
    gzip > "$BACKUP_DIR/db_$DATE.sql.gz"

# Удаляем бэкапы старше 7 дней
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +7 -delete

# Загружаем в S3-совместимое хранилище
aws s3 cp "$BACKUP_DIR/db_$DATE.sql.gz" \
    "s3://backup-bucket/postgres/" \
    --endpoint-url https://storage.example.com

Kubernetes? Не сейчас

Вопрос о Kubernetes возникает каждый квартал. Наш ответ: когда появятся выделенные DevOps-инженеры или когда одному приложению понадобится 10+ реплик - переедем. До тех пор этот стек справляется со всем, что мы на него кидаем.

Экономия реальная: €12/месяц за VPS вместо €200+/месяц за облачные сервисы с эквивалентными ресурсами. Для стартапа или небольшой студии разница принципиальная.

Читайте также

Хостинг для сайта в Кыргызстане: VPS, shared или облако - что выбрать в 2026aunimeda
DevOps

Хостинг для сайта в Кыргызстане: VPS, shared или облако - что выбрать в 2026

Разбираем типы хостинга для сайтов в Кыргызстане: shared, VPS, облачный и выделенный сервер. Реальные цены в сомах, рейтинг провайдеров и когда стоит апгрейдиться.

Как настроить Docker для PHP-разработки в команде: опыт Бишкека (2016)aunimeda
DevOps

Как настроить Docker для PHP-разработки в команде: опыт Бишкека (2016)

В нашей команде в Бишкеке в 2016 году был постоянный хаос: у одного PHP 5.6, у другого 7.0, у третьего XAMPP с настройками 2014 года. Docker решил проблему: единое окружение у всех. Точный docker-compose.yml для PHP 7.0 + nginx + MySQL + Redis с учётом реалий слабого интернета.

Как настроить HTTPS на nginx в 2015 году: от покупки сертификата до A+ на SSL Labsaunimeda
DevOps

Как настроить HTTPS на nginx в 2015 году: от покупки сертификата до A+ на SSL Labs

В 2015 году Let's Encrypt ещё не вышел. HTTPS стоил денег и требовал ручной настройки. Мы прошли путь от покупки сертификата за $10 до рейтинга A+ на SSL Labs для сервисов наших бишкекских клиентов. Конфигурация nginx, цепочка сертификатов, HSTS — точный рабочий конфиг.

Нужна IT-разработка для вашего бизнеса?

Разрабатываем сайты, мобильные приложения и AI-решения для бизнеса в Кыргызстане. Бесплатная консультация.

Получить консультацию Все статьи