Как onlyapps.org работает на одном сервере — Go API, очередь задач внутри PostgreSQL, Next.js для сайта и консоли, Dokploy для деплоя — и четыре урока дня запуска.
Новый onlyapps.org — публичный сайт, консоль, в которой его редактирует наша команда, API и все фоновые задачи — работает на одном VPS. Без управляемых баз данных, без Redis, без внешнего CI. Рассказываем, как всё устроено и что мы узнали при запуске.
Из чего состоит
| Часть | Технологии | Что делает |
|---|---|---|
| Сайт | Next.js 16 | Серверный рендеринг страниц на английском, сербском и русском |
| Консоль | Next.js 16 | Редакторы, публикация, настройки для команды |
| API | Go, chi, pgx, sqlc | Вся бизнес-логика, авторизация, медиа |
| Воркер | Go, River | Отложенные публикации, письма, IndexNow, обновление кэша |
| База данных | PostgreSQL 18 | Данные и очередь задач |
| Платформа | Dokploy, Traefik, Let's Encrypt | Сборка, маршрутизация и TLS на нашем VPS |
Одна база для данных и задач
Фоновые задачи живут в PostgreSQL благодаря River — очереди задач для Go поверх Postgres. Главный выигрыш — транзакции: когда редактор публикует пост, изменение контента и запускаемые им задачи — обновить кэш сайта, уведомить IndexNow, отправить письма подписчикам — записываются в одной транзакции. Либо происходит всё, либо ничего, и не нужна вторая система, за которой надо следить.
Контент как данные, а не HTML
Посты и документы хранятся как структурированный JSON из редактора, а сайт отрисовывает их компонент за компонентом. HTML не хранится, поэтому его нечего очищать при выводе, а один и тот же контент одинаково выглядит на всех языках.
Быстрые страницы без CDN
Сайт использует cache components в Next.js. Страницы отдаются из кэша и инвалидируются по тегам в момент публикации — воркер вызывает сайт, и пересобираются только затронутые страницы. Медиа хранятся на Docker-томе и отдаются Go API со своего поддомена, а изображения конвертируются в более лёгкие варианты WebP или JPEG. В наших тестовых прогонах Lighthouse для мобильных показывает производительность в районе 90+ и 100 по SEO.
Деплой через Dokploy
Весь стек — один Docker Compose с шестью сервисами: сайт, консоль, API, воркер, PostgreSQL и контейнер бэкапов, который каждую ночь запускает pg_dump и хранит дампы за две недели. Dokploy забирает репозиторий, собирает образы на сервере и маршрутизирует домены через Traefik с сертификатами Let's Encrypt. Миграции базы применяются автоматически при старте API.
Четыре урока дня запуска
- Уникальные имена сервисов в общей сети. На сервере с множеством проектов Dokploy подключает публичные сервисы к одной общей Docker-сети. У других проектов там были свои сервисы
api,webиpostgres, так что обычныйhttp://api:8080мог попасть не в тот контейнер. Теперь внутренние адреса — уникальные алиасы вродеonlyapps-api. - Домены — в одном месте. Наш compose-файл объявлял роутеры Traefik, а Dokploy генерировал свои для тех же хостов. Мы перенесли все домены в Dokploy, а в compose оставили только middleware — заголовки безопасности и редирект с www, каждый на своём сервисе.
- Большим загрузкам нужен долгий read timeout. Traefik v3 по умолчанию обрывает тело запроса через 60 секунд, и загрузка видео на медленных соединениях прерывалась. Помогло увеличение read timeout на entrypoint.
- Пароли внутри URL. Пароль базы, сгенерированный в base64, может содержать слэш — и строка подключения
postgres://ломается. Пароли в hex полностью снимают проблему.
Совет
Перед первым деплоем проверьте, не попадают ли ваши секреты внутрь URL, и если попадают — генерируйте их в hex.
Почему мы держим всё таким простым
Всё проверяется до выхода в прод: каждое изменение проходит линтер, unit- и интеграционные тесты, продакшен-сборку и end-to-end-тесты с проверкой доступности. В итоге получается стек, который небольшая команда понимает целиком, может перенести на другой сервер за вечер и содержит по цене одного VPS.