Деплой на VPS (masterhost): docker compose на сервере

Деплой на VPS (masterhost): docker compose на сервере

Деплой на VPS - это перенос локального docker compose up на удалённый сервер. В этом уроке - от чистого сервера до работающего приложения, плюс обновления и откат. Базовые навыки Linux и bash-скриптов обязательны.

Подготовка сервера

1. Базовая настройка (Ubuntu 22.04+)

ssh root@YOUR_SERVER_IP

# Обновления
apt update && apt upgrade -y

# Создаём деплой-пользователя (не работаем от root)
adduser deploy
usermod -aG sudo deploy

# Настраиваем SSH-ключ для deploy
su - deploy
mkdir -p ~/.ssh
# скопируй свой public key в ~/.ssh/authorized_keys
Root-доступ по SSH - риск. Создай отдельного пользователя `deploy`, добавь в группу docker и sudo. Закрой SSH по паролю - только по ключу.

2. Установка Docker

# Установка Docker Engine
sudo apt install -y docker.io docker-compose-plugin

# Добавляем deploy в группу docker (без sudo для docker-команд)
sudo usermod -aG docker deploy
newgrp docker

# Проверка
docker --version
docker compose version

3. Firewall

sudo ufw allow 22/tcp    # SSH
sudo ufw allow 80/tcp    # HTTP
sudo ufw allow 443/tcp   # HTTPS
sudo ufw enable

Подробнее про порты и сети - в уроке Linux networking.

Структура проекта на сервере

Структура /opt/myapp на сервере: compose.yml, .env (секреты), backend/, frontend/, deploy/backup.sh для cron

`/opt/` - директория для стороннего ПО в Linux (FHS). Альтернатива - `/srv/` или `/home/deploy/app`. Главное - единообразие: одно место для всех проектов.

Первый деплой

Вариант 1: Git clone (рекомендуется)

ssh deploy@YOUR_SERVER_IP
sudo mkdir -p /opt/myapp && sudo chown deploy:deploy /opt/myapp
cd /opt/myapp

# Клонируем
git clone git@gitlab.com:user/myapp.git .

# Создаём .env из примера
cp .env.example .env
nano .env   # заполняем реальные значения

# Поднимаем
docker compose up -d --build
docker compose ps

Вариант 2: Docker images из Registry

# На сервере - только docker-compose.yml и .env, без исходников
# Образы тянутся из GitLab Container Registry

docker login registry.gitlab.com
docker compose pull
docker compose up -d

<ComparisonTable title="Git clone vs Registry images" headers={["", "Git clone", "Registry images"]} rows={[ ["Исходники на сервере", "Да", "Нет (только compose + env)"], ["Сборка", "На сервере (docker build)", "В CI (заранее)"], ["Скорость деплоя", "Медленнее (сборка)", "Быстрее (только pull)"], ["Нагрузка на сервер", "CPU при сборке", "Только сеть (pull)"], ["Подходит для", "Начало, один сервер", "Продакшен, CI/CD"] ]} />

docker-compose.yml для продакшена

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

  backend:
    build: ./backend
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}?sslmode=disable
      PORT: "8080"
    ports:
 - "8080:8080"

  frontend:
    build: ./frontend
    restart: unless-stopped
    ports:
 - "80:80"

volumes:
  dbdata: {}
Без `restart` контейнер не перезапустится после падения или перезагрузки сервера. `unless-stopped` - перезапускает всегда, кроме ручной остановки (`docker compose stop`). Альтернатива: `always` - перезапускает даже после `docker stop`.

.env файл

# .env (НИКОГДА не коммитить в git!)
DB_USER=myapp
DB_PASSWORD=strong_random_password_here
DB_NAME=myapp
SMTP_HOST=smtp.yandex.ru
SMTP_PASSWORD=app_password_here
OAUTH_GITHUB_SECRET=github_secret_here

В .gitignore обязательно:

.env
.env.*
!.env.example

.env.example - шаблон без реальных значений, коммитится в git.

Обновление приложения

ssh deploy@YOUR_SERVER_IP
cd /opt/myapp

# Получаем изменения
git pull

# Пересобираем и перезапускаем
docker compose up -d --build

# Проверяем
docker compose ps
docker compose logs -f --tail=100 backend

Compose пересоберёт только изменившиеся сервисы. Если Dockerfile не менялся - используется кэш.

Откат

# Смотрим историю коммитов
git log --oneline -10

# Откат на предыдущий коммит
git checkout <commit-hash>
docker compose up -d --build

# Или просто: откат на предыдущий коммит
git revert HEAD
docker compose up -d --build

