Umbrel
EN

Como rodar o Twrctrl no Docker: Simulador ATC no navegador

Aprenda a rodar o simulador ATC Twrctrl no Docker com Caddy. Configure WebSockets estáveis e gerencie o tráfego aéreo pelo navegador no seu homelab.

Áudio Narrado Ouça este artigo
00:00 / 00:00

O Twrctrl chamou bastante atenção no Hacker News recentemente ao apresentar um simulador de controle de tráfego aéreo direto no navegador, com código aberto e foco em operações de torre e solo. Quem já teve contato com ferramentas tradicionais desse meio — como EuroScope, VRC ou softwares comerciais para desktop do tipo Tower!3D — conhece bem o custo operacional: você lida com pacotes de dados de dezenas de gigabytes, dependência chata de ambientes Windows antigos, liberação manual de portas UDP para conexões diretas e arquivos de configuração que parecem saídos diretamente de 2004.

O Twrctrl adota a postura contrária. O servidor roda como um processo enxuto que gerencia coordenadas de aeronaves, ocupação de pistas e atualização de strips de voo. Enquanto isso, o cliente opera 100% dentro de uma aba do navegador via WebSockets. No homelab, essa arquitetura é uma mão na roda. Dá para empacotar o backend em um container Docker bem isolado, colocá-lo atrás do seu reverse proxy atual e abrir uma sessão de controle em um notebook fraco, em um iPad ou no seu desktop sem precisar compilar ou instalar nenhum binário localmente.

Colocar essa estrutura de pé com estabilidade exige alguns cuidados técnicos. Conexões WebSocket não funcionam como requisições HTTP comuns sem estado, e longos períodos com a pista vazia podem acionar os timeouts do seu proxy reverso. Abaixo, detalhamos como essa arquitetura funciona, como montar o ambiente com Docker Compose e como ajustar seu proxy para manter a conexão firme.


Perfil de Arquitetura: Renderização no Navegador vs. Stacks Desktop

Simuladores ATC convencionais costumam sobrecarregar a máquina local, que precisa atuar simultaneamente como motor gráfico e autoridade de cálculo. Ao executar uma sessão com 40 aeronaves ativas, a CPU do seu computador calcula trajetórias, processa algoritmos de separação de tráfego e renderiza modelos 3D ou telas vetoriais pesadas.

O Twrctrl divide esse trabalho com precisão cirúrgica:

  • O Backend (Servidor): Funciona como a autoridade absoluta do estado da simulação. Ele mantém o relógio interno sincronizado, avança os waypoints das aeronaves, confere regras de separação, valida a ocupação de pistas e distribui as atualizações de estado via WebSockets.
  • O Cliente (Navegador): Recebe apenas os pacotes de coordenadas e desenha as varreduras de radar, os mapas de solo e as fitas de voo usando WebGL e a Canvas API do HTML5. Ele também recorre à Web Audio API para reproduzir bipes de alerta e sons de transmissão de rádio.

Essa divisão transfere praticamente todo o custo de renderização para a ponta. O trabalho pesado fica a cargo do processador gráfico do dispositivo que abre a página, liberando seu servidor de homelab para cuidar apenas de I/O de rede e sincronização de dados.

Recurso / Métrica Baseado no Navegador (Modelo Twrctrl) Simuladores Desktop (ex: Tower!3D, EuroScope)
Uso de RAM no Host 35 MB ocioso / ~180 MB sob tráfego intenso N/A (roda localmente na estação de trabalho)
Memória no Cliente 250 MB a 550 MB (uma única aba aberta) 4 GB a 12 GB de RAM dedicada no sistema
Carga de CPU no Host Mínima (< 5% de 1 núcleo vCPU) Alta carga em múltiplos núcleos da CPU local
Plataformas Suportadas Linux, macOS, Windows, ChromeOS, iOS Somente Windows (APIs DirectX/Win32)
Instalação Zero instalação: basta abrir a URL Instaladores gigantescos (10 GB a 40 GB em assets)
Rede WebSockets comuns via TLS (WSS) Portas UDP customizadas, binds de IP e regras de NAT

Como o estado do servidor é totalmente desacoplado da interface gráfica, dá para manter o simulador ativo 24/7 em um mini PC ou servidor doméstico de baixo consumo, sem precisar deixar uma GPU potente consumindo energia na tomada.


Como Rodar o Twrctrl com Docker Compose

Ao rodar uma aplicação interativa como essa no homelab, o ideal é blindar o container ao máximo. Como o backend se limita a servir arquivos estáticos e repassar pacotes de eventos em tempo real, não há motivo para conceder acesso root, volumes persistentes de disco ou permissão de escrita no sistema de arquivos do container.

Crie uma pasta dedicada no seu servidor e salve o arquivo docker-compose.yml com a seguinte estrutura:

