Servidor Local (LAN) — Docker
Duas opções de instalação local:
- App Desktop (Electron) — instalador standalone (
.exe/.dmg/.AppImage), sem Docker. Mais simples, recomendado para restaurantes individuais.- Docker (esta página) — para ambientes com sincronização cloud, múltiplas unidades ou preferência por containers.
O modo LAN com Docker permite rodar o PopinaFlow completamente offline, na rede interna do restaurante. Todos os dispositivos da mesma rede Wi-Fi (caixas, tablets, celulares) acessam o sistema sem depender de internet — ideal para ambientes com conectividade instável ou como fallback ao VPS em nuvem.
Arquitetura
docker-compose.local.yml
├── mongo — MongoDB 7 (dados locais, volume ./data/mongo)
├── api — NestJS (porta 3000 interna)
└── front — Nginx servindo o build React (porta 80 pública)O frontend é servido diretamente pela porta 80 (sem porta exposta separada). O nginx faz proxy reverso das rotas /api/* para o container api.
A sincronização com o VPS em nuvem é gerenciada pelo SyncScheduler do NestJS, que roda a cada 5 minutos quando há conexão com a internet.
Pré-requisitos
| Requisito | Versão mínima | Link |
|---|---|---|
| Docker Desktop | 4.x | docker.com/products/docker-desktop |
| Git | 2.x | git-scm.com (necessário apenas para Atualizar-Servidor.bat) |
| Windows | 10 (build 1809+) | — |
| RAM | 2 GB livres | — |
Linux / macOS: use
setup-local.sh(ver seção abaixo). Docker Engine ≥ 24 + Docker Compose v2.
Variáveis de ambiente (backend/.env.local)
Criado automaticamente pelo launcher a partir de backend/.env.local.example.
| Variável | Obrigatório | Descrição |
|---|---|---|
MONGODB_URI | — | Preenchido automaticamente (mongodb://mongo:27017/meu-restaurante) |
JWT_SECRET | sim | Deve ser idêntico ao valor do VPS em nuvem para tokens cross-environment |
SYNC_CLOUD_URL | sim | URL do VPS (ex: https://popinaflow.com) |
SYNC_SERVICE_EMAIL | sim | E-mail da conta de serviço de sync (padrão: sync@system) |
SYNC_SERVICE_PASSWORD | sim | Senha forte — deve existir como superadmin no banco da nuvem |
STORAGE_LOCAL | — | true — salva uploads em disco local em vez de S3 |
FRONTEND_URL | sim | IP/host local para CORS (ex: http://192.168.1.10) |
GOOGLE_CLIENT_ID | opcional | OAuth Google (deixar em branco para desabilitar) |
FOCUS_NFSE_TOKEN | opcional | NF-e / NFS-e (requer internet) |
Launchers Windows
Iniciar-Servidor.bat — uso diário
Dê duplo clique. O script executa automaticamente:
- Verifica Docker instalado
- Verifica a pasta do projeto
- Cria
data\mongoeuploadsse não existirem - Primeira vez: copia
.env.local.example→.env.locale abre o Bloco de Notas para configuração - Executa
docker compose -f docker-compose.local.yml up -d - Health-check em loop (
curl http://localhost/api/health) — até 60 s / 30 tentativas - Detecta IP local via
ipconfig - Abre
http://localhostno navegador padrão
Atualizar-Servidor.bat — após novas versões
git pull origin main
docker compose -f docker-compose.local.yml up -d --buildFaz pull do repositório, reconstrói as imagens e reinicia os containers. O arquivo .env.local não é sobrescrito.
Iniciar-Servidor.ps1 — variante PowerShell
Equivalente ao .bat com melhorias visuais (barra de progresso, MessageBox de confirmação). Útil para distribuir como .exe.
build-exe.ps1 — compilar .exe distribuível
Usa o módulo ps2exe (instalado automaticamente do PowerShell Gallery) para compilar Iniciar-Servidor.ps1 em um .exe standalone.
# Com janela de terminal (recomendado)
.\build-exe.ps1
# Sem janela de terminal (só MessageBox)
.\build-exe.ps1 -NoConsole
# Exige privilégios de administrador
.\build-exe.ps1 -RequireAdminO arquivo gerado Iniciar-Servidor.exe pode ser distribuído para o restaurante sem necessidade de PowerShell instalado.
Launcher Linux / macOS (setup-local.sh)
bash setup-local.shO script interativo pede JWT_SECRET, SYNC_SERVICE_PASSWORD e IP local, preenche o .env.local via sed e inicia os containers com --build.
Sincronização com a nuvem
O SyncScheduler (backend/src/sync/sync.scheduler.ts) executa a cada 5 minutos:
- Autentica via service account JWT no VPS
- Envia pedidos e reservas não sincronizados (
_syncedAt: null) - Baixa atualizações de cardápio, mesas e usuários
- Marca registros com
_syncedAt: Date
Para disparar manualmente:
POST /api/sync/trigger
Authorization: Bearer <admin-jwt>Modo Offline
Detecção de conexão
SyncService.checkInternet() faz uma requisição HEAD para SYNC_CLOUD_URL com timeout de 3 s antes de cada ciclo do scheduler. Se a requisição falhar ou expirar, o ciclo é marcado como offline e nenhum push/pull é tentado.
Fila de pendências
Pedidos e reservas criados enquanto offline ficam com _syncedAt: null no MongoDB local. O campo _syncedAt?: Date existe nos schemas Order e Reservation. A query que localiza pendências usa o índice composto { tenant, _syncedAt } para eficiência.
Fluxo de dados
PUSH (local → nuvem)
- O scheduler consulta pedidos/reservas com
_syncedAt: null - Envia via
POST /api/sync/receive-ordersePOST /api/sync/receive-reservationsno VPS - Após confirmação HTTP 200/201 do VPS, marca
_syncedAt: new Date()localmente
PULL (nuvem → local)
- Cardápio, mesas e usuários são baixados via
GET /api/sync/menueGET /api/sync/users - Fire-and-forget — erros no pull não bloqueiam nem revertem o push
Resolução de conflitos
O endpoint de recebimento no VPS usa $setOnInsert em um upsert por orderNumber. Isso garante que dados já existentes na nuvem não são sobrescritos — o primeiro registro vence.
Endpoint de status (público)
GET /api/sync/statusSem autenticação. Retorna:
{
"syncEnabled": true,
"online": false,
"pendingOrders": 12,
"pendingReservations": 3,
"lastSync": "2026-03-13T18:45:00.000Z",
"syncing": false
}Badge de status (SyncStatusBadge.tsx)
Polled a cada 30 s no AdminLayout. Visível apenas quando syncEnabled: true (instâncias cloud omitem SYNC_ENABLED).
| Estado | Condição |
|---|---|
| 🟢 Verde | online: true e pendingOrders + pendingReservations = 0 |
| 🟡 Amarelo | syncing: true ou há pendências aguardando envio |
| 🔴 Vermelho | online: false — exibe contagem de pendências |
Operações que requerem internet
As seguintes funcionalidades dependem de endpoints externos e falharão se a internet estiver indisponível, mesmo na instância local:
- NF-e / NFS-e — envia dados para a API Focus NFe (
focusNfeTokenobrigatório) - EFD fiscal — geração de arquivos SPED via API externa
- Upload de imagens — salvos no S3 (bucket externo)
Todas as outras operações — cardápio, PDV, pedidos, mesas, reservas, kitchen display, fechar conta — funcionam completamente offline.
SYNC_ENABLED=false
No VPS em nuvem essa variável é omitida ou definida como false. O resultado é:
- O badge de status fica oculto no
AdminLayout - O
SyncSchedulernão roda — sem loops de sync de volta para si mesmo
Acessar de outros dispositivos
Após iniciar o servidor, o launcher exibe o IP local detectado via ipconfig. Todos os dispositivos na mesma rede Wi-Fi podem acessar usando esse IP:
http://192.168.X.X/t/demo/menu ← Cardápio (clientes)
http://192.168.X.X/t/demo/admin ← Painel admin
http://192.168.X.X/t/demo/kitchen ← Display cozinhaComandos Docker úteis
# Parar todos os containers
docker compose -f docker-compose.local.yml down
# Ver logs em tempo real
docker compose -f docker-compose.local.yml logs -f
# Ver logs apenas da API
docker compose -f docker-compose.local.yml logs -f api
# Status dos containers
docker compose -f docker-compose.local.yml ps
# Reiniciar apenas a API
docker compose -f docker-compose.local.yml restart api
# Rebuild completo (após mudanças no código)
docker compose -f docker-compose.local.yml up -d --buildTroubleshooting
| Problema | Causa provável | Solução |
|---|---|---|
| "Docker não encontrado" | Docker Desktop não instalado | Instalar e reiniciar o PC |
Containers sobem mas api cai | JWT_SECRET em branco | Editar backend/.env.local e reiniciar |
| Sync não funciona | SYNC_SERVICE_PASSWORD errado | Verificar se a conta existe no VPS |
| Porta 80 em uso | Outro serviço na porta 80 | Parar IIS / Apache ou alterar porta no docker-compose.local.yml |
| Browser não abre automaticamente | Firewall bloqueando | Acessar http://localhost manualmente |