← Blog

Como hospedar um bot do Discord 24 horas por dia

Railyard

3 de junho de 2026 · 10 min de leitura

Siga isso no seu próprio servidor. Comece grátis, sem cartão

O bot funciona. node index.js no seu notebook faz ele logar no seu servidor de teste, !ping recebe um pong de volta, tudo certo, até você fechar o terminal ou colocar o notebook pra dormir e o bot ficar offline junto. O que falta não é mais código. É um processo que continua rodando depois que você se afasta, o que é um problema diferente daquele que você resolveu ao escrever o bot em si.

Este guia constrói um bot discord.js de verdade, empacota ele num Dockerfile, e explica a única coisa que trava quase todo mundo hospedando um bot do Discord pela primeira vez: por que ele precisa de um processo que nunca para, não de uma função que inicia por demanda. Depois, cobre a política de restart, o logging e o caminho em um clique pra ter a mesma coisa rodando no Railyard.

O que você vai precisar

  • Node.js 22 ou 24 instalado localmente (o discord.js 14 exige Node 18 ou mais recente).
  • Uma conta no Discord e um servidor (guild) onde você possa testar, de preferência um que você controla.
  • Docker instalado localmente, pra buildar e rodar a imagem antes de ela ir pra qualquer outro lugar.
  • Um servidor pra rodar o container, ou uma conta no Railyard pro atalho da última seção.

Passo 1: criar a aplicação do bot e conseguir um token

Acesse o Discord Developer Portal e clique em New Application. Dê um nome a ela, depois abra a aba Bot à esquerda. Clique em Reset Token pra gerar um token, e copie ele pra algum lugar seguro. Essa é a credencial que seu código usa pra logar como o bot; qualquer pessoa com ela pode controlar seu bot, então ela vai numa variável de ambiente, nunca num arquivo que você commita.

Na mesma aba Bot, em Privileged Gateway Intents, ative Message Content Intent se o seu bot precisa ler o texto das mensagens (um bot de moderação ou um bot de comandos que não é só de slash commands precisa disso; um bot que só responde a slash commands e cliques em botão não precisa).

Abra OAuth2 → URL Generator, marque o escopo bot, marque as permissões que seu bot realmente precisa (comece com Send Messages e Read Message History pra um bot simples), copie a URL gerada, e abra ela num navegador pra convidar o bot pro seu servidor de teste.

Passo 2: escrever o bot

O discord.js 14.27.0 é a versão atual no npm. Inicie o projeto e instale:

npm init -y
npm install discord.js@^14.27.0

Adicione "type": "module" ao package.json pra poder usar import, e um script start:

{
  "name": "my-discord-bot",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "start": "node index.js"
  },
  "dependencies": {
    "discord.js": "^14.27.0"
  }
}

index.js:

import { Client, GatewayIntentBits, Events } from "discord.js";

const client = new Client({
  intents: [
    GatewayIntentBits.Guilds,
    GatewayIntentBits.GuildMessages,
    GatewayIntentBits.MessageContent,
  ],
});

client.once(Events.ClientReady, (readyClient) => {
  console.log(`Logged in as ${readyClient.user.tag}`);
});

client.on(Events.MessageCreate, async (message) => {
  if (message.author.bot) return;
  if (message.content === "!ping") {
    await message.reply("pong");
  }
});

client.on(Events.Error, (error) => {
  console.error("Discord client error:", error);
});

client.login(process.env.DISCORD_TOKEN);

Cada entrada de GatewayIntentBits declara uma categoria de eventos que o Discord vai de fato mandar pro seu bot; GuildMessages e MessageContent juntos são o que faz o MessageCreate disparar com um message.content legível pra um comando de texto simples como !ping. Pedir MessageContent no código sem ativar o Message Content Intent no Developer Portal no Passo 1 te dá um erro de “disallowed intents” no login, que é o primeiro erro mais comum com um bot novo.

Teste localmente:

