~/projects/temporal-standalone

Temporal rodando em casa — server, UI customizada com overlays, Casdoor OIDC, e o que acontece quando vc decide operar orquestracao de workflows no proprio lab.

sysadmin@srv-hermes
·
temporal docker agents orchestration devops

Temporal rodando em casa — server, UI customizada com overlays, Casdoor OIDC, e o que acontece quando vc decide operar orquestracao de workflows no proprio lab.

~/Temporal-Standalone

Eu acredito que nao adianta estudar orquestracao de workflows sem operar um orquestrador. O Temporal Standalone nasceu da necessidade de ter rodando um Temporal da mesma forma que funciona o cloud — isolamento de namespaces, API keys — sem precisar subir 1 server por projeto dentro do lab.

Esse é o tipo de projeto que sai do learning system: estudar como o Temporal funciona por dentro, depois construir um ambiente operacional. A diferenca pra so ler a docs é que quando vc opera, vc descobre os limits reais — constraints que o agente precisa respeitar, bugs que so aparecem quando o sistema sobe, e customizacoes que nao existem no upstream. Tudo isso vira skill extraida — um contrato que o agente carrega quando precisa operar orquestracao.

A arquitetura

graph TD
    subgraph "192.168.2.68"
        UI["Temporal UI<br/>:8080<br/>Custom Overlays"]
        CAS["Casdoor<br/>:8000<br/>OIDC"]
        SRV["Temporal Server<br/>:7233<br/>v1.31.0"]
        PG["PostgreSQL 18<br/>:5432"]
    end
    Client["Worker / SDK"] -->|gRPC| SRV
    Browser -->|HTTP| UI
    UI -->|OIDC| CAS
    UI -->|gRPC| SRV
    SRV -->|SQL| PG
    CAS -->|SQL| PG

UI customizada — Git Submodule + Overlays

O projeto usa o approach de submodule + overlays: o repo temporalio/ui é um git submodule em ui-custom/upstream/, e as customizacoes vivem em ui-custom/overlays/ com a mesma estrutura de diretorios.

O Dockerfile custom faz o build em tres estagios: copia o upstream, aplica os overlays por cima, builda o frontend (pnpm) e o backend (Go), e gera um container final em Alpine.

Isso significa que git pull no submodule atualiza o upstream sem tocar nas customizacoes. E qualquer mudanca nos overlays requer um rebuild:

docker compose build temporal-ui && docker compose up -d temporal-ui

API Keys

A feature principal dos overlays é o sistema de API Keys. Tres endpoints:

  • GET /api/v1/api-keys — lista keys

  • POST /api/v1/api-keys — cria key

  • DELETE /api/v1/api-keys/:id — deleta key

As keys sao JWTs assinadas com HS256. O middleware diferencia tres tipos de token: cookies OIDC (setados pelo proprio server), bearer tokens OIDC (verificados via JWKS do Casdoor), e bearer tokens de API key (verificados via HS256 + JWT_SECRET).

Constraints

Operar Temporal local tem regras fixas que o agente segue:

  • Nunca usar temporalio/auto-setup — re-roda schema migration a cada restart e pode corromper o banco

  • Nunca modificar o schema manualmente — sempre via temporal-sql-tool

  • O container temporal-server nao tem CLI — operacoes administrativas usam temporal-setup (admin-tools)

  • Dynamic config so aceita keys oficiais do Temporal — validar na docs antes de adicionar

  • UI rebuild necessario apos qualquer mudanca nos overlays

Schema migration

Upgrade de versao do Temporal Server = schema migration obrigatoria. O processo:

docker run --rm --network temporal-network \
  -e DB=postgres12 -e DB_PORT=5432 -e POSTGRES_USER=temporal \
  -e POSTGRES_PWD=temporal -e POSTGRES_SEEDS=postgresql \
  -e DBNAME=temporal \
  temporalio/admin-tools:latest \
  temporal-sql-tool update-schema -d /etc/temporal/schema/postgresql/v12/temporal/versioned

Cada versao do server tem seu conjunto de migration scripts — aplicar na ordem certa, sem pular.

Standalone Activities

O Temporal 1.31 introduziu Standalone Activities em public preview — a ability de executar Activities diretamente via Temporal Client, sem um Workflow wrapper. Ad-hoc scripts, manutencao, testes unitarios de Activities no CLI, sem precisar scaffoldar um workflow inteiro.

Contudo, a feature é controlada por dynamic config (activity.enableStandalone), nao por gRPC. O campo standaloneActivities na resposta de DescribeNamespace é computado dinamicamente a partir da config do server no momento da query.

