Niti — monorepo приложения для управления поиском работы. Backend построен на
FastAPI, Granian, SQLAlchemy и Alembic; frontend — на React, TypeScript и Vite.
Публичный лендинг находится в landing, а production-приложение запускается
через Docker Compose и Traefik v3.
Niti/
├── landing/ # публичный лендинг для useniti.xyz
├── frontend/ # React/Vite и production-образ Nginx
├── backend/ # FastAPI/Granian, Alembic и production-образ
├── infrastructure/
│ ├── compose.yaml
│ ├── postgres/initdb/ # инициализация расширений PostgreSQL
│ └── traefik/ # статическая и динамическая конфигурация
├── .env.example
├── .gitignore
└── README.md
Требования: Docker с Compose v2, uv и Node.js 20+.
# Переменные для локальных инфраструктурных контейнеров
cp .env.example .env
# Локальные настройки приложений
cp backend/.env.example backend/.env
cp frontend/.env.example frontend/.env
# PostgreSQL слушает только 127.0.0.1
make db-up
make install
make migrate
make seedПосле этого запустите приложения в двух терминалах:
make backend # API: http://localhost:8000, Swagger: http://localhost:8000/docs
make frontend # UI: http://localhost:5173Тесты и проверки:
make test
make lintИз корня репозитория:
cp .env.example .env
docker compose -f infrastructure/compose.yaml up -d --buildBackend при старте ждёт готовности PostgreSQL, применяет alembic upgrade head,
затем запускает Granian. Frontend собирается с адресом API из API_HOST и
отдаётся Nginx. Порты приложений наружу не публикуются: доступ к ним
маршрутизирует Traefik.
Проверить состояние и логи:
docker compose -f infrastructure/compose.yaml ps
docker compose -f infrastructure/compose.yaml logs -fОстановить окружение без удаления данных:
docker compose -f infrastructure/compose.yaml downДля удаления именованных volumes и всех данных используйте down --volumes
только при осознанной необходимости.
| Сервис | Назначение | Доступ |
|---|---|---|
traefik |
TLS, Let's Encrypt, HTTP → HTTPS, маршрутизация, dashboard | 80, 443 |
landing |
Публичный продуктовый лендинг | https://useniti.xyz |
frontend |
Собранный React SPA под Nginx | https://app.useniti.xyz |
backend |
FastAPI под Granian | https://api.useniti.xyz |
postgres |
PostgreSQL 16 и pg_trgm |
внутри сети; локально 127.0.0.1:5432 |
Все сервисы подключены к одной bridge-сети niti. PostgreSQL, загруженные файлы
и состояние ACME хранятся в именованных Docker volumes.
Dashboard Traefik доступен по https://traefik.useniti.xyz и защищён Basic
Auth. В примере заданы admin / change-me; перед production-деплоем их нужно
заменить через переменную TRAEFIK_DASHBOARD_AUTH_USERS.
Compose читает корневой .env. Файл не должен попадать в Git.
| Переменная | Описание |
|---|---|
APP_HOST |
публичный домен приложения |
API_HOST |
публичный домен API; встраивается в frontend при сборке |
TRAEFIK_HOST |
публичный домен dashboard |
COOKIE_DOMAIN |
домен session cookie; production по умолчанию .useniti.xyz |
LETSENCRYPT_EMAIL |
email для ACME/Let's Encrypt |
POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORD |
настройки PostgreSQL |
DATABASE_URL |
SQLAlchemy DSN backend; пароль должен совпадать с POSTGRES_PASSWORD |
SECRET_KEY |
секрет подписи JWT |
TRAEFIK_DASHBOARD_AUTH_USERS |
строка user:bcrypt-hash для Basic Auth |
BACKEND_WORKERS |
число процессов Granian |
ACCESS_TOKEN_EXPIRE_MINUTES |
срок действия access token |
POSTGRES_PORT |
локальный loopback-порт PostgreSQL |
DOCKER_NETWORK_NAME |
имя общей Docker-сети |
Перед production-деплоем замените все значения CHANGE_ME. Надёжные секреты
можно сгенерировать так:
openssl rand -hex 32 # SECRET_KEY
openssl rand -base64 36 # пароль PostgreSQL
htpasswd -nbB admin 'strong-dashboard-password' # Basic AuthЗначение с bcrypt-хешем оставляйте в .env в одинарных кавычках, чтобы символы
$ не интерпретировались Compose. Если пароль PostgreSQL содержит специальные
символы URL, URL-кодируйте их в DATABASE_URL.
-
Установите Docker Engine и Compose v2. Откройте входящие TCP-порты
80и443; PostgreSQL наружу открывать не нужно. -
Создайте DNS A/AAAA-записи
app.useniti.xyz,api.useniti.xyzиtraefik.useniti.xyz, указывающие на VPS. Apex-доменuseniti.xyzнаправляется на хостинг лендинга. Для выпуска сертификатов приложения порт 80 должен быть доступен из интернета. -
Клонируйте репозиторий на VPS, выполните
cp .env.example .env, задайте production-секреты, email и домены. -
Запустите:
docker compose -f infrastructure/compose.yaml up -d --build
-
Проверьте
docker compose -f infrastructure/compose.yaml ps, логи Traefik и ответыhttps://api.useniti.xyz/health,https://app.useniti.xyz.
Для обновления получите новую версию кода и повторите up -d --build.
Именованные volumes при этом сохраняются. Перед обновлением схемы рекомендуется
сделать резервную копию PostgreSQL; миграции применяются автоматически при
старте backend.
Workflow .github/workflows/deploy.yml
запускается после каждого push в main и вручную через workflow_dispatch.
Он подключается к VPS по SSH, выполняет fast-forward-only обновление ветки и
пересобирает Compose-окружение. Одновременно может выполняться только один
production-деплой.
Один раз подготовьте VPS:
sudo mkdir -p /opt/niti
sudo chown "$USER":"$USER" /opt/niti
git clone git@github.com:TinyFrontier/Niti.git /opt/niti
cd /opt/niti
cp .env.example .env
# Заполните production-значения в .env
docker compose -f infrastructure/compose.yaml up -d --buildПользователь деплоя должен иметь доступ к Docker без sudo и право читать
репозиторий; на VPS нужен Docker Compose 2.18 или новее. Для приватного
репозитория добавьте отдельный read-only deploy key в GitHub и настройте этот
ключ на VPS.
Workflow использует уже существующие repository secrets из Settings → Secrets and variables → Actions:
| Secret | Значение |
|---|---|
SSH_HOST |
IP или DNS-имя VPS |
SSH_USER |
непривилегированный SSH-пользователь |
SSH_PRIVATE_KEY |
приватный SSH-ключ, чей public key находится в authorized_keys на VPS |
Дополнительные repository variables необязательны:
DEPLOY_PATH— путь к репозиторию на VPS, по умолчанию/opt/niti;SSH_PORT— SSH-порт, по умолчанию22.
Workflow получает host key через ssh-keyscan перед подключением. Для более
строгой защиты его можно позднее заменить заранее закреплённым fingerprint.
Файл .env не обновляется и не перезаписывается workflow. Если рабочая копия на
VPS содержит незакоммиченные изменения или не может быть обновлена через
fast-forward, деплой завершится ошибкой вместо перезаписи файлов.
Traefik использует Docker Provider для обнаружения сервисов и File Provider для
общих security headers/TLS options. Контейнеры без traefik.enable=true не
публикуются. HTTP-запросы перенаправляются на HTTPS, а сертификаты Let's Encrypt
получаются через HTTP-01 challenge и сохраняются в volume traefik_acme.