services: twrctrl: image: ghcr.io/your-repo/twrctrl:latest # Substitua pela imagem desejada container_name: twrctrl restart: unless-stopped user: "1000:1000" read_only: true security_opt: - no-new-privileges:true ports: - "127.0.0.1:8080:8080" environment: - PORT=8080 - NODE_ENV=production - SIM_TICK_RATE=20 # 20 Hz de taxa de atualização da simulação tmpfs: - /tmp:rw,noexec,nosuid,size=32m deploy: resources: limits: cpus: "1.00" memory: 256M reservations: cpus: "0.10" memory: 64M healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/healthz"] interval: 30s timeout: 5s retries: 3

Vale reparar em algumas decisões de segurança adotadas nessa configuração:

  • read_only: true: Bloqueia todo o sistema de arquivos raiz dentro do container. Caso ocorra uma brecha na camada web, invasores não conseguirão gravar arquivos maliciosos em disco nem alterar binários da aplicação.
  • user: "1000:1000": Garante que o container execute sob uma conta sem privilégios administrativos, sem herdar permissões de root.
  • tmpfs: /tmp: Disponibiliza uma partição temporária de 32 MB diretamente na memória RAM para arquivos de controle efêmeros (como PIDs ou sockets). O parâmetro noexec impede a execução de binários a partir desse ponto.
  • 127.0.0.1:8080:8080: Limita a escuta da porta à interface de loopback do host. Isso impede que a porta seja alcançada diretamente pela sua rede local antes de passar pela filtragem do reverse proxy.

Para subir o container e acompanhar a inicialização:

docker compose up -d
docker compose logs -f twrctrl

Para checar se a verificação de integridade está respondendo como esperado:

docker inspect --format='{{json .State.Health.Status}}' twrctrl

Se o retorno for "healthy", a aplicação está pronta para receber requisições do proxy.


Configuração de WebSocket no Caddy para Tráfego Contínuo

O ponto mais sensível ao hospedar simuladores web é a configuração do proxy reverso. Em páginas normais, as requisições HTTP duram frações de segundo. Em um simulador de torre de controle, o túnel WebSocket precisa ficar de pé durante horas ininterruptas.

Se um avião passar vários minutos aguardando autorização de táxi e a rede não registrar nenhuma alteração de estado por 60 segundos, os timeouts padrão do proxy acharão que o cliente desconectou e vão derrubar o socket. O resultado é incômodo: a varredura do radar trava, as identificações somem da tela e você é obrigado a recarregar a página para recuperar a visão do tráfego.

Veja como configurar o Caddy para lidar com conexões persistentes sem surpresas:

atc.home.arpa {
    encode gzip zstd

    reverse_proxy 127.0.0.1:8080 {
        # WebSockets demandam timeouts generosos
        transport http {
            read_timeout 3600s
            write_timeout 3600s
            keepalive 60s
        }

        # Encaminhamento de cabeçalhos reais do cliente
        header_up Host {host}
        header_up X-Real-IP {remote_host}
        header_up Connection {>Connection}
        header_up Upgrade {>Upgrade}
    }
}

Configurando read_timeout e write_timeout para uma hora (3600s), o Caddy tolera intervalos silenciosos na simulação sem interromper a transmissão de dados.

Caso utilize o Traefik na sua infraestrutura, defina essas propriedades via labels no próprio container:

labels:
  - "traefik.enable=true"
  - "traefik.http.routers.twrctrl.rule=Host(`atc.home.arpa`)"
  - "traefik.http.routers.twrctrl.entrypoints=websecure"
  - "traefik.http.services.twrctrl.loadbalancer.server.port=8080"
  - "traefik.http.services.twrctrl.loadbalancer.responseForwarding.flushInterval=100ms"

O parâmetro flushInterval=100ms faz com que o Traefik repasse os pacotes pequenos de WebSocket instantaneamente, sem segurar buffers para empacotamento maior. Isso garante uma movimentação fluida dos alvos no radar.


Armadilhas e Problemas Comuns

Migrar um motor de simulação para o navegador muda a natureza dos gargalos do sistema. Fique atento a estes pontos práticos:

1. Consumo de Memória na Aba do Navegador

Como os contextos de Canvas e WebGL redesenham alvos em movimento entre 30 e 60 quadros por segundo, sessões muito longas acumulam lixo na memória do cliente. No servidor, o Twrctrl raramente passa da faixa entre 35 MB e 65 MB de RAM. No entanto, no seu notebook, uma aba aberta por quatro horas seguidas pode facilmente saltar de 250 MB para mais de 600 MB se o motor do navegador adiar as coletas do garbage collector.

Se a tela do radar começar a perder quadros ou a navegação pelo mapa do aeroporto engasgar, dê uma olhada no gerenciador de tarefas do próprio navegador (Shift+Esc no Chrome ou about:performance no Firefox). Um simples F5 na aba esvazia a memória alocada sem resetar a simulação ativa no servidor.

2. Diferença de Relógio Entre Servidor e Cliente (Clock Drift)

Sistemas de controle de tráfego aéreo utilizam o Horário Universal Coordenado (UTC) para calcular velocidade de solo, sequência de aproximação e mínimos de separação. Se o relógio do seu servidor de homelab atrasar três segundos em relação à estação de trabalho, os aviões começarão a dar solavancos na tela, já que o navegador tentará interpolar dados com registros no passado ou no futuro.

