Деплой
Де це працює, як відбувається деплой і що робити, коли він падає.
Топологія
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