ceu.gg
Hospedagem de servidores de Minecraft em bare metal self-hosted, com uma engine de orquestração escrita do zero.
12,9 mil+
contas
que criaram 14,6 mil+ comunidades
14,7 mil+
servidores criados
70 no ar ao mesmo tempo, média do pico diário
5
máquinas dedicadas
Rio de Janeiro e Bahia, 4 ativas e 1 de reserva
~160 mil
linhas de TypeScript
Painel e API, ambos de primeira parte
Números lidos da API de métricas do ceu.gg em agosto de 2026.
O que é
O ceu.gg dá às pessoas um servidor de Minecraft para jogar com os amigos sem exigir que entendam nada disso. Escolhe a versão, aperta um botão, compartilha o endereço. O plano gratuito continua gratuito, e há planos pagos para quem cresce além dele.
O painel, a API, a engine de provisionamento e as imagens de container são todos feitos aqui. Não há painel de prateleira por baixo. Os servidores rodam em Kubernetes sobre bare metal self-hosted, em duas localidades no Brasil, atrás de um roteamento que eu mesmo configuro. Eu escrevo o código; um parceiro divide comigo o brainstorm, os testes e o suporte. Duas coisas definem o formato de todo o resto: o hardware é cotado em dólar enquanto a receita entra em real, e duas pessoas só têm as horas que têm. Por isso simplicidade aqui é requisito.
- Da tabela de roteamento ao painel, tudo é de primeira parte
- Criar um servidor não aloca nada além de uma linha no banco
- A capacidade reservada se sustenta porque o scheduler se recusa a colocar outra coisa nela
- O agente de diagnóstico lê texto escrito por jogadores e não tem nenhuma ferramenta de escrita
- O chat age na sua conta com as suas permissões, nunca com as próprias
As camadas
Quatro camadas, cada uma para impedir que uma falha dentro dela vire uma falha em todo o resto.