Откат по хешу коммита работает, но в команде удобнее откатываться на тег релиза: git checkout v1.2.0 понятно всем, git checkout a1b2c3d - никому.

Бэкап базы данных

# Бэкап
docker compose exec db pg_dump -U ${DB_USER} ${DB_NAME} > backup_$(date +%Y%m%d).sql

# Восстановление
docker compose exec -T db psql -U ${DB_USER} ${DB_NAME} < backup_20260505.sql

Автоматизация - cron-задача:

# crontab -e
0 3 * * * cd /opt/myapp && docker compose exec -T db pg_dump -U myapp myapp | gzip > /opt/backups/db_$(date +\%Y\%m\%d).sql.gz
Без бэкапов `docker compose down -v` или сбой диска = потеря всех данных. Минимум: ежедневный бэкап БД + хранение на отдельном диске/сервере. Проверяй, что бэкап можно восстановить!

Reverse proxy (Caddy / Nginx)

Для HTTPS и доменного имени нужен reverse proxy перед приложением:

# docker-compose.yml - добавляем Caddy
services:
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
 - "80:80"
 - "443:443"
    volumes:
 - ./Caddyfile:/etc/caddy/Caddyfile
 - caddy_data:/data
# Caddyfile
example.com {
    reverse_proxy frontend:80
}

api.example.com {
    reverse_proxy backend:8080
}

Caddy автоматически получает и обновляет Let's Encrypt сертификаты. Минимум конфигурации.

Мониторинг: минимальный набор

# Статус контейнеров
docker compose ps

# Логи (follow + tail)
docker compose logs -f --tail=200

# Ресурсы (CPU, RAM)
docker stats

# Диск
df -h
docker system df

Для продакшена добавь хотя бы uptime-мониторинг (UptimeRobot, Healthchecks.io - бесплатные) на health endpoint.

Чеклист деплоя

  • Сервер: deploy-пользователь, SSH по ключу, firewall
  • Docker + Compose установлены
  • .env заполнен реальными значениями
  • docker compose up -d - все сервисы running/healthy
  • Бэкап БД настроен (cron)
  • HTTPS через reverse proxy (Caddy/Nginx)
  • restart: unless-stopped для всех сервисов
  • Мониторинг (хотя бы docker stats + uptime check)

Мини-задание

  • Подними проект на сервере (или в локальной VM) через docker compose up -d
  • Проверь docker compose ps - все сервисы running
  • Сделай бэкап БД через pg_dump
  • Обнови код (git pull) и передеплой - проверь, что данные сохранились
  • Сделай docker compose logs -f своим первым инструментом диагностики

Итог

  • Деплой = Docker + Compose на сервере. Git clone или pull из Registry.
  • .env - секреты на сервере, никогда в git. .env.example - шаблон.
  • restart: unless-stopped - контейнеры переживают перезагрузку сервера.
  • Бэкапы БД (pg_dump + cron) - обязательны с первого дня.
  • Reverse proxy (Caddy) - HTTPS автоматически, минимум конфигурации.
  • Обновление: git pull && docker compose up -d --build. Откат: git revert.

Типичная ошибка

Забыть restart: unless-stopped - после перезагрузки сервера контейнеры не поднимутся. Ты узнаешь об этом от пользователей, а не от мониторинга.

Вторая ошибка - docker compose down -v на продакшене. Флаг -v удаляет volumes, включая данные PostgreSQL. Без бэкапа - это катастрофа. На сервере: docker compose down (без -v) для остановки, docker compose up -d --build для обновления.

Мини-практика (15-20 минут)

Проведи полный цикл деплоя локально (эмуляция сервера):

# 1. Создай директорию «сервера»
mkdir -p /tmp/server && cd /tmp/server

# 2. Создай docker-compose.yml с postgres + простым бэкендом
# 3. Создай .env с DB_PASSWORD

# 4. Подними
docker compose up -d

# 5. Добавь данные в БД
docker compose exec db psql -U postgres -c "CREATE TABLE test (id serial, name text);"
docker compose exec db psql -U postgres -c "INSERT INTO test (name) VALUES ('deploy');"

# 6. Сделай бэкап
docker compose exec db pg_dump -U postgres > backup.sql

# 7. Уничтожь volumes (эмуляция сбоя)
docker compose down -v

# 8. Восстанови из бэкапа
docker compose up -d
docker compose exec -T db psql -U postgres < backup.sql
docker compose exec db psql -U postgres -c "SELECT * FROM test;"
# Видишь данные? Бэкап работает!

Зарегистрируйтесь бесплатно, чтобы пройти квиз, решить задание с автопроверкой и вести прогресс.