Serenity is Rust's most established Discord library, and it runs on the Rust application like any other Cargo project — with one big caveat this guide is upfront about: the Rust application compiles your code on every start, and a Discord library pulls in a lot of crates, so that first build is slow. This guide gives you a minimal, correct Serenity bot, then tells the honest truth about build times and how the cache saves you.
| At a glance | |
|---|---|
| You need | a bot token from Your first Discord bot, and a Falix server running the Rust application (some RAM headroom helps — see below) |
| Plan | Free or premium — but read the compile-time note before choosing free |
| Time | about forty minutes, most of it the first build |
Rust on Falix explains the build model and cache in full; this guide is the Serenity-specific build on top of it.
The model, and the honest catch
The Rust application runs cargo run --release when you press Start — it builds from your Cargo.toml and then runs the result. A pure bot needs no port (it dials out to Discord), so that part is simple. The catch is the build:
⚠️ Heads up: Serenity is not a small dependency. A fresh Serenity bot pulls in ~150 crates (Serenity, Tokio, the HTTP and TLS stacks, and their dependencies). On a capable machine that cold build takes roughly a minute; on a free plan's 2.5 GB of shared RAM it is considerably slower, and a big compile can even run out of memory (exit code 137) — the exact pitfall Rust on Falix and Out of memory warn about. You pay this once:
cargocaches everything intarget/, so after the first successful build, restarts are fast and only your changed code recompiles (a few seconds). A reinstall or application switch clearstarget/and brings the long build back.
If you want a bot online in five minutes, discord.js or discord.py get you there faster. Choose Rust when you want Rust — for the type safety, the performance, or the learning.
Step 1 — Cargo.toml
Keep dependencies lean; every extra crate lengthens that build. Serenity plus Tokio (its async runtime) is the minimum:
[package]
name = "serenity-bot"
version = "0.1.0"
edition = "2021"
[dependencies]
serenity = "0.12"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Step 2 — src/main.rs
A minimal bot: read the token from a file, connect, and reply Pong! to !ping.
use std::fs;
use serenity::async_trait;
use serenity::model::channel::Message;
use serenity::model::gateway::Ready;
use serenity::prelude::*;
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn message(&self, ctx: Context, msg: Message) {
if msg.content == "!ping" {
if let Err(why) = msg.channel_id.say(&ctx.http, "Pong!").await {
println!("Error sending message: {why:?}");
}
}
}
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("Listening as {}", ready.user.name);
}
}
#[tokio::main]
async fn main() {
// Read the token from token.txt, next to the project (never hard-code it).
let token = fs::read_to_string("token.txt")
.expect("could not read token.txt")
.trim()
.to_string();
let intents = GatewayIntents::GUILD_MESSAGES
| GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::MESSAGE_CONTENT;
let mut client = Client::builder(&token, intents)
.event_handler(Handler)
.await
.expect("Error creating client");
if let Err(why) = client.start().await {
println!("Client error: {why:?}");
}
}
The pieces: EventHandler is a trait you implement (the #[async_trait] makes its methods async), overriding message and ready for the events you want. GatewayIntents declares what the bot listens for. Client::builder(token, intents) builds the connection, and client.start() runs it, holding the process open — which is what keeps the server "online."
⚠️ Heads up:
MESSAGE_CONTENTis a privileged intent. A!pingmessage bot reads message text, so you must switch Message Content on under Bot in the Developer Portal — otherwise Discord rejects the login for requesting a disallowed intent. (Prefer slash commands and you can drop that intent; Serenity supports them too, see the docs below.)
Step 3 — Onto the server
Two things go into /home/container:
- Your project —
Cargo.tomland thesrc/folder. Upload via the File Manager or SFTP, or push with Git deploy. token.txt— create it in the File Manager with your bot token as the only line. The code reads it at startup, so the token stays out of your source and out of Git. Add it to.gitignore.
Step 4 — Start it (and wait out the first build)
Press Start. The console fills with Compiling ... lines — Serenity, Tokio, and their dependency tree, one crate at a time. This is the slow part, and it's working, not stuck. When it finishes you'll see cargo's Finished line, then your bot connects and prints:
Listening as YourBotName
That's the success signal. Later restarts skip almost all of that — the target/ cache means only your own code rebuilds.
💡 Tip: Watch the console during the first build. If it dies partway with exit code 137, that's an out-of-memory kill on the compile — trim dependencies or move to a plan with more RAM. Keeping the crate list short is the single best thing you can do for build time and memory here.
Verify it works
Once you see Listening as …, type !ping in a channel the bot can see. It replies Pong!. That confirms the whole chain: the project compiled, Serenity connected with your token, and the handler fired. No reply? Check that Message Content is enabled in the portal and that the bot has permission to read and send in that channel.
Troubleshooting
Client error: Gateway(InvalidAuthentication)— the token intoken.txtis wrong or was reset. Copy a fresh one from the Developer Portal (resetting invalidates the old) and save it again..trim()already handles stray newlines. See Bot appears offline.- Login rejected for a disallowed intent — you requested
MESSAGE_CONTENT(or another privileged intent) without enabling it on the Bot page. Toggle it on, or drop the intent if you don't need it. - First start takes forever — that's the initial compile, not a hang. Watch for
Compilingprogress;Finishedmeans it's done and later starts are fast. See Rust on Falix. - Build dies with exit code 137 — the compiler ran out of memory, common on small plans with a big dependency tree. Trim crates or add RAM. See Out of memory.
could not read token.txt— the file isn't in/home/container. Create it in the File Manager with the token as its only line.
Next steps
- Rust on Falix — the build cache and compile-time reality in depth
- Async Rust on Falix — how Tokio underpins Serenity
- Deploy your code with Git
- Serenity's own documentation and examples are at docs.rs/serenity