- 01
Bare metal
- Servidores dedicados AMD Ryzen e Intel Xeon
- 3 máquinas no Rio de Janeiro, 2 na Bahia, uma delas ainda de reserva
- Um disco de cold storage com passthrough para a VM
- Rede segmentada
- 02
Virtualização com Proxmox
- Control plane e workloads em VMs separadas
- Uma pane derruba a VM e mais nada
- 03
Cluster k3s
core-system- o painel e a APIservers- um pod por servidor de jogo: app, watchdog, seeder, backupmonitoring- Prometheus e Grafana- Limites por pod, restart automático, um único formato de deploy para tudo
- 04
Borda e entrada de tráfego
- Cloudflare para DNS e TLS
- Uma VPN site-to-site do gateway na nuvem até o roteador próprio
- Roteamento MikroTik para dentro do cluster
- Traefik com certificados ACME via DNS-01
De um botão a um mundo no ar
O que realmente acontece entre apertar o botão e o mundo ficar acessível.
- 01painel
O wizard pergunta em linguagem de jogador
Onde seus amigos jogam, plugins ou mods. Software, versão e template saem das respostas, então ninguém precisa saber o que é Paper, Purpur ou Fabric.
- 02backend
As regras são checadas, e quase nada é alocado
Limites do plano, cota da comunidade e permissões ABAC são resolvidos primeiro, e tudo o que se escreve é uma linha no banco, uma porta reservada e credenciais SFTP cifradas. Nada existe no cluster ainda.
- 03engine
O primeiro start passa por uma fila
A entrada carrega uma prioridade, pago antes de gratuito, e um CronJob drena a fila a cada minuto, respeitando a capacidade atual dos nós.
- 04cluster
O seeder preenche um volume novo e então o pod sobe
O zip do mundo é puxado do MinIO para um PVC novo, e o pod sobe com os sidecars de watchdog e backup.
- 05watchdog
A contagem de jogadores vem do protocolo do Minecraft
Um sidecar em cada pod pergunta ao próprio servidor de jogo e devolve o número para a API.
- 06engine
Um servidor vazio é desligado
Depois de dez minutos sem ninguém, o servidor do plano gratuito para, e o volume ainda fica trinta minutos no nó antes de o cron de arquivamento levá-lo. O plano gratuito funciona porque a maioria dos servidores passa a maior parte do tempo desligada.
- 07storage
O mundo é zipado no object storage e o volume é apagado
Ligar de novo recria o mundo a partir desse zip. Nada se perde, e um servidor ocioso não ocupa nada do cluster.
A assistente
A Sky é a assistente da plataforma. Ela responde no painel e no Discord, e consegue agir na conta de quem está perguntando - com as permissões dessa pessoa, nunca com as próprias.
# o chat de ajuda
Um motor só responde nos dois lugares. O widget do painel e o bot do Discord rodam o mesmo serviço, e a rota do Next na frente do widget é um proxy que repassa a resposta conforme ela chega.
- As respostas chegam em stream por Server-Sent Events, e o mesmo canal carrega a linha de status que o widget mostra enquanto uma ferramenta roda.
- As respostas saem de um conjunto curado de arquivos markdown sobre a plataforma, buscados por palavra-chave com peso maior para o que casa no título da seção. Não há embeddings nem banco vetorial; o corpus é pequeno o bastante para carregar inteiro, então a pergunta comum nem chega a uma busca.
- Cada canal do Discord tem um modo - responder tudo, responder só quando mencionada, ou ficar de fora. Canais de ticket ganham uma saudação, e a Sky se cala quando quem está falando é a equipe.
- O bot roda em exatamente uma réplica por vez, eleita por um advisory lock do Postgres. Perder o lock desconecta o gateway e outra réplica assume.
- Quando não consegue resolver, o
escalate_to_humanchama a equipe naquele canal em vez de deixar o modelo improvisar uma resposta.
# chamada de ferramentas
O modelo pode chamar 18 ferramentas. Nenhuma delas encosta no banco: cada uma é uma chamada HTTP para a API da própria plataforma levando o JWT de quem está na conversa, então uma chamada de ferramenta é autorizada do mesmo jeito que um clique no painel.
- As permissões vêm do ABAC, a mesma matriz que o painel lê. A Sky nunca tem credencial própria.
- Leitura é liberada para qualquer um logado: comunidades, servidores, status ao vivo, console, arquivos, backups, plano e saldo.
- Escrita - editar um arquivo de configuração, ligar ou desligar um servidor, rodar um comando no console, criar backup - exige plano pago ativo, e essa checagem falha fechada.
- Nada destrutivo roda na primeira chamada. A ferramenta devolve
needsConfirmation, a Sky precisa perguntar, e só uma segunda chamada comconfirmed: truepassa. - Cada resposta tem no máximo seis rodadas de ferramenta, e a última rodada vai sem ferramenta nenhuma, para obrigar a parar de investigar e responder.
# diagnóstico
Quando um servidor não sobe, o console é uma parede de stack trace em inglês. O diagnóstico é um loop separado que lê o servidor e escreve um laudo: o que está errado, a evidência disso, e as correções como botões.
- O modelo recebe ferramentas de leitura e oito rodadas. O prompt empurra ele a pedir vários arquivos na mesma rodada, porque rodada é o que falta.
- Ele termina chamando
submit_diagnosis: um resumo em português, achados marcados comoINFO,WARNINGouCRITICAL, e ações propostas. Se ele não chamar, a execução devolve texto e nenhuma ação. - Todo achado precisa citar algo que ele leu de fato - uma linha de log, um valor de config, um nome de arquivo. Sem isso é chute, e chute faz o dono do servidor mexer no lugar errado.
- A cota é do servidor, não da conta que clicou. Ela sai do plano de hospedagem daquele servidor, e uma comunidade com vários administradores teria limites diferentes dependendo de quem pedisse.
- Limite zero também quer dizer indisponível, e é assim que a feature liga e desliga para o gratuito pelo admin sem precisar de deploy.
- Uma execução que passa do teto de custo para e devolve o que tem. Um laudo parcial vale mais que uma fatura aberta.
- Os nomes das ferramentas viram linguagem de gente antes de o laudo ser salvo. Um laudo real chegou ao cliente dizendo
get_server retornou: "Não encontrei esse recurso".
Problemas que valeu a pena anotar
Quatro que deram trabalho de verdade, e alguns menores que ficaram anotados.
Um servidor que não está rodando não custa nada
# o problema
Todo servidor ocioso prendendo um volume a um nó é capacidade que ninguém está usando. Num plano gratuito, onde a maioria dos servidores fica vazia a maior parte do tempo, isso é o orçamento inteiro de capacidade.
# o que ele faz
- Criar um servidor não aloca nada no cluster: uma linha, uma porta reservada, credenciais SFTP cifradas. O primeiro start faz todo o trabalho de verdade, e um CronJob o drena por
WAITING → PROCESSING → SEEDING → STARTING → COMPLETED | FAILED. - Em repouso, um mundo é um objeto só:
sv-{id}/.ceugg-world.zip. Um cron encontra volumes ociosos além de um TTL de 30 minutos, manda o conteúdo para o object storage e apaga o volume. - A escrita vai direto em stream para uma chave
.tmp, e orclone movetopromove essa chave. Esse move é uma cópia feita dentro do próprio storage, então é atômico: um upload que quebra deixa um.tmpórfão e nunca encosta no zip vivo. - As leituras resolvem o zip primeiro, e isso resolveu a migração de graça: objeto solto sob o prefixo é só resto de antes.
É isso que permite manter 14,7 mil+ servidores criados em 5 máquinas. Os que ninguém está jogando não ocupam nada do cluster, e voltam de um único zip quando alguém joga.
Capacidade que não dá para vender duas vezes
# o problema
Planos pagos prometem CPU e RAM reservadas. Num cluster que também roda um plano gratuito, "reservada" só quer dizer alguma coisa se o scheduler se recusar a entregar essa capacidade a mais alguém.
# o que ele faz
- Os nós carregam as labels
ceu.gg/tier=high-performanceeceu.gg/storage-gb. O controle de disco fica numa label porque o Kubernetes não expõe o tamanho do volume group, e a ServiceAccount, restrita ao namespace, não pode listarpersistentvolumes. - Nós de alta performance são exclusivos. Um servidor gratuito ou comum nunca cai em um deles, mesmo vazio.
- Não existe downgrade silencioso. Um pedido de alta performance que não pode ser alocado fica em
WAITING_RESOURCESe dispara um alerta de operação. - Os nós comuns guardam uma reserva fixa de 20% para servidores pagos, e os gratuitos nunca pegam emprestado. Memória é reservada em 100% porque é incompressível; CPU tem um fator de request ajustável porque não é.
A garantia se sustenta por recusa, não por observação. Um servidor pago encontra seus recursos livres porque nada mais teve permissão de ocupá-los.
Deixar um modelo agir numa conta de verdade
# o problema
No chat, diferente do diagnóstico, a Sky consegue ligar um servidor, reescrever um arquivo de configuração e rodar comandos no console. Quem decide quando usar isso é o modelo.
# o que ele faz
- As ferramentas nunca encostam num repositório. Cada uma é uma chamada HTTP para a API da própria plataforma levando o JWT do usuário, então quem responde se a ação é permitida é o mesmo ABAC, as mesmas specifications e os mesmos eventos de domínio que respondem isso para um clique no painel.
- A validade do token é checada antes da chamada. Sessão morta devolve um
session_expiredque também serve de instrução: peça para a pessoa entrar de novo, não tente de novo sozinha. - O gate de plano pago falha fechado. Se a própria consulta do plano der erro, a escrita não acontece.
- Qualquer coisa destrutiva devolve
needsConfirmationem vez de rodar. A Sky precisa perguntar, esperar o sim, e chamar de novo comconfirmed: true.
O modelo escolhe o que tentar. Ele nunca escolhe o que tem permissão de fazer. O modo diagnóstico responde a mesma pergunta apagando as ferramentas de escrita, porque lá a entrada é texto que qualquer jogador pode ter escrito.
Um agente sem ferramentas de escrita
# o problema
A Sky lê o console, os arquivos de configuração e a lista de plugins de um servidor para explicar por que ele não sobe. Qualquer jogador daquele servidor consegue escrever nos três.
# o que ele faz
- No modo diagnóstico o modelo não recebe nenhuma ferramenta de escrita. Assim, um arquivo com nome feito para parecer uma instrução continua sendo o que é: texto num relatório.
- A autorização nunca vem do modelo. O cliente manda só o id de uma ação proposta, e o backend relê essa ação a partir do que foi persistido naquela execução e revalida do zero.
- As ações são uma allow-list fechada. Cada uma passa por ABAC, plano, cota e cooldown antes de rodar.
Um nome de arquivo malicioso consegue mudar o que o modelo diz. Não consegue mudar o que o sistema faz - é exatamente para isso que a autoridade fica fora da saída do modelo.
$ git log --grep="fix:" --oneline
Teto de heap abaixo do limite do container
--max-old-space-size=768 sob um limite de 1Gi. Sem isso, o V8 mira num teto que o cgroup não vai dar, e o kernel mata o processo por OOM antes de um GC completo rodar.
Threads da libuv dimensionadas pelo host errado
UV_THREADPOOL_SIZE reduzido de 16 para 8. O Node dimensionava o pool pelos 38 cores do host, e não pelos 4 que tinha de fato, então as threads disputavam 4× mais CPU do que existia e travavam nos picos de arquivamento e SFTP.
Um vazamento de sockets no cliente Kubernetes
Cada construção do cliente criava um https.Agent novo, então as conexões se acumulavam em ESTABLISHED e nunca eram reaproveitadas. Memoizar o agent resolveu.
Operando
# observabilidade
Prometheus e Grafana no cluster, OpenTelemetry dentro dos serviços: traces, métricas e logs em oito instrumentações no servidor. O tracing está com sampling desligado em produção, porque neste tamanho custava mais CPU do que os traces entregavam.

