CatalystOps é uma plataforma SaaS para gestão de operações e serviços técnicos, desenvolvida em Ruby on Rails e preparada para rodar em Docker tanto em desenvolvimento quanto em produção.
Este projeto foi estruturado para que o ambiente local seja o mais próximo possível do ambiente real de execução. A ideia do repositório é simples:
- Rails para a aplicação web
- PostgreSQL para persistência
- Redis para fila e cache
- Sidekiq para jobs em background
- Nginx para roteamento por subdomínio
O CatalystOps permite que empresas prestadoras de serviços técnicos organizem:
- clientes
- ordens de serviço
- técnicos e atribuições
- agenda de atendimento
- anexos
- suporte interno
- base de conhecimento
- assinaturas e operação SaaS
| Componente | Função |
|---|---|
| Ruby on Rails | aplicação principal |
| PostgreSQL 15 | banco de dados |
| Redis 7 | fila e cache |
| Sidekiq | processamento assíncrono |
| Nginx | proxy reverso / subdomínios |
| Docker Compose | orquestração local |
Os serviços são definidos em docker-compose.yml e, em desenvolvimento, complementados por docker-compose.override.yml.
Serviços principais:
| Serviço | Container | Papel no ambiente |
|---|---|---|
web |
catalystops-app |
executa o Rails |
sidekiq |
catalystops-app |
processa jobs em background |
db |
postgres:15 |
banco de dados |
redis |
redis:7 |
fila do Sidekiq e cache |
nginx |
nginx:latest |
entrada HTTP/HTTPS e subdomínios |
Fluxo simplificado:
cliente -> nginx -> web (Rails) -> PostgreSQL
cliente -> nginx -> web (Rails) -> Redis -> Sidekiq
Para rodar o projeto localmente, você precisa ter:
- Docker
- Docker Compose
- acesso ao arquivo
config/master.keyou ao valor deRAILS_MASTER_KEY
Você não precisa instalar Ruby, Rails, PostgreSQL nem Redis diretamente na sua máquina se for usar o fluxo padrão com Docker.
As configurações ficam em .env. Um exemplo base existe em .env_example.
Crie o arquivo local com:
cp .env_example .envPrincipais variáveis:
MP_TEST_ACCESS_TOKEN='TEST_ACCESS_TOKEN_EXAMPLE'
MP_PRODUCTION_ACCESS_TOKEN='APP_USR_PRODUCTION_ACCESS_TOKEN_EXAMPLE'
MP_PUBLIC_KEY='APP_USR_PUBLIC_KEY_EXAMPLE'
MP_PUBLIC_KEY_TEST='TEST_PUBLIC_KEY_EXAMPLE'
MP_WEBHOOK_SECRET='WEBHOOK_SECRET_EXAMPLE'
RAILS_ENV=development
POSTGRES_DB=catalyst_ops_development
POSTGRES_USER=user_database
POSTGRES_PASSWORD=password_database
POSTGRES_PORT=5432
REDIS_PORT=6379
WEB_PORT=3000
RAILS_LOG_TO_STDOUT=true
RAILS_MASTER_KEY='your_rails_master_key_here'
SECRET_KEY_BASE='your_secret_key_base_here'
PRECOMPILE_ASSETS=0
ASSETS_SECRET_KEY_BASE='your_assets_secret_key_base_here'Observações:
- em desenvolvimento, use
RAILS_ENV=development - para desenvolvimento,
PRECOMPILE_ASSETS=0costuma ser suficiente RAILS_MASTER_KEYprecisa bater com as credenciais do projetoSECRET_KEY_BASEpode ser gerada combin/rails secret
O projeto utiliza subdomínios para separar áreas da aplicação:
catalystops.localapp.catalystops.locallogin.catalystops.localadmin.catalystops.localregister.catalystops.localwebhook.catalystops.localsidekiq.catalystops.local
No macOS ou Linux:
sudo nano /etc/hostsAdicione:
127.0.0.1 catalystops.local
127.0.0.1 app.catalystops.local
127.0.0.1 login.catalystops.local
127.0.0.1 admin.catalystops.local
127.0.0.1 register.catalystops.local
127.0.0.1 webhook.catalystops.local
127.0.0.1 sidekiq.catalystops.local
Depois de subir o ambiente:
https://app.catalystops.localhttps://login.catalystops.localhttps://admin.catalystops.localhttps://register.catalystops.localhttps://webhook.catalystops.localhttps://sidekiq.catalystops.local
Opcionalmente, você também pode acessar o Rails diretamente por:
http://localhost:3000
Na raiz do projeto:
docker compose up -d --buildIsso sobe:
dbrediswebsidekiqnginx
docker compose exec web bin/rails db:create db:migratedocker compose exec web bin/rails db:seeddocker compose ps
docker compose logs -f web
docker compose logs -f sidekiqO arquivo docker-compose.override.yml monta o diretório do projeto dentro dos containers web e sidekiq:
services:
web:
volumes:
- .:/rails
- ./storage:/rails/storage
sidekiq:
volumes:
- .:/rails
- ./storage:/rails/storageNa prática, isso significa:
- alterações em Ruby, views, JS e CSS refletem imediatamente no container
- em geral, não é necessário rebuild quando a mudança é apenas de código
- se você alterar gems, é recomendável rodar
bundle installno container e reiniciarwebesidekiq
Exemplo:
docker compose exec web bundle install
docker compose restart web
docker compose restart sidekiqdocker compose logs -f web
docker compose logs -f sidekiq
docker compose logs -f nginx
docker compose logs -f dbdocker compose exec web bin/rails consoledocker compose exec web bin/rails runner "puts Company.count"docker compose exec web bin/rails db:migrate
docker compose exec web bin/rails db:rollbackdocker compose exec web bin/rails testdocker compose restart web
docker compose restart sidekiq
docker compose restart nginxdocker compose downUse com cuidado. Isso apaga volumes e banco local:
docker compose down -v
docker compose up -d --buildO arquivo db/seeds.rb carrega seeds em duas etapas:
- tudo que estiver em
db/seeds/common - tudo que estiver em
db/seeds/<ambiente>
No ambiente development, a ordem atual é:
db/seeds/common/0-prepare.rbdb/seeds/common/1.plans.rbdb/seeds/development/2-companies.rbdb/seeds/development/3-users.rbdb/seeds/development/4-clients.rbdb/seeds/development/5-order_services.rbdb/seeds/development/6-assignments.rbdb/seeds/development/7-service_items.rbdb/seeds/development/8-knowledge_base_articles.rbdb/seeds/development/9-support_tickets.rbdb/seeds/development/9a-support_messages.rb
O seed db/seeds/common/0-prepare.rb limpa os dados antigos antes de recriar os registros. Essa limpeza usa disable_referential_integrity para evitar erro de chave estrangeira durante delete_all.
Se o db:seed falhar, confira primeiro:
- se o banco está acessível
- se as migrations estão atualizadas
- se o comando está rodando dentro do container correto
Redis é usado para:
- fila do Sidekiq
- cache, quando habilitado
Para jobs funcionarem corretamente:
redisprecisa estar em execuçãosidekiqprecisa estar em execução- o
REDIS_URLprecisa apontar para o containerredis
Cheque rapidamente:
docker compose ps
docker compose logs -f sidekiqOs agendamentos recorrentes são registrados no startup do Sidekiq via
config/initializers/sidekiq_schedules.rb,
usando Sidekiq::Cron::Job.load_from_hash!.
Timezone configurado para os agendamentos:
America/Sao_Paulo
Cron jobs atuais:
| Job | Cron | Frequência | Objetivo |
|---|---|---|---|
MarkOverdueOrderServicesJob |
* * * * * |
a cada minuto | marca OS como atrasadas quando o horário agendado já passou |
Subscriptions::CycleSubscriptionsJob |
0 10 * * * |
diariamente às 10:00 | cicla assinaturas aptas para renovação |
Subscriptions::NotifyOverdueSubscriptionsJob |
0 9 * * * |
diariamente às 09:00 | notifica assinaturas vencidas há 5 dias |
Subscriptions::ExpireOverdueSubscriptionsJob |
0 11 * * * |
diariamente às 11:00 | expira assinaturas vencidas há 10 dias ou mais |
Subscriptions::ReconcileSubscriptionsJob |
0 12 * * * |
diariamente às 12:00 | reconcilia status local de assinaturas com o Mercado Pago |
Observações:
- esses cron jobs só ficam ativos com o processo
sidekiqem execução - a fila usada nesses agendamentos é
default - a concorrência base do Sidekiq está em
config/sidekiq.yml - para validar se os agendamentos foram carregados, acompanhe os logs do container
sidekiq
Os jobs de reconciliação/reprocessamento usam janela para reduzir chamadas ao gateway:
Subscriptions::ReconcileSubscriptionsJobusasubscriptions.updated_ate env:SUBSCRIPTIONS_RECONCILIATION_WINDOW_DAYSSubscriptions::ReprocessPendingPaymentsJobusa query operacional de pendentes sem webhook processado e env:SUBSCRIPTIONS_PENDING_REPROCESS_WINDOW_DAYS
Padrão para ambas:
- últimos
30dias - se valor ausente/inválido (
0ou negativo), fallback automático para30
Exemplo:
SUBSCRIPTIONS_RECONCILIATION_WINDOW_DAYS=30
SUBSCRIPTIONS_PENDING_REPROCESS_WINDOW_DAYS=30Os envios de email usam deliver_later, então passam pelo Sidekiq.
Isso vale para fluxos como:
- boas-vindas de usuário
- notificações de ordens de serviço
- sugestões enviadas pela área de suporte
No model app/models/order_service.rb, existem callbacks que enfileiram emails em diferentes mudanças de estado:
after_create :notify_createafter_update :notify_completeafter_update :notify_scheduledafter_update :notify_finishedafter_update :notify_in_progressafter_update :notify_overdue
Esses callbacks chamam o OrderServiceMailer.
Exemplo comum:
Performing ActionMailer::MailDeliveryJob ...
Rendered order_service_mailer/notify_create.html.erb ...
Rendered layout layouts/mailer.html.erb ...
Isso significa:
- o job foi retirado da fila
- o mailer começou a ser executado
- a view do email foi renderizada
Isso ainda não garante entrega final. Para considerar sucesso, o log precisa fechar sem erro, normalmente com Performed ActionMailer::MailDeliveryJob ou sem WARN e ERROR subsequentes para o mesmo job.
Se houver atraso grande entre enqueued_at e Performing, normalmente existe backlog ou o sidekiq ficou parado durante algum período.
O projeto também possui documentação funcional em docs/:
Esses arquivos são usados como base para artigos da base de conhecimento.
Verifique se o seed de limpeza está atualizado em db/seeds/common/0-prepare.rb e se o comando foi rodado depois da correção.
Confira:
docker compose psdocker compose logs -f sidekiqdocker compose logs -f redis
Se os jobs estiverem acumulados e depois forem processados de uma vez, isso normalmente indica indisponibilidade temporária do sidekiq.
Se foi mudança de gem ou dependência:
docker compose exec web bundle install
docker compose restart web
docker compose restart sidekiqSe foi mudança simples de código Ruby ou view, normalmente basta recarregar a página.
Este projeto é Docker-first. Se você rodar bin/rails diretamente na máquina hospedeira, pode encontrar divergências de Ruby, Bundler ou plataforma do Gemfile.
O caminho recomendado é executar comandos Rails dentro do container web.
- subir containers
- rodar
db:create db:migrate - rodar
db:seed - acessar
app.catalystops.local - acompanhar
webesidekiqnos logs durante novos fluxos
Para produção, o fluxo geral é:
- preparar
.envcom valores reais - subir containers com build
- executar migrations
- validar conectividade com banco e redis
- configurar DNS e SSL
- monitorar logs de
web,nginxesidekiq
O deploy de produção é acionado por workflow do GitHub Actions em
.github/workflows/deploy.yml,
que executa bin/deploy no servidor via SSH.
Pontos importantes do bin/deploy:
- roda
git fetche identifica arquivos alterados entreHEADlocal eorigin/main - decide quando precisa rebuild/restart de
webesidekiq - executa
docker compose run --rm web bundle exec rails db:migratepara aplicar migrations pendentes - não encerra cedo apenas por não haver commit novo; ainda valida migrations
- ao final de um deploy bem-sucedido, remove cache de build e imagens Docker
não usadas há mais de 7 dias (
DOCKER_PRUNE_UNTIL=168h), sem remover volumes
Observação operacional:
- em produção, o container
webusa código da imagem Docker - se houver alteração que exige imagem nova (ex.:
db/migrate,app/*,config/*,lib/*), o deploy precisa rebuild para o container enxergar o novo código - a limpeza pode ser desabilitada com
PRUNE_DOCKER_AFTER_DEPLOY=falseou ajustada comDOCKER_PRUNE_UNTIL=72h,DOCKER_PRUNE_UNTIL=336h, etc.
Existe workflow de política em
.github/workflows/policy-window.yml
para PRs da main.
Regras atuais:
- mudança sensível (ex.:
db/migrate/*,app/*,config/*,lib/*,Dockerfile,Gemfile.lock) só passa na janela OFF:22:00-06:00BRT - mudança simples só passa na janela comercial:
09:00-18:00BRT - se houver migration no PR, ele deve conter apenas
db/migrate/*e opcionalmentedb/schema.rb
- Compose principal:
docker-compose.yml - Override de desenvolvimento:
docker-compose.override.yml - Exemplo de ambiente:
.env_example - Runbook de deploy/rollback:
docs/operacao/runbook_deploy_rollback_producao.md - Monitoramento de erros (Sentry):
docs/operacao/monitoramento_erros_sentry.md - Seeds:
db/seeds.rb - Mailer de OS:
app/mailers/order_service_mailer.rb - Model de OS:
app/models/order_service.rb
Se algo não funcionar como esperado:
- revise o
.env - confira se os containers estão saudáveis
- valide migrations e seeds
- acompanhe logs de
webesidekiq - consulte a documentação em
docs/