Деплой на 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
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.
Структура проекта на сервере
Первый деплой
Вариант 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: {}
.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
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;"
# Видишь данные? Бэкап работает!