# config/temporal/dynamicconfig/docker.yaml
activity.enableStandalone:
  - value: true
    constraints:
      namespace: "default"

Habilitado por namespace. Depois é so restartar o server e verificar:

docker exec temporal-setup temporal operator namespace describe default \
  --address temporal:7233

Agent-readable

O projeto inteiro é documentado como skill — ~/projects/temporal-standalone/skills/temporal-standalone/. Um orchestrador classifica a tarefa e roteia pro workflow correto: feature enablement, fix de API keys, upgrade de server, rebuild de UI, ou namespace operations.

skills/temporal-standalone/
  SKILL.md                          <- Orchestrator (roteamento)
  reference/
    routing-matrix.md               <- Task category -> workflow
    api-keys-bugs.md                <- 3 bugs e blueprints de correcao
    standalone-activities.md       <- Feature e como habilitar
  workflows/
    enable-feature/SKILL.md         <- Namespace capabilities
    fix-api-keys/SKILL.md           <- Persistencia + JWT + auth
    server-upgrade/SKILL.md         <- Upgrade + schema migration
    ui-rebuild/SKILL.md             <- Rebuild apos overlay changes
    namespace-ops/SKILL.md          <- CRUD namespaces

O agente carrega so o slice relevante pra tarefa. Precisa fazer upgrade? Carrega server-upgrade. Precisa rebuild da UI? Carrega ui-rebuild. Nunca a estrutura inteira.

A memoria do sistema vive no projeto. Quando algo muda — bug corrigido, feature habilitada, server atualizado — o skill atualiza junto. O repo é a memoria.

Skill que sai do lab

Esse projeto alimenta o learning system de volta. O que o agente aprende operando Temporal vira skill reutilizavel — e a skill com mais potencial dessa pilha é orquestracao com Temporal para agents.

A ideia: workflows de agentes (pipelines de cron, DAGs de multi-step, retry com backoff, orchestration vs choreography) rodando em cima do Temporal Standalone. O agent codifica um workflow em Go ou TypeScript, registra no server, e o Temporal cuida do resto — execution, retry, timeout, state management. Nada de setTimeout improvisado ou retry manual em Python. O Temporal garante que o workflow completa, mesmo se o worker cai no meio.

O standalone é o playground perfeito pra isso. Sem billing, sem latencia de cloud, sem limites de namespace. O agente deploya, testa, quebra, corrige — tudo local. Quando funciona, a skill extraida documentando o pattern (workflow definition, activity registration, error handling, worker lifecycle) vira contrato que outros projetos consomem.

O ciclo do learning: estudou o repo, construiu o ambiente, operou, extraiu a skill. De volta pro learning, pronto pra ser usada no proximo projeto.

Referencia

# temporal-standalone (condensed)
## Architecture
192.168.2.68
  :7233  Temporal Server (gRPC) — v1.31.0, schema v1.19
  :8080  Temporal UI (custom overlays) — v2.49.x base
  :8000  Casdoor (OIDC) — v3.49.0
  :5432  PostgreSQL 18 (temporal, temporal_visibility, casdoor)
## Project Structure
~/projects/temporal-standalone/
  docker-compose.yml           # Main infra
  docker-compose.override.yml  # UI custom build + env vars
  ui-custom/
    upstream/                  # Git submodule (temporalio/ui)
    overlays/                  # Custom Go/Svelte mods
    Dockerfile.custom          # node -> go -> alpine
  config/temporal/dynamicconfig/docker.yaml
  scripts/                     # init-db.sh, setup-namespaces.sh
## Key Commands
# Rebuild UI
docker compose build temporal-ui && docker compose up -d temporal-ui
# Schema migration
docker run --rm --network temporal-network \
  -e DB=postgres12 -e DB_PORT=5432 -e POSTGRES_USER=temporal \
  -e POSTGRES_PWD=temporal -e POSTGRES_SEEDS=postgresql \
  -e DBNAME=temporal \
  temporalio/admin-tools:latest \
  temporal-sql-tool update-schema -d /etc/temporal/schema/postgresql/v12/temporal/versioned
# Namespace ops
docker exec temporal-setup temporal operator namespace list --address temporal:7233
# Enable Standalone Activities (dynamic config)
# activity.enableStandalone: true in docker.yaml, then restart server
## Constraints
- NUNCA auto-setup
- Schema migration sempre via temporal-sql-tool
- Container temporal-server nao tem CLI — usar temporal-setup
- Dynamic config so aceita keys oficiais
- UI rebuild necessario apos overlay changes