---
title: "How to create a Discord Bot using the .NET worker template and host it on Azure Container Instances"
excerpt: "Learn how to develop a Discord bot using the .NET worker template, containerize it using Docker, push the container image to Azure Container Registry, and host it on Azure Container Instances."
publishDate: "2021-07-11T00:00:00"
createDate: "2021-07-08T06:36:43.817"
updateDate: "2022-09-11T23:38:02.037"
category: "azure"
categoryName: "Azure"
tags: [".NET","Azure",".NET Core","Docker","containers","Azure Container Registry","Azure Container Instances"]
permalink: "/blog/azure/how-to-create-a-discord-bot-using-the-dotnet-worker-template-and-host-it-on-azure-container-instances"
canonical: "/blog/azure/how-to-create-a-discord-bot-using-the-dotnet-worker-template-and-host-it-on-azure-container-instances"
image: {"src":"/media/wtmnozc5/discord-bot-using-the-dotnet-long.png"}
imageAlt: ".NET Bot, Azure, and Discord are together in a Discord server alongside title: How to create a Discord Bot using the .NET worker template and host it on Azure Container Instances"
imageBackgroundColor: "#32353b"
thumbnailImage: {"src":"/media/2crmmamu/discord-bot-using-the-dotnet-square.png"}
thumbnailImageAlt: ".NET Bot, Azure, and Discord are together in a Discord server"
metaTitle: "How to create a Discord Bot using the .NET worker template and host it on Azure Container Instances"
metaDescription: "Learn how to develop a Discord bot using the .NET worker template, containerize it using Docker, push the container image to Azure Container Registry, and host it on Azure Container Instances."
ogTitle: "How to create a Discord Bot using the .NET worker template and host it on Azure Container Instances"
ogDescription: "Learn how to develop a Discord bot using the .NET worker template, containerize it using Docker, push the container image to Azure Container Registry, and host it on Azure Container Instances."
ogImage: {"src":"/media/ubzhka1a/discord-bot-using-the-dotnet-social.png"}
ogImageAlt: ".NET Bot, Azure, and Discord are together in a Discord server alongside title: How to create a Discord Bot using the .NET worker template and host it on Azure Container Instances"
relatedPostKeys: ["3a907326cb314571bd1f042f0a92304d","e8911bbfbdfe4c05bd5aaf8040221a7e","ca698c8c39f64a7abf52120d2c688e22"]
noIndex: false
noFollow: false
sitemapExclude: false
source: {"nodeId":1520,"contentKey":"01d1f05669f64abaa1eceaebb26f2c03","sortOrder":99}
---

Discord is a communication platform originally built for the gaming audience, but it has also become popular with other groups. Many developer communities have also moved from Slack to Discord. With Discord, you can create servers to build your community. On your server, you can chat, make voice calls, video calls, stream, and more.

There are multiple ways you can extend Discord as a developer. In this tutorial, you'll learn how to create a bot for Discord.

## Discord Bots {#discord-bots}




A bot in Discord can be added to both voice and text channels where it can listen and respond to users' input. To real users, a bot looks just like a normal participant that can be online and offline, although explicitly marked as a bot. Permission management on channels also works the same for users and bots. 