DISCORD_TOKEN=your-token-here node index.js

Você deve ver Logged in as yourbot#0000 no terminal, e !ping no seu servidor de teste deve receber uma resposta pong.

Passo 3: escrever o Dockerfile

FROM node:24-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

FROM node:24-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=deps /app/node_modules ./node_modules
COPY . .
USER node
CMD ["node", "index.js"]

Esse é o mesmo formato de dois estágios que você usaria pra qualquer aplicação Node: as dependências são instaladas no próprio estágio, então ficam em cache separadas do resto do código-fonte, e a imagem final roda como o usuário não-root node em vez de root. A única diferença real em relação ao Dockerfile de uma aplicação web é o que está faltando: nenhum EXPOSE, porque esse processo nunca escuta numa porta. Ele abre uma conexão de saída pro gateway do Discord; nada se conecta nele.

Passo 4: por que isso precisa ser um processo sempre ativo, não uma função

Uma função serverless (uma Vercel Function, uma AWS Lambda) inicia quando uma requisição HTTP chega e desliga logo depois de responder. Esse modelo se encaixa bem num workload feito de requisições discretas e independentes. Um bot do Discord não é isso: depois que client.login() tem sucesso, o discord.js abre uma conexão WebSocket com o gateway do Discord e mantém ela aberta indefinidamente. Toda mensagem, reação e interação de slash command que o seu bot vê chega como um evento empurrado por esse socket já aberto, não como uma requisição HTTP de entrada que sua plataforma roteia pra um handler.

Não existe uma requisição pra disparar um cold start em primeiro lugar, e mesmo que uma plataforma invocasse seu código em algum outro horário, a conexão do gateway em si ainda precisaria existir continuamente entre as invocações pra receber qualquer coisa; uma conexão que só existe enquanto uma função está rodando não é uma conexão capaz de receber um evento que chega entre duas invocações. Essa é a distinção real que uma busca por “hospedar um bot do Discord” está rodeando: não é desempenho, é se tem algo escutando no momento em que o Discord decide mandar alguma coisa.

(O Discord também oferece um endpoint de interações baseado em HTTP especificamente pra slash commands, onde o Discord manda um POST HTTP pro seu bot por interação em vez de um evento de gateway. Esse modelo pode rodar numa função serverless, mas ele só cobre slash commands e components, não mensagens comuns, presença, ou qualquer outra coisa que o gateway carrega, e é uma arquitetura diferente da abordagem Client + gateway que este guia usa.)

O que um bot como esse realmente precisa não tem nada de glamouroso: um processo que inicia, continua rodando, e reconecta por conta própria se a conexão cair, o que o discord.js já faz internamente pra instabilidades comuns de rede. Seu trabalho é garantir que o processo em si continue existindo, o que é uma política de restart, não um framework.

Passo 5: política de restart e logging

Rode a imagem localmente com uma política de restart pra que o Docker traga ela de volta depois de uma falha ou de um reboot do host:

docker build -t my-discord-bot .
docker run -d --name my-discord-bot --restart unless-stopped \
  -e DISCORD_TOKEN=your-token-here \
  my-discord-bot

--restart unless-stopped diz pro daemon do Docker reiniciar o container automaticamente, a menos que você mesmo tenha parado ele explicitamente, o que cobre tanto uma exceção não tratada derrubando o processo quanto um reboot da máquina host.

Pros logs, não adicione uma biblioteca de logging ainda; console.log e console.error pra stdout/stderr já bastam pra um bot desse tamanho, e o Docker já captura os dois:

docker logs -f my-discord-bot

Você deve ver a linha Logged in as... do Passo 2, e qualquer saída de console.error se o client encontrar um erro. Continue registrando os eventos específicos do Discord que importam pra depurar um bot que ficou quieto, em particular reconexões, pra que um painel ou terminal que você consulte depois te diga que algo aconteceu, em vez de simplesmente ficar em silêncio:

client.on(Events.ShardDisconnect, () => console.warn("Shard disconnected, reconnecting..."));
client.on(Events.ShardReconnecting, () => console.log("Shard reconnecting..."));

Passo 6: rodar num servidor de verdade

Copie a imagem (ou o repositório, se você estiver buildando no servidor) pra uma VPS com Docker instalado, e rode o mesmo comando docker run do Passo 5 lá, com um arquivo .env de verdade em vez de um token inline:

docker run -d --name my-discord-bot --restart unless-stopped --env-file .env my-discord-bot

Essa única flag, --restart unless-stopped, já é a maior parte do que “hospedar um bot 24 horas por dia” realmente significa na prática: um processo que volta por conta própria depois de qualquer coisa que não seja você parar ele explicitamente.

O atalho: fazer isso com o Railyard

Aponte o Railyard pro mesmo repositório. Como o repositório tem um Dockerfile que de fato copia a aplicação pra dentro da imagem (não uma imagem de desenvolvimento esperando um bind mount, caso que a detecção de build do Railyard pula automaticamente), esse Dockerfile builda e roda direto, sem nenhuma adivinhação de Railpack ou buildpack envolvida; veja métodos de build.

Esse bot não tem processo web, e tudo bem com isso: o Railyard roda aplicações só de worker do mesmo jeito que qualquer outra, como um tipo de processo sem porta associada, coberto em workers e tarefas agendadas. Adicione DISCORD_TOKEN na aba Variables da aplicação (veja variáveis e secrets); ela é criptografada e aplicada no próximo deploy, que é o equivalente, do lado do Railyard, ao --env-file .env do Passo 6.

Uma configuração pra deixar quieta: Sleep when idle, em Settings → General, para um processo quando a CPU dele fica ociosa por tempo suficiente, segundo o que é reportado. Um bot do Discord sentado num servidor quieto pode parecer ocioso por essa métrica mesmo com a conexão do gateway dele muito viva e prestes a receber uma mensagem; deixe essa configuração desligada pra esse processo, pra que a conexão nunca seja a coisa que acaba sendo colocada pra dormir.

A política de restart do Passo 5 também não é algo que você configura aqui, é simplesmente como o Railyard roda os containers de toda aplicação: uma falha reinicia o processo sem você entrar via SSH pra digitar docker start. Os logs estão na aba Logs da página da aplicação, ou via railyard logs my-bot --follow pela CLI, o mesmo hábito do docker logs -f do Passo 5, sem precisar de um terminal aberto no próprio servidor.

Pra fazer deploy sem precisar colar a URL do repositório toda vez, adicione um botão de deploy ao README do bot:

[![Deploy to Railyard](https://app.railyard.run/deploy/badge)](https://app.railyard.run/deploy?repo=https://github.com/you/your-discord-bot)

Veja a documentação do botão de deploy pro formato completo do link, e quickstart se você está começando do zero: cadastro, um servidor, e uma aplicação rodando em cerca de dez minutos.

Solução de problemas

O bot aparece offline no Discord mesmo com o container rodando. Confira o docker logs (ou a aba Logs) em busca de um erro de login primeiro; um token inválido ou revogado falha imediatamente. Se o login der certo mas o bot ainda não responder, confirme que a URL de convite do Passo 1 de fato incluía o escopo bot e as permissões que seus comandos precisam.

“Used disallowed intents” na inicialização. Você está pedindo GatewayIntentBits.MessageContent (ou outro intent privilegiado) no código sem ativar ele no Developer Portal. Volte pra aba Bot do Passo 1 e ative o Privileged Gateway Intent correspondente.

O container fica reiniciando em loop. Isso é o --restart unless-stopped fazendo exatamente o trabalho dele contra um processo que está travando imediatamente, geralmente um erro de inicialização, não um erro passageiro. Confira as primeiras linhas do docker logs logo depois de um restart, não o final; o motivo da falha quase sempre está ali, geralmente um DISCORD_TOKEN ausente ou um erro de digitação num import de dependência.

O bot fica quieto por um tempo, depois volta. Isso geralmente é uma reconexão de gateway, que o discord.js trata por conta própria; o logging de ShardDisconnect/ShardReconnecting do Passo 5 transforma “o bot parecia fora do ar” numa linha de log que você pode de fato checar contra a página de status do próprio Discord, se isso continuar acontecendo.

Perguntas frequentes

Posso hospedar um bot do Discord de graça? O framework do bot em si é grátis de qualquer jeito; o que custa dinheiro é o compute sempre ativo em que ele roda. Uma VPS pequena custa alguns dólares por mês, que é o piso real pra “24 horas por dia”, já que nada grátis mantém um processo vivo indefinidamente sem você precisar ficar de olho.

Eu preciso de um servidor web pro meu bot do Discord? Não, não pra abordagem baseada em gateway que este guia usa. O bot só faz conexões de saída (pro gateway e pra API REST do Discord); ele nunca precisa aceitar requisições HTTP de entrada, então não existe porta pra expor nem reverse proxy ou certificado TLS pra gerenciar pro bot em si.

O que acontece com mensagens enviadas enquanto meu bot está offline? Elas se perdem, do ponto de vista do bot. O gateway do Discord não enfileira eventos pra um client desconectado reproduzir depois; se o bot estava offline quando uma mensagem foi enviada, ele nunca vê aquele evento MessageCreate, o que é a razão prática pela qual “24 horas por dia” importa mais pra um bot do que pra maioria das aplicações web.

discord.js ou discord.py: isso importa pra hospedagem? Não, pra nada deste guia. Um bot discord.py (Python) precisa das mesmas coisas: um processo sempre ativo, não uma função; uma política de restart; e intents.message_content = True definido no código mais o Message Content Intent ativado no Developer Portal, o equivalente em Python à configuração de intent do Passo 2. O Dockerfile troca node:24-alpine por python:3.13-slim e bot.run(TOKEN) por client.login(...), mas a arquitetura é idêntica.

Por que não simplesmente deixar um terminal aberto rodando node index.js? Funciona até o terminal fechar, o notebook dormir, ou a sessão SSH cair, e nesse momento o processo morre junto e nada traz ele de volta. --restart unless-stopped (ou o Railyard rodando o container do mesmo jeito por padrão) é a diferença entre um bot que está no ar porque você ainda não fechou o notebook e um que está de fato no ar.

Um bot do Discord é um pedaço pequeno de código fazendo um trabalho grande e contínuo: continuar conectado. Uma vez que o processo em si é algo que reinicia por conta própria e registra onde você pode ver, o bot estar sempre online deixa de ser algo em que você precisa pensar.

Por que escolhem o Railyard para isso

  • Sua própria conta de cloud. Faça deploy no DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou em qualquer servidor Linux via SSH, cobrado direto pelo seu provedor, sem markup.
  • Um preço fixo por equipe. O Free é grátis para sempre: um servidor mais um servidor de banco de dados, três apps, sem cartão. O Pro custa US$19 por mês para até cinco servidores e apps ilimitados. O Team custa US$49 por mês com servidores ilimitados. Sem cobrança por app, por deploy, por assento ou por gigabyte.
  • Templates de um clique. Faça deploy de apps open source, como bancos de dados, analytics e ferramentas de dev, no seu próprio servidor em um clique.
  • Bancos de dados gerenciados. Postgres, MySQL e Redis com backups programados, verificados por restauração, e recuperação point-in-time.
  • Push-to-deploy. Faça deploy a partir do GitHub, GitLab ou da CLI, com HTTPS automático e releases sem downtime.

Pule a configuração do servidor

Faça o deploy do Dockerfile do seu bot discord.js no Railyard como um processo worker, já com política de restart e logs configurados.

Plano grátis, sem cartão, e 14 dias de Pro para cada equipe nova. Um preço fixo por equipe.