Skip to content

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

RequisitoVersão mínimaLink
Docker Desktop4.xdocker.com/products/docker-desktop
Git2.xgit-scm.com (necessário apenas para Atualizar-Servidor.bat)
Windows10 (build 1809+)
RAM2 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ávelObrigatórioDescrição
MONGODB_URIPreenchido automaticamente (mongodb://mongo:27017/meu-restaurante)
JWT_SECRETsimDeve ser idêntico ao valor do VPS em nuvem para tokens cross-environment
SYNC_CLOUD_URLsimURL do VPS (ex: https://popinaflow.com)
SYNC_SERVICE_EMAILsimE-mail da conta de serviço de sync (padrão: sync@system)
SYNC_SERVICE_PASSWORDsimSenha forte — deve existir como superadmin no banco da nuvem
STORAGE_LOCALtrue — salva uploads em disco local em vez de S3
FRONTEND_URLsimIP/host local para CORS (ex: http://192.168.1.10)
GOOGLE_CLIENT_IDopcionalOAuth Google (deixar em branco para desabilitar)
FOCUS_NFSE_TOKENopcionalNF-e / NFS-e (requer internet)

Launchers Windows

Iniciar-Servidor.bat — uso diário

Dê duplo clique. O script executa automaticamente:

  1. Verifica Docker instalado
  2. Verifica a pasta do projeto
  3. Cria data\mongo e uploads se não existirem
  4. Primeira vez: copia .env.local.example.env.local e abre o Bloco de Notas para configuração
  5. Executa docker compose -f docker-compose.local.yml up -d
  6. Health-check em loop (curl http://localhost/api/health) — até 60 s / 30 tentativas
  7. Detecta IP local via ipconfig
  8. Abre http://localhost no navegador padrão

Atualizar-Servidor.bat — após novas versões

git pull origin main
docker compose -f docker-compose.local.yml up -d --build

Faz 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.

powershell
# 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 -RequireAdmin

O arquivo gerado Iniciar-Servidor.exe pode ser distribuído para o restaurante sem necessidade de PowerShell instalado.

Launcher Linux / macOS (setup-local.sh)

bash
bash setup-local.sh

O 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:

  1. Autentica via service account JWT no VPS
  2. Envia pedidos e reservas não sincronizados (_syncedAt: null)
  3. Baixa atualizações de cardápio, mesas e usuários
  4. Marca registros com _syncedAt: Date

Para disparar manualmente:

http
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)

  1. O scheduler consulta pedidos/reservas com _syncedAt: null
  2. Envia via POST /api/sync/receive-orders e POST /api/sync/receive-reservations no VPS
  3. 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/menu e GET /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)

http
GET /api/sync/status

Sem autenticação. Retorna:

json
{
  "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).

EstadoCondição
🟢 Verdeonline: true e pendingOrders + pendingReservations = 0
🟡 Amarelosyncing: true ou há pendências aguardando envio
🔴 Vermelhoonline: 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 (focusNfeToken obrigató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 SyncScheduler nã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 cozinha

Comandos Docker úteis

bash
# 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 --build

Troubleshooting

ProblemaCausa provávelSolução
"Docker não encontrado"Docker Desktop não instaladoInstalar e reiniciar o PC
Containers sobem mas api caiJWT_SECRET em brancoEditar backend/.env.local e reiniciar
Sync não funcionaSYNC_SERVICE_PASSWORD erradoVerificar se a conta existe no VPS
Porta 80 em usoOutro serviço na porta 80Parar IIS / Apache ou alterar porta no docker-compose.local.yml
Browser não abre automaticamenteFirewall bloqueandoAcessar http://localhost manualmente

Lançado sob a licença MIT.