Discord Bot From Hello World to Container Apps
By Anatoly Mironov
Recently I Became Curious About Discord Bots and How They Work.
In this blog post, I am going to write about my learnings. While some parts might be familiar to you, I found it very amusing to start with a simple “Hello, World!” bot, run it locally, deploy it to the cloud, and integrate it with ChatGPT. Along the way, I learned a lot. I hope this post can inspire other people or at least serve as a reference to refresh my memory when I revisit this topic in the future.
All the code is available in my github repository: tollediscordbot001.
Hello, World! Discord Bot
My children and I have a Discord server that we use when playing games. We often use the voice chat and also communicate in the #general channel. Intrigued by Discord bots, I decided to delve deeper, especially since I’ve been working with Teams bots at my workplace. I wanted to explore how bot development works in Discord. To get started, I followed this tutorial:
In just an hour, I had my simple bot up and running. I created a new app in the Discord Developer Portal, requested permissions, and registered intents. The “intents” part wasn’t obvious to me initially, unlike in Teams bot development. However, it was essential for the bot to read messages and respond to them.
No need for External Access
Initially, I was puzzled by the absence of instructions on exposing the bot to the internet. To my surprise, it worked seamlessly according to the tutorial and other YouTube videos I watched. I had expected solutions like using ngrok or configuring router forwarding to enable access the bot in the local network. As it turns out, Discord bot communication happens via websockets, a system known as the Gateway. This realization was an “A-HA” moment for me, and I found myself wishing that communication in Teams bots was also through websockets rather than HTTPS.
Virtual Environment
Although I’m not well-versed in Python, I chose it as the programming language for this project. I also took the opportunity to learn more about virtual environments in Python. A virtual environment ensures that dependencies are installed only for your specific project, preventing pollution of the global Python environment on your computer. I discovered that venv is preinstalled in Python 3.3 and higher, eliminating the need to install virtualenv
.
To create a new virtual environment, use this command:
python -m venv tdb001-venv
Visual Studio Code
Undoubtedly, you’ve heard it before, but I must reiterate: Visual Studio Code is a fantastic tool! It offers intelligent code completion and syntax highlighting for Python, automatically loads the virtual environment, and recognizes runnable Python scripts. It’s a perfect companion for development.
Remote Connections in VS Code
While this may not be news, it’s worth highlighting. I tested the bot code on a Windows machine, a Raspberry Pi Zero W, and an Ubuntu Server. On my Windows machine, I used Visual Studio Code directly. For the other machines, I employed SSH for remote execution. At times, adjustments were necessary, and I relied on editors like nano
and vim
. Both are excellent tools, but I encountered issues with diacritics (Swedish ÅÄÖ) in nano
. While vim is powerful, it’s more suited for advanced users. However, Visual Studio Code shines when it comes to opening code folders with remote connections, particularly on an Ubuntu Server.
Regrettably, Raspberry Pi Zero W’s architecture isn’t supported, but other Raspberry Pi models work seamlessly.
For those interested in trying Remote Connections, I recommend following these simple instructions:
Docker containers
Although a Python virtual environment is beneficial, there are even better ways to isolate, secure, and run your solutions. Enter Docker. I wanted to run my modest bot as a Docker container to refresh my Docker knowledge. I found an excellent tutorial that covered hosting a Discord bot with Docker and GitHub Actions on an Ubuntu Server:
On my Ubuntu server (an older laptop of mine), I installed Docker as a snap package.
Each time I work with Docker containers, I’m impressed by their speed. Container building and startup take mere seconds. I have full control over the container’s contents and can upgrade it anytime. Best of all, I can host it on any Cloud Provider. While I’ll likely end up using Azure due to familiarity and my Visual Studio subscription credits, the flexibility Docker offers is noteworthy.
Here’s a glimpse of my Dockerfile:
FROM python:3.10-bullseye
COPY requirements.txt /app/
WORKDIR /app
RUN pip install -r requirements.txt
COPY bot.py food.txt wisdom.txt ./
CMD ["python3", "bot.py"]
Environment Variables
In my Discord bot, I handle some secrets required for connecting to my Discord server and ChatGPT (more on that later). Locally, I use dotenv (.env
) files to store these secrets and load them as environment variables. Of course, the .env
file is excluded from the Git repository.
When working with Docker, you can pass values from the .env file to the container using the --env-file=.env
option when executing the docker run command:
docker run -d --name tollebot005 --env-file=.env tollediscordbot001:2023-08-04_4
Docker Compose
In my example, managing a single Docker container is straightforward. However, if there were multiple containers, it would involve more manual work. A more efficient way to manage multiple containers is using Docker Compose (docker-compose.yml
). This approach not only streamlines container declaration but also improves documentation by providing a declarative representation. Consequently, there’s less need for extensive README instructions, and commands become easier to recall.
docker compose up -d --build
ChatGPT Pre-prompt
Initially, my Discord bot echoed messages and offered simple responses, such as food suggestions and words of wisdom, or even fetching a joke from icanhazdadjoke.com.
I then ventured into integrating ChatGPT. The most straightforward way to interact with ChatGPT is by utilizing the openai
Python package.
I grasped the importance of maintaining chat history to provide context and continuity for ChatGPT’s responses.
Additionally, I discovered the concept of “pre-prompting” messages with a system
message. This approach allowed me to provide context and instruct the generative AI to adapt its language style for children, assign a name, and mention our Discord server.
While my GPT integration implementation is simple and just for fun, there’s potential for further data enrichment and session separation (channels and direct messages) in future iterations.
Azure Container Apps
Once my bot was functional locally, I wanted to explore cloud deployment. The cloud offers better elasticity and resource management compared to running my laptop or Raspberry Pi continuously. My curiosity led me to Azure Container Apps.
A tutorial from Microsoft Learn served as an excellent starting point:
This guide walked me through enabling container app features in Azure CLI and deploying the solution to Azure.
A standout feature of Azure Container Apps is the consumption plan, which provides a generous Free Grant tier. This makes it cost-effective for experimenting with small Discord bots, making it an ideal platform for learning about both Azure and the cloud.
Azure Container Registry
However, one cost to consider is the Azure Container Registry. After running the bot for a few days, I noticed the only expense in my resource group was associated with the Azure Container Registry.
While it’s not a significant cost, given the Basic SKU (Azure Container Registry Pricing), those looking to minimize expenses while experimenting might consider Github Packages, particularly if their bot resides in a public repository.
I’m not suggesting that services should be entirely free. I’m speaking from the perspective of an inquisitive individual eager to learn and leverage the cloud’s elasticity. I foresee revisiting Publishing Docker images to Github Packages in the future.
Github Actions
This marked my first foray into using Github Actions. The Azure CLI command from the earlier quickstart took care of everything, including generating the necessary YAML files familiar to me from Azure DevOps Pipelines. The key difference is that Github Actions workflows are defined in files within the .github/workflows
directory.
The integration worked smoothly, and I could customize certain aspects to suit my requirements.
One aspect I’m still grappling with is Azure Credentials within Github Actions. A new service principal was created with Contributor permissions on my resource group, yet no secrets or certificates were shared. How is Github able to successfully execute deployments?
Managing Secrets
As mentioned earlier, I needed to tweak the Github Actions workflow. A notable addition was handling secrets, including tokens and keys required for my bot to communicate with Discord and ChatGPT. I chose to store these secrets in Github’s secrets vault, passing them to the container app as environment variables. While suitable for my modest bot, another option would involve utilizing an Azure Key Vault for enhanced security.
To implement this, I created new secrets in the Github Project Settings and added a new step to my workflow. Additionally, in the initial container app update, I included the environmentVariables
option. This setup worked seamlessly.
For the curious, my workflow can be explored here: tollediscordbot001-AutoDeployTrigger-12ef6d50-5bd6-484c-9910-0ff3a8e25eed.yml
For further reading:
Min and Max Replicas
One of Azure Container Apps’ strengths lies in its automatic scaling capabilities. In the default configuration, if no requests are received, the app scales down to zero replicas. “Replicas” refer to container instances of the same revision. However, Discord Bots operate differently, not relying on incoming requests.
Upon my initial attempt, I noticed my bot was responsive but would go offline after a while. This was due to automatic downscaling.
This behavior wasn’t ideal, as there was no user-initiated way to wake the bot up. I resolved this by creating a new revision and setting the minimum replicas to 1.
I adjusted the Github Actions workflow to accommodate both minimum and maximum replicas set at 1, aligning with my bot’s testing requirements.
For further insights:
Discord Bot Shards
A topic I’m leaving for future exploration is Sharding
in Discord Bots. While running instances of my bot locally and in the cloud, I noticed that they both responded to all messages. This resulted in confusing message duplication:
The log stream even indicated that the Shard ID was None:
Discord Bot Commands
When it comes to the actual Discord bot functionality, I’ve only scratched the surface. There’s much more to explore, including the shards I mentioned earlier. Another intriguing avenue is Commands. Rather than parsing messages and reacting, I could implement commands for more structured interactions. However, that’s a topic for another time.
In Conclusion
And that concludes my journey. I hope this post serves as an inspiration to others. It was a rewarding experience to create and witness my first Discord bot in action, set up continuous deployment to Azure Cloud using Github Actions, and acquire a host of small yet crucial insights along the way.
And the last but not the least
It was my first time I tried ChatGPT as a grammar and style correcting tool. I must say, it helped me a lot. So besides all the technical insights I gathered some language insights.