- CPU do cluster e por workload
- Memória de nós e pods
- Contagem de pods ativos e estado do cluster
- Latência, taxa de erro e throughput do API gateway
- Uso de recursos e disponibilidade por servidor
- Snapshots de capacidade dos nós, alimentando o scheduler
# segurança num plano gratuito
Um plano gratuito é um convite aberto. Criação em massa de servidores, execução de código arbitrário e upload de arquivos hostis precisam ser barrados.
- Nada de upload de executável. Plugins, mods e datapacks chegam pelo catálogo integrado do CurseForge e do Modrinth.
- Tudo passa pelo painel e pela API. Não há shell, e não há caminho de um servidor de jogo até o filesystem do host.
- Os containers rodam como não-root, com limites de CPU e memória por pod.
- As permissões são ABAC de ponta a ponta: uma matriz que o frontend lê da API, nunca uma checagem de cargo no código.
O que quebrou e o que ainda está aberto
Dois mudaram o jeito de operar a plataforma.
O disco de cold storage se desconectou
O disco por trás do cold storage caiu sem aviso.
Redundância saiu da lista de "depois a gente resolve", e a recuperação ganhou procedimento escrito.
Um dia sem IPv4
O acesso IPv4 ficou fora por quase um dia inteiro.
O acesso passou a ter rotas independentes, então perder uma delas não derruba mais a plataforma.
# ainda em aberto
- O scheduling aloca servidores por disponibilidade. Ainda não há estratégia regional - tudo bem neste tamanho, e não vai continuar tudo bem.
- O monitoramento diz que rede e memória vão ser os primeiros limitadores conforme a plataforma cresce, antes da CPU.
Histórico de entregas
$ git log --tags --oneline
- alphaDez 2025
Primeiros servidores em hardware próprio
Um painel, um cluster e engine o suficiente para criar um mundo e conectar nele.
- v0.1.8 - v0.2.2Fev 2026
Endereçamento e controle
Subdomínios personalizados, portas TCP e UDP extras com registros SRV para plugins que precisam de conexão própria, parada forçada e logs de console que sobrevivem a um restart.
- v1.0.0Abr 2026
Beta pública
A fila de inicialização com progresso ao vivo, o gerenciador de arquivos, permissões ABAC com hierarquia de cargos, backups no Google Drive e logs de auditoria derivados de domain events.
- v1.5 - v1.6Jul 2026
Consciência de capacidade
Snapshots de capacidade dos nós, admissão na fila pela capacidade disponível, e o estado
WAITING_RESOURCES. A correção dohttps.Agentmemoizado entrou aqui, e a Sky chegou ao Discord. - v2.0.0Ago 2026HEAD -> main
Capacidade reservada e um agente de diagnóstico
A maior mudança desde a beta. Planos por servidor com CPU, RAM e disco de fato reservados; pools de nós de alta performance exclusivos e limitados pelo hardware real; um fluxo de criação guiado; e o modo de diagnóstico somente-leitura da Sky.
Stack completa
- Frontend
- Next.js 16React 19TypeScriptTailwind CSSshadcn/uiRadix
- Backend
- NestJS 11TypeScriptDDDCQRSDomain eventsABACJest
- Dados
- PostgreSQL 16PrismaKafkaMinIOGoogle Cloud StorageSFTP
- IA
- OpenAITool callingServer-Sent EventsToken and cost accounting
- Infraestrutura
- Kubernetes (k3s)ProxmoxHelmTraefikOpenEBS LVMMikroTikCloudflareDocker
- Observabilidade
- PrometheusGrafanaSigNozOpenTelemetrypino
- Integrações
- StripePIX (Efí)Discord.jsResendGoogle DriveCurseForgeModrinth