To build your own bot, you need to connect to Discords WebSocket APIs. This can be a simple command-line application running on a Raspberry PI, but you'll use an Azure Container Instance to host your .NET project. In your .NET project, you'll use the [DSharpPlus](https://github.com/DSharpPlus/DSharpPlus) library to more easily interact with the Discord API.

## Prerequisites {#prerequisites}

For this tutorial, you will need the following:


-   Operating System supported by Docker & .NET (Core)
-   Docker
-   .NET 5 SDK (earlier versions should work too)
-   Azure subscription
-   Azure CLI
-   A Discord Account


This tutorial is built using **Ubuntu** and the **bash** shell. The tutorial should work with other operating systems and shells, but you may need to make slight adjustments.


You can find [the source code for the Discord bot on this GitHub repository](https://github.com/Swimburger/DotNetDiscordBotAcs).




## Create a Discord bot application in the Discord Developer Portal {#create-a-discord-bot-application-in-the-discord-developer-portal}

Before you can start developing a bot for Discord, you need to create a Discord application in the developer portal.


Navigate to <a rel="noopener" href="https://discord.com/developer" target="_blank">discord.com/developer</a> and click on the "New Application" button.




<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/gojlk00n/discord-developer-portal-create-new-application.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/gojlk00n/discord-developer-portal-create-new-application.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/gojlk00n/discord-developer-portal-create-new-application.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/gojlk00n/discord-developer-portal-create-new-application.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/gojlk00n/discord-developer-portal-create-new-application.png 860w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1077/media/gojlk00n/discord-developer-portal-create-new-application.png 1077w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/gojlk00n/discord-developer-portal-create-new-application.png 1120w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1436/media/gojlk00n/discord-developer-portal-create-new-application.png 1436w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1680/media/gojlk00n/discord-developer-portal-create-new-application.png 1680w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=2240/media/gojlk00n/discord-developer-portal-create-new-application.png 2240w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 1120px" width="1120" height="457" alt="Discord Developer Portal applications screen. Every application is listed as a tile. The cursor is clicking the &quot;New Application&quot; button" loading="lazy" decoding="async">
</picture>

Enter a name for your application (the name of your bot) and click the "Create" button.

<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 500w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=750/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 750w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/ht1gbds3/discord-developer-portal-create-new-application-modal.png 860w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 500px" width="500" height="441" alt="Discord's create application modal asking for the name of the application. Cursor is clicking on &quot;Create&quot; button." loading="lazy" decoding="async">
</picture>

After the application is created, you are taken to the settings for the application. <br>Navigate to the "Bot" tab using the side navigation on the left, and click on the "Add Bot" button.

<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/j3xdg2kb/discord-developer-portal-build-a-bot.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 860w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1077/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 1077w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 1120w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1436/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 1436w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1680/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 1680w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=2240/media/j3xdg2kb/discord-developer-portal-build-a-bot.png 2240w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 1120px" width="1120" height="336" alt="Bot tab of the Discord application settings. Cursor is clicking on &quot;Add Bot&quot; button" loading="lazy" decoding="async">
</picture>

When prompted, confirm to add a bot to this app using the "Yes, do it!" button.


Now that the bot functionality has been added, you can copy the **Discord bot token**.<br><span style="text-decoration: underline;">Click on the "Copy" button and keep it somewhere for later</span>.




<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/o1tp5muv/discord-developer-portal-bot.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/o1tp5muv/discord-developer-portal-bot.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/o1tp5muv/discord-developer-portal-bot.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/o1tp5muv/discord-developer-portal-bot.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/o1tp5muv/discord-developer-portal-bot.png 860w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1077/media/o1tp5muv/discord-developer-portal-bot.png 1077w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/o1tp5muv/discord-developer-portal-bot.png 1120w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1436/media/o1tp5muv/discord-developer-portal-bot.png 1436w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1680/media/o1tp5muv/discord-developer-portal-bot.png 1680w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=2240/media/o1tp5muv/discord-developer-portal-bot.png 2240w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 1120px" width="1120" height="485" alt="Bot tab of Discord application after bot has been added. Cursor is clicking on &quot;Copy&quot; button to copy the Discord bot token." loading="lazy" decoding="async">
</picture>



<p class="panel panel--warning">August 31, 2022 Discord Update: <a href="https://support-dev.discord.com/hc/en-us/articles/4404772028055-Message-Content-Privileged-Intent-FAQ">Reading Message Content is now a Privileged Intent</a>. For this tutorial to work, you'll need to go into your Discord Application &gt; Bot and find the "<strong>MESSAGE CONTENT INTENT</strong>" toggle, enable it and click "<strong>Save Changes</strong>". Once your bot reaches 100 or more servers, this will require verification and approval. Instead of using Message Content, Discord recommends using the <strong><a rel="noopener noreferrer" href="https://discord.com/developers/docs/interactions/slash-commands" target="_blank">Interactions API</a></strong> to bring new features like <strong><a rel="noopener noreferrer" href="https://discord.com/developers/docs/interactions/slash-commands" target="_blank">Slash Commands</a></strong>, <strong><a rel="noopener noreferrer" href="https://discord.com/developers/docs/interactions/message-components#buttons" target="_blank">Buttons</a></strong>, and <strong><a rel="noopener noreferrer" href="https://discord.com/developers/docs/interactions/message-components#select-menus" target="_blank">Select Menus</a></strong> that don't require access to message content.</p>

Navigate to the "OAuth2" tab and select the following checkboxes:


-   Under "**Scopes**" check "**bot**"
-   Under "**Bot permissions**" check "**Send Messages**"




<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/fq1bgyiw/discord-developer-portal-oauth2.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/fq1bgyiw/discord-developer-portal-oauth2.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/fq1bgyiw/discord-developer-portal-oauth2.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/fq1bgyiw/discord-developer-portal-oauth2.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/fq1bgyiw/discord-developer-portal-oauth2.png 860w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1077/media/fq1bgyiw/discord-developer-portal-oauth2.png 1077w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/fq1bgyiw/discord-developer-portal-oauth2.png 1120w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1436/media/fq1bgyiw/discord-developer-portal-oauth2.png 1436w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1680/media/fq1bgyiw/discord-developer-portal-oauth2.png 1680w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=2240/media/fq1bgyiw/discord-developer-portal-oauth2.png 2240w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 1120px" width="1120" height="498" alt="OAuth2 tab of Discord application with bot role and &quot;Send Messages&quot; permission enabled. The cursor is clicking on the &quot;Copy&quot; button to copy the OAuth2 URL." loading="lazy" decoding="async">
</picture>




Copy the URL and paste it into your browser. Using this URL, you can add your Discord bot to servers.<br>Select your server in the dropdown and click "Continue". If you don't have a server, here's a helpful guide on <a rel="noopener" href="https://support.discord.com/hc/en-us/articles/204849977-How-do-I-create-a-server-" target="_blank">how to create a Discord Server</a>.




<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/xw3faesz/discord-bot-oauth2.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/xw3faesz/discord-bot-oauth2.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/xw3faesz/discord-bot-oauth2.png 500w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/xw3faesz/discord-bot-oauth2.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/xw3faesz/discord-bot-oauth2.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=750/media/xw3faesz/discord-bot-oauth2.png 750w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 500px" width="500" height="591" alt="Add discord bot to your server using OAuth2 flow. Select the server from the dropdown. Cursor is clicking on &quot;Continue&quot; button." loading="lazy" decoding="async">
</picture>

Leave the "Send Messages" checkbox checked and click on the "Authorize" button.

<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/rzih3t2c/discord-bot-oauth2-confirm.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/rzih3t2c/discord-bot-oauth2-confirm.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=500/media/rzih3t2c/discord-bot-oauth2-confirm.png 500w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/rzih3t2c/discord-bot-oauth2-confirm.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/rzih3t2c/discord-bot-oauth2-confirm.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=750/media/rzih3t2c/discord-bot-oauth2-confirm.png 750w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/rzih3t2c/discord-bot-oauth2-confirm.png 860w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 500px" width="500" height="736" alt="Confirm permissions screen for adding Discord bot to your server using OAuth2 flow. Cursor is clicking on &quot;Authorize&quot; button." loading="lazy" decoding="async">
</picture>

The bot has now joined your Discord server!

<picture>
  <img src="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/d30p4op2/discord-server.png" srcset="https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=430/media/d30p4op2/discord-server.png 430w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=645/media/d30p4op2/discord-server.png 645w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=718/media/d30p4op2/discord-server.png 718w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=860/media/d30p4op2/discord-server.png 860w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1077/media/d30p4op2/discord-server.png 1077w, https://swimburger.net/cdn-cgi/image/format=auto,quality=90,width=1120/media/d30p4op2/discord-server.png 1120w" sizes="(max-width: 480px) 430px, (max-width: 768px) 718px, 1120px" width="1120" height="626" alt="Discord server with a Discord bot whom is offline." loading="lazy" decoding="async">
</picture>

But the bot is offline and doesn't interact with you 😟

Unlike other communication platforms featuring bots that rely on REST APIs and webhooks, Discord bots use a real-time connection for the duration they are online, usually 24/7. <br>Let's set up that real-time connection between a .NET console application and Discord.

## Develop a Discord bot using .NET {#develop-a-discord-bot-using-net}




Run the following commands to create a<strong> .NET worker project</strong> and change the current directory into the project:




<pre style="color:#000000;background:#ffffff;">dotnet <span style="color:#2a00ff; ">new</span> <span style="color:#2a00ff; ">worker</span> -n <span style="color:#2a00ff; ">DiscordBot</span>&#10;cd <span style="color:#2a00ff; ">DiscordBot</span>&#10;</pre>




The worker template is similar to the console template but comes with code for developing long-running background services. This template is ideal for the Discord bot since the bot has to be running non-stop. <br>The worker template also comes with useful defaults & features which you may be used to from ASP.NET Core applications. For example, later in the tutorial, you'll take advantage of the dependency injection which will inject the logger & configuration.


Luckily, there's this amazing open-source project called **DSharpPlus** which is doing the hard work of implementing the Discord functionality and providing an easy-to-use .NET API.

Run the following command to add the **DSharpPlus** library to the project:

<pre style="color:#000000;background:#ffffff;">dotnet <span style="color:#2a00ff; ">add</span> <span style="color:#2a00ff; ">package</span> <span style="color:#2a00ff; ">DSharpPlus</span>&#10;</pre>

Update the *Worker.cs* file with the content below:

<pre style="color:#000000;background:#ffffff;"><span style="color:#7f0055; font-weight:bold; ">using</span> System;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> System.Collections.Generic;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> System.Linq;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> System.Threading;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> System.Threading.Tasks;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> DSharpPlus;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> DSharpPlus.EventArgs;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> Microsoft.Extensions.Configuration;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> Microsoft.Extensions.Hosting;&#10;<span style="color:#7f0055; font-weight:bold; ">using</span> Microsoft.Extensions.Logging;&#10;&#10;<span style="color:#7f0055; font-weight:bold; ">namespace</span> DiscordBot&#10;{&#10;    <span style="color:#7f0055; font-weight:bold; ">public</span> <span style="color:#7f0055; font-weight:bold; ">class</span> Worker : BackgroundService&#10;    {&#10;        <span style="color:#7f0055; font-weight:bold; ">private</span> ILogger&lt;Worker&gt; logger;&#10;        <span style="color:#7f0055; font-weight:bold; ">private</span> IConfiguration configuration;&#10;        <span style="color:#7f0055; font-weight:bold; ">private</span> DiscordClient discordClient;&#10;&#10;        <span style="color:#7f0055; font-weight:bold; ">public</span> Worker(ILogger&lt;Worker&gt; logger, IConfiguration configuration)&#10;        {&#10;            <span style="color:#7f0055; font-weight:bold; ">this</span>.logger = logger;&#10;            <span style="color:#7f0055; font-weight:bold; ">this</span>.configuration = configuration;&#10;        }&#10;&#10;        <span style="color:#7f0055; font-weight:bold; ">public</span> <span style="color:#7f0055; font-weight:bold; ">override</span> async Task StartAsync(CancellationToken cancellationToken)&#10;        {&#10;            logger.LogInformation(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Starting discord bot</span><span style="color:#2a00ff; ">"</span>);&#10;&#10;            <span style="color:#7f0055; font-weight:bold; ">string</span> discordBotToken = configuration[<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBotToken</span><span style="color:#2a00ff; ">"</span>];&#10;            discordClient = <span style="color:#7f0055; font-weight:bold; ">new</span> DiscordClient(<span style="color:#7f0055; font-weight:bold; ">new</span> DiscordConfiguration()&#10;            {&#10;                Token = discordBotToken,&#10;                TokenType = TokenType.Bot,&#10;                Intents = DiscordIntents.AllUnprivileged&#10;            });&#10;&#10;            discordClient.MessageCreated += OnMessageCreated;&#10;            await discordClient.ConnectAsync();&#10;        }&#10;&#10;        <span style="color:#7f0055; font-weight:bold; ">protected</span> <span style="color:#7f0055; font-weight:bold; ">override</span> Task ExecuteAsync(CancellationToken stoppingToken) =&gt;  Task.CompletedTask;&#10;&#10;        <span style="color:#7f0055; font-weight:bold; ">public</span> <span style="color:#7f0055; font-weight:bold; ">override</span> async Task StopAsync(CancellationToken cancellationToken)&#10;        {&#10;            discordClient.MessageCreated -= OnMessageCreated;&#10;            await discordClient.DisconnectAsync();&#10;            discordClient.Dispose();&#10;            logger.LogInformation(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">Discord bot stopped</span><span style="color:#2a00ff; ">"</span>);&#10;        }&#10;&#10;        <span style="color:#7f0055; font-weight:bold; ">private</span> async Task OnMessageCreated(DiscordClient client, MessageCreateEventArgs e)&#10;        {&#10;            <span style="color:#7f0055; font-weight:bold; ">if</span> (e.Message.Content.StartsWith(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">ping</span><span style="color:#2a00ff; ">"</span>, StringComparison.OrdinalIgnoreCase))&#10;            {&#10;                logger.LogInformation(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">pinged, responding with pong!</span><span style="color:#2a00ff; ">"</span>);&#10;                await e.Message.RespondAsync(<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">pong!</span><span style="color:#2a00ff; ">"</span>);&#10;            }&#10;        }&#10;    }&#10;}&#10;</pre>

Let's summarize what the code above does:


-   The built-in dependency injection injects an ILogger and an IConfiguration object into the constructor of the Worker class. You can log information using the logger and retrieve external configuration from the configuration object.<br>The worker template has multiple configuration sources preconfigured such as JSON files, environment variables, user secrets, command-line arguments, and more.<br>Both constructor parameters are stored in private fields so the other parts of the class can use them.
-   The Worker class inherits from the BackgroundService abstract class. You have to implement the ExecuteAsync method, but you can also override other methods like StartAsync and StopAsync.<br>For this application, you'll want to initialize the bidirectional connection with Discord in StartAsync and disconnect from it in StopAsync.
    -   **StartAsync**:
        -   To connect to Discord, the DiscordClient requires the token you copied earlier. Instead of hardcoding the token, the token is pulled from the configuration object using configuration\["DiscordBotToken"\].<br>You'll configure the "DiscordBotToken" configuration later.
        -   The Discord client is created with the discordBotToken.
        -   You can add an event handler to the MessageCreated event to wire some code whenever a message is sent to the server. <br>When a message is sent, the OnMessageCreated method will be invoked.
        -   lastly, the discord client establishes the bidirectional connection using discordClient.ConnectAsync()
    -   **ExecuteAsync**: This is usually where you perform your work, but in this case, you don't need to do anything. To comply with the abstract interface, you do have to implement the method.<br>You can simply return Task.CompletedTask.
    -   **StopAsync**: When the application is being shut down, the StopAsync method will be called. Here you should clean up whatever resources you setup during StartAsync:
        -   Remove the event handler
        -   Disconnect from Discord
        -   Dispose of the Discord client
-   OnMessageCreated is called whenever a message is sent to the Discord server. When the message starts with "ping", the bot will respond with "pong!"


Since the worker template comes with my external configuration sources pre-configured, you can configure the "DiscordBotToken" in many different ways.<br>For local development, the easiest way to do so would be to use the user-secrets commands in the .NET CLI.<br>Use the following commands to initialize user-secrets and set the DiscordBotToken user-secret:

<pre style="color:#000000;background:#ffffff;">dotnet <span style="color:#2a00ff; ">user-secrets</span> <span style="color:#2a00ff; ">init</span>&#10;dotnet <span style="color:#2a00ff; ">user-secrets</span> <span style="color:#2a00ff; ">set</span> <span style="color:#2a00ff; ">DiscordBotToken</span> <span style="color:#2a00ff; ">[YOUR_DISCORD_BOT_TOKEN]</span>&#10;</pre>

Now that the code and configuration are finished, run the application using the .NET CLI:

<pre style="color:#000000;background:#ffffff;">dotnet <span style="color:#2a00ff; ">run</span>&#10;</pre>

Now that your bot is running, switch to your Discord application and send "ping" into the chat of your server.<br>You'll notice that your bot will immediately respond with 'pong!' 👏

<p><img src="/media/1uwke52x/ping-pong.gif" alt="Discord user sends &quot;Ping&quot; into the chat and a Discord bot responds with &quot;Pong!&quot;" width="1271" height="708"></p>



## Hosting the Discord bot on Azure Container Instances {#hosting-the-discord-bot-on-azure-container-instances}

There are a lot of options to host your Discord bot. Anywhere that runs .NET Core can run your bot. You could run it on your computer, but then your bot will be offline when you turn off your computer.<br>You could run it on something like a Raspberry Pi and just keep the process running forever. Alternatively, you could host it in a cloud, like Microsoft Azure. In Azure, you also have multiple options:


-   You could create an **Azure Virtual Machine** and run your application there.
-   You can <a href="/blog/azure/creating-a-discord-bot-using-net-core-and-azure-app-services" title="Creating a Discord Bot using .NET Core and Azure App Services">use <strong>Azure App Service</strong> and run it inside of a continuous <strong>webjob</strong> as blogged here</a>.
-   You could containerize your application and run it inside of an **Azure Container Instance**.


In this tutorial, you'll learn how to containerize your .NET worker application and host it in an Azure Container Instance.

### Containerize the Discord bot using Docker {#containerize-the-discord-bot-using-docker}

A huge benefit of containerizing your application is that you can deploy your container not just to Azure Container Instances, but any place with container support.<br>This makes your application very portable across clouds and vendors.

To containerize your .NET application, create a new file called "Dockerfile" and add the following content:

<pre style="color:#000000;background:#ffffff;">FROM mcr.microsoft.com/dotnet/runtime:5.0 AS base&#10;WORKDIR /app&#10;&#10;# Creates a non-root user with an explicit UID and adds permission to access the /app folder&#10;# For more info, please refer to <span style="color:#3f3fbf; ">https://aka.ms/vscode-docker-dotnet-configure-containers</span>&#10;RUN adduser -u 5678 --disabled-password --gecos <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">"</span> appuser &amp;&amp; chown -R appuser /app&#10;USER appuser&#10;&#10;FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build&#10;WORKDIR /src&#10;COPY [<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBot.csproj</span><span style="color:#2a00ff; ">"</span>, <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">./</span><span style="color:#2a00ff; ">"</span>]&#10;RUN dotnet restore <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBot.csproj</span><span style="color:#2a00ff; ">"</span>&#10;COPY . .&#10;WORKDIR <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">/src/.</span><span style="color:#2a00ff; ">"</span>&#10;RUN dotnet build <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBot.csproj</span><span style="color:#2a00ff; ">"</span> -c Release -o /app/build&#10;&#10;FROM build AS publish&#10;RUN dotnet publish <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBot.csproj</span><span style="color:#2a00ff; ">"</span> -c Release -o /app/publish&#10;&#10;FROM base AS final&#10;WORKDIR /app&#10;COPY --from=publish /app/publish .&#10;ENTRYPOINT [<span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">dotnet</span><span style="color:#2a00ff; ">"</span>, <span style="color:#2a00ff; ">"</span><span style="color:#2a00ff; ">DiscordBot.dll</span><span style="color:#2a00ff; ">"</span>]&#10;</pre>

The Dockerfile will tell Docker how to build your container image and how to run your container. <br>In summary, the Dockerifle does the following:


-   Using Microsoft's .NET SDK docker image,
    -   Copy over only the "DiscordBot.csproj" file which contains the NuGet package references
    -   run dotnet restore to restore the NuGet packages in "DiscordBot.csproj"
    -   Copy over all the other files from your machine into Docker
    -   run dotnet build in release mode and put the output at '/app/build'
    -   run dotnet publish in release mode and put the output at '/app/publish'
-   Using a separate docker Image which only contains the .NET runtime, but not the SDK
    -   Copy over the files form '/app/publish' from the previous stage into the current stage
    -   Tell docker to run dotnet DiscordBot.dll when the container starts


Run the following docker command to build the container:

<pre style="color:#000000;background:#ffffff;">docker <span style="color:#2a00ff; ">build</span> -t <span style="color:#2a00ff; ">discord-bot-image:latest</span> <span style="color:#2a00ff; ">.</span>&#10;</pre>




This command will build the container image following all the instructions in the Dockerfile and tag the image with '**discord-bot-image**' as the name and '**latest**' as the tag.


Now that your docker image is built, you can run it with the following command:

<pre style="color:#000000;background:#ffffff;">docker <span style="color:#2a00ff; ">run</span> -it <span style="color:#2a00ff; ">discord-bot-image:latest</span>&#10;&#10;<span style="color:#3f7f59; "># Output: </span>&#10;<span style="color:#3f7f59; ">#   info: DiscordBot.Worker[0]</span>&#10;<span style="color:#3f7f59; ">#         Starting discord bot</span>&#10;<span style="color:#3f7f59; ">#   [2021-07-09 03:06:03 +00:00] [101 /Startup     ] [Info ] DSharpPlus, version 4.0.1</span>&#10;<span style="color:#3f7f59; ">#   Unhandled exception. System.Exception: Authentication failed. Check your token and try again.</span>&#10;<span style="color:#3f7f59; ">#    ---&gt; DSharpPlus.Exceptions.UnauthorizedException: Unauthorized: 401</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.Net.DiscordApiClient.GetGatewayInfoAsync()</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.BaseDiscordClient.GetGatewayInfoAsync(String token)</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.DiscordClient.InternalUpdateGatewayAsync()</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.DiscordClient.InternalConnectAsync()</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.DiscordClient.ConnectAsync(DiscordActivity activity, Nullable`1 status, Nullable`1 idlesince)</span>&#10;<span style="color:#3f7f59; ">#      --- End of inner exception stack trace ---</span>&#10;<span style="color:#3f7f59; ">#      at DSharpPlus.DiscordClient.ConnectAsync(DiscordActivity activity, Nullable`1 status, Nullable`1 idlesince)</span>&#10;<span style="color:#3f7f59; ">#      at DiscordBot.Worker.StartAsync(CancellationToken cancellationToken) in /src/Worker.cs:line 38</span>&#10;<span style="color:#3f7f59; ">#      at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)</span>&#10;<span style="color:#3f7f59; ">#      at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)</span>&#10;<span style="color:#3f7f59; ">#      at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)</span>&#10;<span style="color:#3f7f59; ">#      at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)</span>&#10;<span style="color:#3f7f59; ">#      at DiscordBot.Program.Main(String[] args) in /src/Program.cs:line 14</span>&#10;</pre>

The container ran, but there's an exception occurring. The exception occurs because the 'DiscordBotToken' is not configured in any of the configuration sources used by the worker template.<br>You configured the token using 'user-secrets', but that isn't accessible to the docker container. As an alternative to 'user-secrets', you can pass in the configuration using an environment variable.

Run the following command with the -e argument:

<pre style="color:#000000;background:#ffffff;">docker <span style="color:#2a00ff; ">run</span> -it <span style="color:#2a00ff; ">discord-bot-image:latest</span> -e <span style="color:#2a00ff; ">DiscordBotToken=[YOUR_DISCORD_BOT_TOKEN]</span>&#10;&#10;<span style="color:#3f7f59; "># Output:</span>&#10;<span style="color:#3f7f59; ">#   info: DiscordBot.Worker[0]</span>&#10;<span style="color:#3f7f59; ">#         Starting discord bot</span>&#10;<span style="color:#3f7f59; ">#   [2021-07-09 03:13:37 +00:00] [101 /Startup     ] [Info ] DSharpPlus, version 4.0.1</span>&#10;<span style="color:#3f7f59; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#3f7f59; ">#         Application started. Press Ctrl+C to shut down.</span>&#10;<span style="color:#3f7f59; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#3f7f59; ">#         Hosting environment: Production</span>&#10;<span style="color:#3f7f59; ">#   info: Microsoft.Hosting.Lifetime[0]</span>&#10;<span style="color:#3f7f59; ">#         Content root path: /app</span>&#10;<span style="color:#3f7f59; ">#   info: DiscordBot.Worker[0]</span>&#10;<span style="color:#3f7f59; ">#         pinged, responding with pong!</span>&#10;</pre>



### Push your Docker image to Azure Container Registry {#push-your-docker-image-to-azure-container-registry}

Before you can create an Azure Container Instance, you'll need to push the Docker image to a Docker registry.<br>You can use any Docker registry, but for this tutorial, you'll create an Azure Container Registry (ACR) and push the image to your ACR.

Make sure you have the Azure CLI installed and are logged in. Then, create a new resource group called "DiscordBotRg":

<pre style="color:#000000;background:#ffffff;">az <span style="color:#2a00ff; ">group</span> <span style="color:#2a00ff; ">create</span> <span style="color:#2a00ff; ">--location</span> <span style="color:#2a00ff; ">eastus</span> <span style="color:#2a00ff; ">--resource-group</span> <span style="color:#2a00ff; ">DiscordBotRg</span>&#10;</pre>

Create a new Azure Container Registry:

<pre style="color:#000000;background:#ffffff;">az acr create --name DiscordBotAcr --resource-group DiscordBotRg <span style="color:#2a00ff; ">\</span>&#10;              --sku Basic <span style="color:#2a00ff; ">\</span>&#10;              --location eastus&#10;</pre>

Log into your new ACR:

<pre style="color:#000000;background:#ffffff;">az acr login --name DiscordBotAcr&#10;</pre>

Push the docker image you created previously by tagging it with the registry URL and then pushing it with the native docker command:

<pre style="color:#000000;background:#ffffff;">docker tag discord-bot-image:latest <span style="color:#3f3fbf; ">discordbotacr.azurecr.io/discord-bot-image</span>:latest&#10;docker push <span style="color:#3f3fbf; ">discordbotacr.azurecr.io/discord-bot-image</span>:latest&#10;</pre>

Alternatively, you can use the Azure Container Registry build command to build the docker image locally and push it put to ACR:

<pre style="color:#000000;background:#ffffff;">az acr build -r DiscordBotAcr -t discord-bot-image:latest <span style="color:#7f0055; font-weight:bold; ">.</span>&#10;</pre>

Your Discord bot container image is now in Azure Container Registry and you can move on to creating an Azure Container Instance!

### Deploy your Discord bot to Azure Container Instances {#deploy-your-discord-bot-to-azure-container-instances}

With your Docker image up in your registry, you should be able to create an Azure Container Instance, but by default, the container instance service won't have permission to access the registry.<br>As a result, the container instance won't be able to pull your container image. There are multiple ways to give access to the registry. <br>In this tutorial, you'll create a service principal in Azure and give it permission to pull from your registry.

Run the following bash script:

<pre style="color:#000000;background:#ffffff;"><span style="color:#3f7f59; "># Obtain the full registry ID for subsequent command args</span>&#10;ACR_REGISTRY_ID=$(az acr show --name DiscordBotAcr --query id --output tsv)&#10;&#10;SP_PASSWD=$(az ad sp create-<span style="color:#7f0055; font-weight:bold; ">for</span>-rbac --name acr-service-principal --scopes $ACR_REGISTRY_ID --role acrpull --query password --output tsv)&#10;SP_APP_ID=$(az ad sp list --display-name acr-service-principal --query <span style="color:#2a00ff; ">[</span><span style="color:#2a00ff; ">0</span><span style="color:#2a00ff; ">]</span><span style="color:#7f0055; font-weight:bold; ">.</span>appId -o tsv)&#10;</pre>

The script will


-   create a service principal
-   give the service principal the '**acrpull**' role on your Azure Container Registry
-   store the password of the service principal in SP\_PASSWD
-   store the service principal app id in SP\_APP\_ID


You can use SP\_APP\_ID as the username and SP\_PASSWD as the password to log into your registry.<br>Now you have the credentials required to let your upcoming Azure Container Instance pull from your registry.

Run the following command to create the Azure Container Instance:

<pre style="color:#000000;background:#ffffff;">az container create --resource-group DiscordBotRg <span style="color:#2a00ff; ">\</span>&#10;                    --name discord-bot-container <span style="color:#2a00ff; ">\</span>&#10;                    --image <span style="color:#3f3fbf; ">discordbotacr.azurecr.io/discord-bot-image</span>:latest <span style="color:#2a00ff; ">\</span>&#10;                    --registry-username $SP_APP_ID <span style="color:#2a00ff; ">\</span>&#10;                    --registry-password $SP_PASSWD <span style="color:#2a00ff; ">\</span>&#10;                    --secure-environment-variables DiscordBotToken=<span style="color:#2a00ff; ">[</span><span style="color:#2a00ff; ">YOUR_DISCORD_BOT_TOKEN</span><span style="color:#2a00ff; ">]</span> <span style="color:#2a00ff; ">\</span>&#10;                    --location eastus&#10;</pre>

Make sure you replace \[YOUR\_DISCORD\_BOT\_TOKEN\] with your token. This will be stored in a secure environment variable which you won't be able to see in the Azure UI or APIs.

And you're done! That was a lot of steps to get to the end result, but now that everything is configured, you won't need to do much.<br>If you want to update the container instance with new code, push a new version of your container image to the ACR and restart the container instance.

## Summary {#summary}

In this tutorial you


- created a Discord application in the <strong>Discord Developer Portal </strong>
- created a Discord bot using the <strong>DSharpPlus</strong> library and the <strong>.NET worker template</strong>
- containerized the .NET application using <strong>Docker</strong>
- created a private docker registry with <strong>Azure Container Registry </strong>
- pushed your Docker image to your private registry
- created a <strong>service principal</strong> with access to <strong>pull</strong> from your registry
- and finally created an<strong> Azure Container Instance</strong> to run your Discord bot in a docker container


Instead of running your Docker container on Azure Container Instance, you could also host it on any other platform that supports Docker containers, whether it's under your desk or up in the cloud.

A lot of technologies were glossed over in this tutorial, so here are some resources to learn more about them:


- <a href="https://dsharpplus.github.io/">Documentation for the DSharpPlus library</a>
- <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/workers">An overview of the .NET Worker service</a> by the Microsoft Docs team.
- <a href="https://docs.microsoft.com/en-us/dotnet/core/docker/introduction">Introduction to .NET and Docker</a> by the Microsoft Docs team.
- <a href="https://docs.microsoft.com/en-us/dotnet/core/extensions/cloud-service?pivots=cli">Deploy a Worker Service to Azure</a> by <a href="https://twitter.com/davidpine7">David Pine</a> on Microsoft Docs. <br>This tutorial by David is the source of inspiration for the tutorial you just went through. Thank you, David!
- <a href="https://docs.microsoft.com/en-us/azure/container-registry/container-registry-authentication?tabs=azure-cli">An overview of different ways you can authenticate to Azure Container Registry</a> by the Microsoft Docs team.
