Деплой
Где это работает, как происходит деплой и что делать, когда он падает.
Топология
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/.
- CI — ruff, mypy, pytest против SQLite и Postgres, проверка целостности сида, сборка Docker-образа со smoke-тестом.
- Build — образ
linux/arm64вghcr.io/custommt/language_service, теги — SHA коммита иlatest. Хост на Graviton; образ только под amd64 упал бы с «exec format error». - 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 должен резолвиться на хост |
Далее
- Rebuilding the Seed — обновление каталога из консоли
- Admin Panel