Como Rodar — Local e Produção
1. Rodando localmente (desenvolvimento)
Pré-requisitos
- Docker Desktop (Mac/Windows) ou Docker Engine + Docker Compose (Linux)
- Portas livres:
3000,3001,4242,5432,6379,8000,8042,8043 - Git
Passo a passo
1. Clone o repositório.env separados:
| Arquivo | Para quê serve |
|---|---|
.env (raiz) | Infra Docker: credenciais do Postgres, Redis, Orthanc, ORTHANC_WEBHOOK_SECRET |
backend/.env | Laravel: APP_KEY, JWT_SECRET, DB_*, REDIS_*, FRONTEND_URL, ORTHANC_WEBHOOK_SECRET |
frontend/.env.local | Next.js: NEXT_PUBLIC_API_BASE_URL e NEXT_PUBLIC_OHIF_VIEWER_URL — opcional em dev com Docker (o docker-compose.yml já define os valores para localhost:8000 / localhost:3001). Necessário apenas se for rodar o frontend fora do Docker (npm run dev direto) ou sobrescrever os padrões |
backend/.env e preencha obrigatoriamente:
Os valores deDB_*eREDIS_*nobackend/.envjá apontam para os containers Docker — não mude em dev.
3. Suba a stackALLOWED_ORIGINno.envraiz não precisa ser preenchida em dev — o compose usa o fallbackhttp://localhost:3001automaticamente.
healthy:
backend/.env em JWT_SECRET= e recarregue:
- Super admin padrão (verifique
database/seeders/para o email/senha) - Tenant de exemplo
- Unidade de exemplo
| Serviço | URL |
|---|---|
| Frontend | http://localhost:3000 |
| Backend API | http://localhost:8000 |
| OHIF Viewer | http://localhost:3001 |
| Orthanc (web) | http://localhost:8042 |
| Proxy DicomWeb | http://localhost:8043 |
Comandos do dia a dia
Troubleshooting comum
“Carregando sessão…” infinito no login Causas mais comuns:-
Acessando via
127.0.0.1:3000em vez delocalhost:3000O CORS do backend permite apenasFRONTEND_URL(defaulthttp://localhost:3000). O browser bloqueia silenciosamente requests de origens diferentes — a sessão nunca resolve. Fix: sempre acesse viahttp://localhost:3000. -
Backend retornando 500 em vez de 401 em rotas protegidas
O middleware
Authenticatedo Laravel tenta redirecionar para a rota nomeadaloginque não existe em apps API-only. Fix: já resolvido embootstrap/app.php—AuthenticationExceptionretorna JSON 401. -
Workers RoadRunner crashando após restart
Após
docker compose restart backend, aguarde alguns segundos antes de acessar o app.
bootstrap/app.php. Se voltar a aparecer, verifique se o arquivo foi alterado:
docker compose down -v, sessão trava
O banco foi zerado mas cookies antigos persistem no browser. Limpe os cookies de localhost no DevTools e rode as migrations:
Testando o webhook do Orthanc manualmente
Recriando um container específico
Resetando tudo do zero
2. Git Workflow e Releases
Estrutura de branches
| Branch | Função | Quem commita diretamente |
|---|---|---|
main | Código estável em produção — o servidor sempre roda daqui | Ninguém — só via PR |
develop | Integração do dia a dia — branch base para o trabalho | Correções pequenas e commits de infra |
feat/*, fix/*, infra/* | Branches temporárias para features e correções maiores | O desenvolvedor da tarefa |
Fluxo de trabalho diário
- Commitar direto no
develop: correções pequenas de bug, ajustes de config, documentação, infra sem risco de quebrar nada - Criar branch
feat/*: features novas, refatorações, qualquer coisa que possa afetar outras partes do sistema ou precise de revisão antes de integrar
Como fazer um deploy (promote develop → main)
O servidor sempre rodamain. Para publicar o que está em develop:
1. Abrir um Pull Request no GitHub
Release: hardening de segurança + infra de produção).
2. No servidor, atualizar e reconstruir
Situação atual — primeiro deploy
Todo o trabalho realizado até agora está na branchdevelop e ainda não foi mesclado na main. Antes do primeiro deploy no servidor, é necessário fazer o merge:
A forma recomendada é abrir um PR no GitHub (develop → main) para ter um registro histórico do que foi para produção e quando.
3. Rodando em produção
Pré-requisitos do servidor
- Ubuntu 22.04 LTS ou Debian 12 (recomendado)
- Docker Engine + Docker Compose Plugin
- Mínimo: 2 vCPU, 4 GB RAM, 40 GB disco
- Domínio apontando para o IP do servidor (ex:
app.suaempresa.com.br) - Portas abertas no firewall:
80,443(e opcionalmente4242para receber DICOM)
Passo a passo
0. Certifique-se de quedevelop foi mesclado em main
Antes de clonar o servidor, o código precisa estar na main. Abra um PR no GitHub (develop → main) e faça o merge. Veja a seção Git Workflow e Releases para o passo a passo.
1. Configure o acesso ao repositório privado no servidor
O repositório é privado — o servidor precisa de permissão para clonar. A forma recomendada é uma Deploy Key (chave SSH sem passphrase, somente leitura):
Em produção não é necessário criarGere os secrets antes de preencher:frontend/.env. O Dockerfile.prod embute asNEXT_PUBLIC_*no bundle em compile time viaARG, lendo os valores direto do.envraiz. Ofrontend/.env.localserve só para dev fora do Docker.
.env raiz com os valores gerados e os domínios reais:
Edite oACME_EMAILé usado pelo Caddy para emitir certificados via Let’s Encrypt. Os limites de CPU acima são para um servidor de 1 CPU — ajuste proporcionalmente para servidores maiores (ex: 4 CPUs →BACKEND_CPU_LIMIT=2).
backend/.env:
ORTHANC_USERNAMEeORTHANC_PASSWORD— deixe em branco. O Orthanc está comAuthenticationEnabled: falsenoorthanc.json; quem protege o acesso é o nginx proxy via token temporário.
JWT_SECRET — deixe em branco por enquanto. Será gerado após o primeiro build (passo 6).
3.5. Edite o Caddyfile com os seus domínios OJWT_TTL=480— o token expira em 8 horas. O frontend renova automaticamente viaPOST /api/refreshquando recebe 401, sem exigir novo login. O refresh é válido por 2 semanas (padrão tymon) — após esse período, o usuário precisa fazer login novamente.
docker/caddy/Caddyfile tem os subdomínios hardcoded no repositório (usados no ambiente de homolog). Antes de subir a stack, troque pelos domínios reais do seu servidor:
.env e backend/.env:
Três armadilhas comuns aqui:4. Suba a stack de produção
- Underscore em domínio é inválido —
technik_api.seudominio.comnão resolve em DNS. Use sempre hífen:technik-api.seudominio.com.- Caddyfile,
.enve DNS têm que bater exatamente. Se o Caddyfile listaapp.seudominio.commas o.envdizNEXT_PUBLIC_API_BASE_URL=https://api.outro-dominio.com/api, o frontend quebra em runtime.NEXT_PUBLIC_*são compiladas no build do Next.js. Se você trocar os domínios no.envraiz depois do primeiro build, é obrigatóriodocker compose ... up -d --build frontend.
ONão rode--forceé necessário emAPP_ENV=productionpara confirmar que você quer rodar migrations.
--seed em produção — o seed é para dados de desenvolvimento.
8. Configure o DNS
O sistema usa três subdomínios. O proxy DicomWeb (Orthanc) é servido como path sob o viewer (/dicom-proxy/), sem precisar de subdomínio próprio.
| Subdomínio | Serviço |
|---|---|
app.suaempresa.com.br | Frontend (Next.js) |
api.suaempresa.com.br | Backend (Laravel) |
viewer.suaempresa.com.br | OHIF Viewer + Proxy DicomWeb (/dicom-proxy/) |
SubstituaVerificando a propagação DNS (aguarde de 1 a 30 minutos após criar os registros):203.0.113.10pelo IP real do servidor (curl -4 ifconfig.me). Se usar Cloudflare, desative o proxy (nuvem laranja) para evitar conflitos com o TLS do Caddy.
4242 é usada por modalities (tomógrafos, RX) para enviar imagens. Não precisa de subdomínio — as modalities são configuradas com o IP direto do servidor:
9. Suba o Caddy (reverse proxy + TLS automático) O Caddy já está incluído no
docker-compose.prod.yml como container. A config está em docker/caddy/Caddyfile e é montada automaticamente.
Só execute este passo (e o build da stack) após o DNS ter propagado — o Caddy usa os domínios para emitir os certificados via Let’s Encrypt. Se os domínios não apontarem para o servidor, a emissão falhará.Certifique-se de que
ACME_EMAIL está preenchido no .env raiz e que as portas 80 e 443 estão abertas no firewall:
caddy_data.
Troubleshooting — NXDOMAIN no log do Caddy:
Se docker logs pacs_caddy mostrar algo como:
- Os 3 registros A realmente existem no provedor de DNS? →
dig +short app.seudominio.com.br - Os domínios no
Caddyfilebatem exatamente com o DNS e com o.env? - Se usar Cloudflare, o proxy (nuvem laranja) está desativado? Com proxy ativo o challenge TLS-ALPN do Caddy não fecha — desative até o primeiro cert ser emitido.
- As portas 80 e 443 estão abertas no firewall do servidor e do provedor cloud (security group / ufw)?
10. Crie o primeiro usuário super admin O seed não é rodado em produção. Para criar o primeiro acesso é preciso criar um tenant da plataforma + uma unit principal + o super admin (o campopacs_orthanc_cors_proxy— container nginx que autentica as requisições DicomWeb do OHIF antes de repassar ao Orthanc. Em produção é acessado via path/dicom-proxy/sob o subdomínioviewer.*. Sobe junto com a stack — sem ele o OHIF não consegue carregar imagens.
tenant_id em users é NOT NULL — mesmo o super admin precisa pertencer a um tenant; o comportamento “acesso global” vem da flag is_super_admin, não da ausência de tenant).
Troque o email e a senha imediatamente após o primeiro login.
Este tenant/unit da plataforma é interno — os tenants reais das clínicas você cria depois pelo painel super admin (/admin/tenants).
11. Verifique o status
Comandos de produção
Fazendo um novo deploy (fluxo completo)
Passo 1 — no repositório (local ou GitHub): mesclardevelop em main
A forma recomendada é abrir um Pull Request no GitHub:
O Dockerfile.prod já rodaconfig:cache,route:cacheeview:cachedurante o build. Oartisan config:clearsó é necessário se você alterar variáveis de ambiente sem fazer rebuild — nesse caso, recarregue apenas o backend:
Backup
O projeto incluiscripts/backup.sh — faz dump comprimido do PostgreSQL com retenção de 7 dias.
Configurar cron (uma vez, no servidor):
pacs_orthanc_data. O Docker Compose prefixa automaticamente o nome com ${COMPOSE_PROJECT_NAME}_ — com o padrão do projeto resulta em pacs_cloud_pacs_orthanc_data. Para localizar no host:
Checklist pré-deploy
Antes de cada deploy em produção, verifique: Variáveis de ambiente —backend/.env:
-
APP_ENV=production -
APP_DEBUG=false -
APP_KEYgerada (php -r "echo 'base64:'.base64_encode(random_bytes(32)).PHP_EOL;") -
APP_URL=https://api.suaempresa.com.br -
LOG_LEVEL=error -
JWT_SECRETgerada comphp artisan jwt:secret -
JWT_TTL=480(8 horas) -
FRONTEND_URL=https://app.suaempresa.com.br -
SESSION_SECURE_COOKIE=true -
DB_PASSWORD/REDIS_PASSWORD/ORTHANC_WEBHOOK_SECRET(mesmos do.envraiz)
.env raiz:
-
REDIS_PASSWORD(forte, gerada comopenssl rand -hex 16) -
ORTHANC_WEBHOOK_SECRET(gerada comopenssl rand -hex 32) -
NEXT_PUBLIC_API_BASE_URL=https://api.suaempresa.com.br/api -
NEXT_PUBLIC_OHIF_VIEWER_URL=https://viewer.suaempresa.com.br -
ALLOWED_ORIGIN=https://viewer.suaempresa.com.br -
ACME_EMAIL=seuemail@suaempresa.com.br -
BACKEND_CPU_LIMIT/FRONTEND_CPU_LIMIT/WORKER_CPU_LIMIT/QUEUE_CPU_LIMIT(nunca excedernproc)
- Registros A criados para
app,apieviewerapontando para o IP do servidor - DNS propagado —
dig +short api.suaempresa.com.brretorna o IP correto - Portas
80e443abertas no firewall (necessárias para o Caddy e o Let’s Encrypt) - Porta
4242aberta no firewall (para receber DICOM das modalities) - Container
pacs_caddyrodando —docker logs pacs_caddy --tail 20mostra certificado emitido - Certificado TLS válido —
curl https://api.suaempresa.com.br/upretorna 200
-
docker compose ps— todos os containershealthyourunning -
curl https://api.suaempresa.com.br/upretorna 200
DEV-TO-PROD.md.