Mantenha a sincronização de horário do seu host em dia via systemd-timesyncd ou chrony:

timedatectl status

Confira se a saída exibe NTP service: active e System clock synchronized: yes. Caso prefira o chrony, acompanhe o desvio com:

chronyc tracking

3. Falha no Upgrade de WebSocket em Subpastas

Se você tentar rodar o simulador sob uma subpasta de rota em vez de um subdomínio próprio (por exemplo, home.arpa/twrctrl em vez de atc.home.arpa), vários proxies reversos não repassam os cabeçalhos Upgrade: websocket e Connection: Upgrade de forma transparente.

Para evitar dores de cabeça com regras complexas de rewrite e manipulação de cabeçalhos, use sempre um subdomínio exclusivo com DNS interno (split-horizon) para ferramentas baseadas em tempo real.

4. Suspensão de Abas em Segundo Plano

Navegadores atuais aplicam limites rígidos a abas inativas para poupar bateria e processador. Se você deixar o Twrctrl aberto em um segundo monitor e clicar em outra janela, navegadores como Chrome, Firefox e Safari reduzem os temporizadores internos (setTimeout e requestAnimationFrame) para frequências de até 1 Hz.

Nesse cenário, a conexão WebSocket permanece aberta, mas a tela para de desenhar os passos intermediários da rota. Ao voltar para a janela do simulador, o cliente tenta processar todos os pacotes acumulados de uma vez só, fazendo com que as aeronaves disparem descontroladamente pelas pistas. Para contornar isso, mantenha a janela em foco ou desative a suspensão de abas em segundo plano para o seu domínio interno nas flags do navegador (chrome://flags/#calculate-native-win-occlusion em forks do Chromium).


Perguntas Frequentes (FAQ)

É viável rodar um simulador ATC self-hosted no navegador usando Docker?

Sim. O componente de backend roda sobre binários comuns em Node.js, Go ou Rust, entregando os arquivos web estáticos e coordenando a troca de dados via WebSockets. Empacotar a aplicação em um container Docker sobre Alpine Linux é um processo simples. Não há necessidade de repasse de placas de vídeo (GPU passthrough), módulos de kernel extras ou privilégios de root no host.

Como ajustar o Caddy ou o Traefik para aplicações que usam WebSockets contínuos?

Ajuste os parâmetros de timeout para conexões longas. Proxies configurados apenas para páginas web comuns costumam encerrar conexões que ficam sem dados por 30 a 90 segundos. No Caddy, aumente as diretivas read_timeout e write_timeout para 3600s ou mais dentro do bloco transport http. No Traefik, verifique se a rota não impõe timeouts baixos e configure o flushInterval para um valor pequeno (como 100ms) para entregar pacotes de posição sem retenção em buffer.

Qual é o hardware mínimo necessário para manter um simulador web no homelab?

O consumo de recursos no lado do servidor é muito baixo:

  • CPU: 1 vCPU (com consumo médio inferior a 5% em chips x86 ou ARM modernos).
  • RAM no Host: De 35 MB a 65 MB ocioso, raramente ultrapassando 180 MB mesmo com diversos tráfegos simultâneos.
  • RAM no Cliente: Entre 250 MB e 550 MB na aba do navegador, necessários para manipular o contexto WebGL, buffers de áudio e a renderização geral da interface.

Qual a diferença prática entre radares web e softwares desktop como o Tower!3D?

Simuladores convencionais de desktop priorizam modelagem 3D pesada de aeroportos, reconhecimento de voz complexo e malhas detalhadas de aeronaves, exigindo placas de vídeo dedicadas e compatibilidade restrita com Windows. Projetos web trocam a fidelidade gráfica pesada pela facilidade de acesso, focando na visão bidimensional de controle de solo, telas clássicas de radar e strips de voo. Essa escolha permite operar a simulação em notebooks modestos, tablets ou servidores caseiros, sem instalações gigantescas.


Veredito

Projetos como o Twrctrl mostram o poder dos padrões modernos da web para substituir softwares locais inchados. Delegando a renderização gráfica para o navegador do cliente, é possível rodar um simulador de controle aéreo ágil sobre um servidor bastante modesto. Proteja seu container com permissões restritas e sistema de arquivos somente leitura, dê atenção aos timeouts de WebSocket no proxy reverso e você terá um console de radar rápido e pronto para uso dentro da sua rede doméstica.

Conteúdos relacionados neste cluster

  • /en/posts/cursor-xai-vs-local-llms-privacy-guide-for-homelabs/
  • /en/posts/docker-sandbox-kit-spec-v3-hardening-ai-agent-containers/
  • /en/posts/docker-sandboxes-secure-isolation-for-autonomous-ai-agents/

Relacionados neste cluster

  • /pt/posts/como-isolar-agentes-ia-docker-sandbox/
  • /pt/posts/como-isolar-agentes-ia-docker/
  • /pt/posts/como-isolar-containers-docker-seguranca/