Как установить и настроить Temporal на VPS
Если у вас несколько микросервисов и между ними летают асинхронные цепочки вызовов — оплата, резервирование склада, уведомление, ретраи при сбое одного из шагов — рано или поздно эта логика превращается в кашу из очередей, таймеров и ручных проверок «а не зависла ли задача». Temporal решает именно эту проблему: он берёт на себя состояние длительного процесса, ретраи и восстановление после падений, а вы пишете обычный код на Go, Python, Java или TypeScript, как будто он выполняется без сбоев. Ниже — как поднять Temporal на своём VPS через Docker Compose, подключить PostgreSQL для хранения состояния, открыть Web UI и написать первый воркер.
Содержание
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →Что такое Temporal и когда он нужен
Temporal — это оркестратор durable-workflow: платформа, которая гарантирует, что длительный бизнес-процесс (workflow) выполнится до конца, даже если сервис-исполнитель упал посреди работы, сеть моргнула или задача ждёт внешнего события сутки. Внутри это кластер из нескольких служб (frontend, history, matching, worker) плюс база данных, где хранится история событий каждого workflow. Ваш код-воркер подключается к кластеру, забирает задачи из очереди (task queue) и выполняет их — а Temporal следит, чтобы при падении воркера задача не потерялась и выполнилась заново с того же места.
Типичные сценарии, где Temporal оправдан:
- цепочки заказов в e-commerce (оплата → резерв → доставка → возврат при отмене на любом шаге);
- сложные саги между микросервисами с компенсирующими действиями при откате;
- долгоживущие процессы: подписки, биллинг с ретраями, ожидание подтверждения от пользователя днями;
- ETL и batch-пайплайны, где важно не потерять прогресс при рестарте.
Если у вас простой CRUD или пара cron-задач — Temporal избыточен, хватит очереди задач попроще (Celery, BullMQ) или обычного Redis с воркером. Temporal имеет смысл, когда бизнес-логика распределена по нескольким сервисам и цена потерянного шага высока.
Требования к серверу
Для теста и небольшой продакшен-нагрузки (десятки-сотни workflow в минуту) достаточно одного VPS:
| Компонент | Минимум | Комфортно |
|---|---|---|
| vCPU | 2 | 4 |
| RAM | 4 GB | 8 GB |
| Диск | 20 GB SSD | 40+ GB SSD |
| ОС | Ubuntu 24.04 / Debian 12 | — |
Это без Elasticsearch — он нужен только для расширенного поиска workflow по произвольным атрибутам (Advanced Visibility). Для старта его можно пропустить: базовый поиск по ID, типу и статусу работает и на одном PostgreSQL. Если позже упрётесь в лимиты видимости — Elasticsearch добавляется отдельным сервисом в тот же compose-файл, но заметно поднимает требования к RAM (закладывайте ещё 2-4 GB).
Под серьёзную нагрузку в проде кластер обычно разносят на несколько нод и выносят базу отдельно, но для старта и для большинства проектов среднего размера один VPS с PostgreSQL в соседнем контейнере работает годами без нареканий.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверУстановка через Docker Compose
Ставим Docker, если его ещё нет — процесс подробно расписан в статье про Docker Compose для продакшена. Дальше создаём рабочую директорию и compose-файл:
mkdir -p ~/temporal && cd ~/temporal
nano docker-compose.yml
Минимальный рабочий стек — PostgreSQL для хранения состояния, сам сервер Temporal в режиме auto-setup (он сам накатывает схему БД при первом запуске), admin-tools для CLI-команд и Web UI:
services:
postgresql:
image: postgres:15
container_name: temporal-postgresql
restart: unless-stopped
environment:
POSTGRES_USER: temporal
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- temporal-net
temporal:
image: temporalio/auto-setup:1.24.2
container_name: temporal
restart: unless-stopped
depends_on:
- postgresql
environment:
- DB=postgres12
- DB_PORT=5432
- POSTGRES_USER=temporal
- POSTGRES_PWD=${POSTGRES_PASSWORD}
- POSTGRES_SEEDS=postgresql
- DYNAMIC_CONFIG_FILE_PATH=config/dynamicconfig/development-sql.yaml
ports:
- "127.0.0.1:7233:7233"
networks:
- temporal-net
temporal-admin-tools:
image: temporalio/admin-tools:1.24.2
container_name: temporal-admin-tools
depends_on:
- temporal
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CLI_ADDRESS=temporal:7233
stdin_open: true
tty: true
networks:
- temporal-net
temporal-ui:
image: temporalio/ui:2.31.2
container_name: temporal-ui
restart: unless-stopped
depends_on:
- temporal
environment:
- TEMPORAL_ADDRESS=temporal:7233
- TEMPORAL_CORS_ORIGINS=http://localhost:8080
ports:
- "127.0.0.1:8080:8080"
networks:
- temporal-net
networks:
temporal-net:
driver: bridge
volumes:
postgres-data:
Версии образов (1.24.2 для сервера и admin-tools, 2.31.2 для UI) актуальны на конец августа 2026 года — перед установкой сверьтесь со свежими тегами на Docker Hub, проект развивается быстро. Порты 7233 и 8080 намеренно забинжены на 127.0.0.1 — снаружи их закрываем и пускаем трафик через Nginx с TLS и базовой аутентификацией (об этом ниже), потому что ни gRPC-порт кластера, ни Web UI из коробки не защищены паролем.
Пароль для PostgreSQL держим в .env рядом с compose-файлом, а не в самом yaml:
cat > .env <<'EOF'
POSTGRES_PASSWORD=замените-на-длинный-случайный-пароль
EOF
chmod 600 .env
Подробнее про управление секретами в compose-проектах — в статье про Docker Secrets. Запускаем стек:
docker compose up -d
docker compose logs -f temporal
Первый старт занимает 20-40 секунд — auto-setup создаёт базы temporal и temporal_visibility в PostgreSQL и накатывает миграции схемы. В логах должна появиться строка вида Starting server for services: [history matching worker frontend] без последующих циклов рестарта.
Проверка и создание namespace
Temporal группирует workflow по namespace — это единица изоляции и настройки retention (сколько хранить историю завершённых workflow). После первого запуска обычно уже существует default, но для реального проекта стоит завести отдельный:
docker compose exec temporal-admin-tools \
temporal operator namespace create \
--namespace my-app \
--retention 168h
--retention 168h — семь дней хранения истории после завершения workflow. В проде retention обычно 3-30 дней в зависимости от того, как часто нужно поднимать историю закрытых процессов для разбора инцидентов — это напрямую влияет на размер PostgreSQL.
Список namespace и проверка, что кластер живой:
docker compose exec temporal-admin-tools temporal operator namespace list
docker compose exec temporal-admin-tools temporal operator cluster health
Если cluster health отвечает SERVING — кластер поднялся и готов принимать workflow.
Nginx как обратный прокси для Web UI
Web UI Temporal — это открытая панель без встроенной аутентификации, поэтому напрямую в интернет её выставлять нельзя. Ставим Nginx перед контейнером temporal-ui (базовая настройка reverse proxy разобрана в отдельной статье), добавляем Basic Auth и TLS через certbot:
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd temporal-admin
Конфиг сайта:
server {
listen 443 ssl http2;
server_name temporal.example.com;
ssl_certificate /etc/letsencrypt/live/temporal.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/temporal.example.com/privkey.pem;
auth_basic "Temporal UI";
auth_basic_user_file /etc/nginx/.htpasswd;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 80;
server_name temporal.example.com;
return 301 https://$host$request_uri;
}
Для gRPC-порта 7233, к которому подключаются воркеры, публичный доступ обычно не нужен вовсе — если воркеры крутятся на том же сервере или в приватной сети (VPC, WireGuard), оставляйте бинд на 127.0.0.1 или внутренний IP. Если воркеры распределены по разным серверам, поднимайте между ними приватную сеть, а не пробрасывайте 7233 в интернет без mTLS.
Первый workflow: воркер на Python
Устанавливаем SDK и пишем минимальный пример — активность (реальное действие) и workflow (логика, которая её вызывает):
python3 -m venv venv && source venv/bin/activate
pip install temporalio
# worker.py
import asyncio
from datetime import timedelta
from temporalio import workflow, activity
from temporalio.client import Client
from temporalio.worker import Worker
@activity.defn
async def send_confirmation(order_id: str) -> str:
# здесь реальная работа: вызов API, запись в БД и т.д.
return f"Order {order_id} confirmed"
@workflow.defn
class OrderWorkflow:
@workflow.run
async def run(self, order_id: str) -> str:
return await workflow.execute_activity(
send_confirmation, order_id,
start_to_close_timeout=timedelta(seconds=10),
)
async def main():
client = await Client.connect("localhost:7233", namespace="my-app")
worker = Worker(
client, task_queue="orders-task-queue",
workflows=[OrderWorkflow], activities=[send_confirmation],
)
await worker.run()
if __name__ == "__main__":
asyncio.run(main())
Запускаем воркер (python worker.py), а сам workflow стартуем отдельным клиентским вызовом client.start_workflow(OrderWorkflow.run, "order-42", id="order-workflow-order-42", task_queue="orders-task-queue"). Если теперь остановить воркер посреди выполнения активности и запустить заново — Temporal подхватит незавершённый workflow с того места, где он был, а не с начала. Это и есть durability, ради которой всё затевалось: в Web UI видна полная история попыток, ретраев и итоговый результат по order-workflow-order-42.
Мониторинг и бэкапы
Сервер Temporal отдаёт метрики в формате Prometheus — добавьте в сервис temporal переменную PROMETHEUS_ENDPOINT=0.0.0.0:9090 и порт 9090:9090, тогда Prometheus сможет забирать их как обычный target. Если у вас уже есть стек Grafana и Prometheus, добавление ещё одного джоба в prometheus.yml — вопрос пяти минут; готовые дашборды для Temporal есть в официальном репозитории проекта. В первую очередь стоит следить за workflow_task_schedule_to_start_latency — если она растёт, воркерам не хватает параллелизма.
С бэкапами всё сводится к бэкапу PostgreSQL — вся история workflow живёт там, а не в самом Temporal:
docker compose exec postgresql pg_dump -U temporal -Fc temporal > temporal_$(date +%F).dump
docker compose exec postgresql pg_dump -U temporal -Fc temporal_visibility > temporal_visibility_$(date +%F).dump
Оба дампа стоит гнать по расписанию и хранить хотя бы неделю-две — при повреждении базы это единственный способ восстановить историю workflow. Для восстановления используется pg_restore на чистую базу до старта контейнера temporal, чтобы auto-setup не попытался накатить схему поверх.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Чем Temporal отличается от обычной очереди вроде RabbitMQ или Celery?
Очередь доставляет сообщение воркеру один раз (ретраи — на вашей логике). Temporal хранит весь ход workflow как последовательность событий и восстанавливает состояние с точностью до шага после падения воркера или ожидания днями — это оркестрация состояния, а не просто доставка сообщений.
Нужен ли Elasticsearch обязательно?
Нет. Он нужен только для поиска workflow по произвольным пользовательским атрибутам. Базовый поиск по ID, типу и статусу работает на одном PostgreSQL — для старта и большинства проектов этого хватает.
Можно ли использовать MySQL вместо PostgreSQL?
Да, Temporal поддерживает и MySQL, и Cassandra — меняется значение DB (mysql8 вместо postgres12) и переменные подключения. PostgreSQL в примере выбран как наиболее предсказуемый вариант для одиночного VPS.
Как обновить Temporal без потери данных?
Поднимаете новую версию образов auto-setup и ui, делаете docker compose pull && docker compose up -d — миграции схемы auto-setup применяет сам при старте. Перед обновлением на проде обязательно снимите дамп PostgreSQL: схема меняется от релиза к релизу, откат не всегда тривиален.
Сколько ресурсов нужно на десятки воркеров?
Сами воркеры — отдельные процессы вашего приложения, они не входят в ресурсы кластера и масштабируются независимо от него. Кластеру на 4 GB RAM обычно комфортно на сотнях workflow в минуту; дальше упор идёт в PostgreSQL.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →