Главная/Эксплуатация/Деплой ENУКРРУС API-справочник (ReDoc) ↗

Деплой

Где это работает, как происходит деплой и что делать, когда он падает.

Топология

languages.service.custom.mt на общем хосте сервисов, за общим edge-прокси Caddy.

Интернет ──▶ Caddy (:443, /opt/caddy)
                └─ languages.service.custom.mt ──▶ language-service:8000
                                                        └─▶ postgres (свой контейнер, свой том)

Сервис имеет собственный Postgres — в собственном compose-стеке, со своим томом. У него нет соединения с базой легаси-консоли ни в один момент: та база читается ровно один раз, вручную, чтобы собрать сид. См. Rebuilding the Seed.

Всё живёт в /opt/language-service/ на хосте: docker-compose.prod.yml, .env и том Postgres.

Автодеплой

Пуш в main → CI → сборка → деплой, через .github/workflows/.

  1. CI — ruff, mypy, pytest против SQLite и Postgres, проверка целостности сида, сборка Docker-образа со smoke-тестом.
  2. Build — образ linux/arm64 в ghcr.io/custommt/language_service, теги — SHA коммита и latest. Хост на Graviton; образ только под amd64 упал бы с «exec format error».
  3. Deploy — по SSH: синк compose и Caddy-файлов, pull, alembic upgrade head, сид, рестарт, ожидание health-гейта, reload Caddy.

Управляется переменной репозитория DEPLOY_ENABLED=true. Секреты: DEPLOY_HOST, DEPLOY_USER, DEPLOY_SSH_KEY.

Сид выполняется на каждом деплое

python -m app.cli seed идемпотентен и работает как upsert: сопоставляет строки по естественным ключам, обновляет на месте и никогда не удаляет. Правки в админке для строк, о которых сид не знает, остаются нетронутыми. Но закреплённый код, о котором сид знает, будет сброшен к засеянному значению — так что исправление, задуманное как постоянное, должно быть в сиде, а не только в панели.

Конфигурация

Переменная Обязательна Примечания
DATABASE_URL да postgresql+asyncpg://…
APP_ENV да production на сервере
ADMIN_USERNAME да
ADMIN_PASSWORD_HASH_FILE да file holding the argon2id hash — see Admin Panel
ADMIN_SECRET_KEY_FILE да openssl rand -hex 48 into a file
ADMIN_ENABLED нет false убирает панель полностью
DOCS_ENABLED нет Swagger UI. /redoc и /wiki публичны всегда
CACHE_TTL_SECONDS нет дефолт 300

С APP_ENV=production и включённой админкой отсутствующий ADMIN_SECRET_KEY или ADMIN_PASSWORD_HASHошибка старта. Секреты генерируются на сервере и никогда его не покидают.

Health

Проба Проверяет Для чего
/health ничего — процесс жив liveness контейнера
/health/ready база доступна, каталог непуст гейт деплоя, балансировщик

Readiness падает, когда миграции прошли, а сид — нет: пустой каталог отвечал бы 404 на всё.

Эксплуатация

cd /opt/language-service

docker compose -f docker-compose.prod.yml logs -f api
docker compose -f docker-compose.prod.yml run --rm api python -m app.cli stats
docker compose -f docker-compose.prod.yml run --rm api python -m app.cli resolve deepl_api pt_BR

# ротация пароля админа
docker compose -f docker-compose.prod.yml run --rm api python -m app.cli hash-password
# → вставить в .env, далее:
docker compose -f docker-compose.prod.yml up -d api

Логи — один JSON-объект на строку: event, level и поля события. Строки запросов несут correlation_id.

Откат

Задеплойте предыдущий тег образа:

cd /opt/language-service
sed -i "s|^IMAGE=.*|IMAGE=ghcr.io/custommt/language_service:<sha>|" .env
docker compose -f docker-compose.prod.yml up -d api

Миграции аддитивны, так что один релиз назад не требует downgrade.

Когда деплой падает

Симптом Что смотреть
Контейнер unhealthy logs api — обычно DATABASE_URL или отсутствующий секрет админки
/health/ready 503, catalog: false сид не выполнился: run --rm api python -m app.cli seed
502 от Caddy контейнер не в сети edge, или site-файл не синкнулся
TLS не выдался DNS: *.service.custom.mt должен резолвиться на хост

Далее