<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Fabricio Flores</title>
    <description>The latest articles on DEV Community by Fabricio Flores (@fabricioflores).</description>
    <link>https://dev.to/fabricioflores</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3757250%2F69692a6f-ba89-4c6c-ada8-80c2a09c263d.JPG</url>
      <title>DEV Community: Fabricio Flores</title>
      <link>https://dev.to/fabricioflores</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/fabricioflores"/>
    <language>en</language>
    <item>
      <title>/server start: How I Let My Friends Control Our Minecraft Server via Discord</title>
      <dc:creator>Fabricio Flores</dc:creator>
      <pubDate>Fri, 20 Feb 2026 15:37:28 +0000</pubDate>
      <link>https://dev.to/aws/server-start-how-i-let-my-friends-control-our-minecraft-server-via-discord-38l1</link>
      <guid>https://dev.to/aws/server-start-how-i-let-my-friends-control-our-minecraft-server-via-discord-38l1</guid>
      <description>&lt;p&gt;&lt;em&gt;BUZZ BUZZ BUZZ&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My phone is getting blown up in the middle of the night with calls, texts, and pings from my friends wanting to hop on our Minecraft server. Turns out I had accidentally turned off my ancient laptop that was hosting it. I begrudgingly get up from my cozy bed to boot up the server. Interrupted sleep, all to preserve the sacred annual 2-week Minecraft phase...&lt;/p&gt;

&lt;p&gt;So I put my thinking cap on. Is there a way that I could host the server in the cloud and let my friends turn it on and off themselves?&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to build a Discord bot that can interact with your AWS resources. We'll set up a Minecraft server on EC2 as our example, but this same pattern works for any AWS service.&lt;/p&gt;

&lt;p&gt;Before we dive in, here's what you'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/" rel="noopener noreferrer"&gt;AWS Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.docker.com/products/docker-desktop/" rel="noopener noreferrer"&gt;Docker&lt;/a&gt; (for packaging dependencies in a Linux environment)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.python.org/downloads/" rel="noopener noreferrer"&gt;Python 3.12&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.minecraft.net/" rel="noopener noreferrer"&gt;Minecraft Account&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  1. Create Discord Application
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://discord.com/developers/applications" rel="noopener noreferrer"&gt;Discord Developer Portal&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Create New Application&lt;/li&gt;
&lt;li&gt;Go to Bot → then click &lt;strong&gt;Reset Token&lt;/strong&gt; and save it securely&lt;/li&gt;
&lt;li&gt;Go to OAuth2 → URL Generator → Select &lt;code&gt;bot&lt;/code&gt; + &lt;code&gt;applications.commands&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Copy Generated URL and add bot to your server&lt;/li&gt;
&lt;li&gt;Note your &lt;strong&gt;Application ID&lt;/strong&gt; and &lt;strong&gt;Public Key&lt;/strong&gt; from General Information&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  2. Create PyNaCl Lambda Layer
&lt;/h3&gt;

&lt;p&gt;Discord requires signature verification on every request. The PyNaCl library handles this, but it has native dependencies — meaning we can't just pip install it on our Mac/Windows and upload to Lambda. We need to build it for Amazon Linux.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Build the layer zip (requires Docker)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Open up your terminal of choice and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; pynacl_layer
docker run &lt;span class="nt"&gt;--platform&lt;/span&gt; linux/amd64 &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/pynacl_layer:/out"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  public.ecr.aws/sam/build-python3.12 bash &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"pip install PyNaCl -t /tmp/python/lib/python3.12/site-packages/ &amp;amp;&amp;amp; cd /tmp &amp;amp;&amp;amp; zip -r /out/pynacl_layer.zip python"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Make sure Docker Desktop is running first!&lt;/strong&gt;&lt;br&gt;
You'll get a "Cannot connect to Docker daemon" error otherwise.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Upload it to Lambda&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Head to the &lt;a href="https://console.aws.amazon.com/lambda" rel="noopener noreferrer"&gt;Lambda console&lt;/a&gt; and navigate to &lt;strong&gt;Layers → Create layer&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Name:&lt;/strong&gt; &lt;code&gt;pynacl-layer&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Upload:&lt;/strong&gt; Choose the &lt;code&gt;pynacl_layer.zip&lt;/code&gt; file from your local machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compatible runtimes:&lt;/strong&gt; Python 3.12&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ubn7zttcxlrrt5ffeet.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ubn7zttcxlrrt5ffeet.png" alt="Configuring the PyNaCl layer" width="800" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Create Lambda Function
&lt;/h3&gt;

&lt;p&gt;Lambda lets us run code on-demand in the cloud. This is where we'll handle incoming Discord commands and control our EC2 instance.&lt;/p&gt;

&lt;p&gt;Now go to &lt;strong&gt;Functions → Create function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Basic settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Function name:&lt;/strong&gt; &lt;code&gt;discord-bot&lt;/code&gt; (or whatever you like)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runtime:&lt;/strong&gt; Python 3.12&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architecture:&lt;/strong&gt; x86_64 (must match your layer!)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click &lt;strong&gt;Create function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once created, scroll to the bottom of the page. You'll see a &lt;strong&gt;Layers&lt;/strong&gt; section. Click &lt;strong&gt;Add a layer&lt;/strong&gt;, select &lt;strong&gt;Custom layers&lt;/strong&gt;, pick &lt;code&gt;pynacl-layer&lt;/code&gt; from the dropdown, and hit &lt;strong&gt;Add&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Add your Discord public key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Configuration tab → Environment variables&lt;/li&gt;
&lt;li&gt;Click Edit → Add environment variable&lt;/li&gt;
&lt;li&gt;Key: DISCORD_PUBLIC_KEY&lt;/li&gt;
&lt;li&gt;Value: your public key from Discord Developer Portal (General Information page)&lt;/li&gt;
&lt;li&gt;Click Save&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Increase the timeout:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Still in Configuration → General configuration&lt;/li&gt;
&lt;li&gt;Click Edit&lt;/li&gt;
&lt;li&gt;Set Timeout to 15 seconds (to reduce risk if we encounter latency)&lt;/li&gt;
&lt;li&gt;Click Save&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Lambda Code (&lt;code&gt;lambda_function.py&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;Paste this into the Lambda code editor. It verifies that requests are actually coming from Discord, then starts, stops, or checks the status of your EC2 instance based on the command. We're using boto3, the AWS SDK for Python.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nacl.signing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;VerifyKey&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;nacl.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BadSignatureError&lt;/span&gt;

&lt;span class="n"&gt;PUBLIC_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;DISCORD_PUBLIC_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# INSTANCE_ID = os.environ['INSTANCE_ID']  # Uncomment in step 8
&lt;/span&gt;
&lt;span class="n"&gt;ec2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ec2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;verify_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Verify the request is actually from Discord&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-signature-ed25519&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Signature-Ed25519&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x-signature-timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Signature-Timestamp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;verify_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;VerifyKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PUBLIC_KEY&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;verify_key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="si"&gt;}{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromhex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Start, stop, or check the EC2 instance&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢 Starting server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stop&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stop_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🔴 Stopping server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe_instances&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InstanceIds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;INSTANCE_ID&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Reservations&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Instances&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;State&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;emoji&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟢&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;running&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🔴&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stopped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;🟡&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;emoji&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; Server is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown action&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;lambda_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Entry point for Lambda&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;verify_signature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid signature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Discord PING check
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})}&lt;/span&gt;

    &lt;span class="c1"&gt;# Slash command
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;command_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;command_name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;server&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;options&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
            &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
            &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;handle_server&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;}})}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;statusCode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unknown command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}})}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit &lt;strong&gt;Deploy&lt;/strong&gt; to save your changes. That's it for the Lambda function for now. We'll come back to it later once we set up our EC2 instance.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Create API Gateway
&lt;/h3&gt;

&lt;p&gt;Discord needs a public URL to send commands to. API Gateway gives us that. It acts as the front door to our Lambda function and exposes it as an HTTPS endpoint.&lt;/p&gt;

&lt;p&gt;Head to the &lt;a href="https://console.aws.amazon.com/apigateway" rel="noopener noreferrer"&gt;API Gateway console&lt;/a&gt; and create a REST API. Name it something like &lt;code&gt;discord-endpoint&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create resource &lt;code&gt;/event&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create POST method on &lt;code&gt;/event&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Check &lt;strong&gt;Lambda proxy integration&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select your Lambda function&lt;/li&gt;
&lt;li&gt;Deploy API to a stage (e.g., "prod")&lt;/li&gt;
&lt;li&gt;Note the Invoke URL: &lt;code&gt;https://xxx.execute-api.YOUR_REGION.amazonaws.com/prod/event&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  5. Register Discord Endpoint
&lt;/h3&gt;

&lt;p&gt;Now we tell Discord where to send commands when someone uses our bot.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Discord Developer Portal → Your App → General Information&lt;/li&gt;
&lt;li&gt;Set Interactions Endpoint URL to your API Gateway URL&lt;/li&gt;
&lt;li&gt;Discord will send a test PING - if verification passes, it saves&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  6. Register Slash Commands
&lt;/h3&gt;

&lt;p&gt;Discord doesn't know what commands our bot supports yet. We need to register them.&lt;/p&gt;

&lt;p&gt;To find your Guild ID, right-click your server name in Discord and click &lt;strong&gt;Copy Server ID&lt;/strong&gt;. If you don't see that option, enable Developer Mode first: &lt;strong&gt;Settings → Advanced → Developer Mode&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Run this locally in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://discord.com/api/v10/applications/YOUR_APP_ID/guilds/YOUR_GUILD_ID/commands"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bot YOUR_BOT_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "server", "type": 1, "description": "Control game server", "options": [{"name": "action", "description": "What to do", "type": 3, "required": true, "choices": [{"name": "start", "value": "start"}, {"name": "stop", "value": "stop"}, {"name": "status", "value": "status"}]}]}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  7. Create EC2 Instance
&lt;/h3&gt;

&lt;p&gt;EC2 is a virtual machine in the cloud. We'll set up a Minecraft server on it, but you could run whatever you want here.&lt;/p&gt;

&lt;p&gt;Head to the &lt;a href="https://console.aws.amazon.com/ec2" rel="noopener noreferrer"&gt;EC2 console&lt;/a&gt; and launch a new instance.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AMI:&lt;/strong&gt; Amazon Linux 2023&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instance type:&lt;/strong&gt; t3.medium (go larger for more demanding games)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Key pair:&lt;/strong&gt; Create or select one for SSH access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security group:&lt;/strong&gt; Under &lt;strong&gt;Network settings&lt;/strong&gt;, create a new security group. By default, SSH has &lt;strong&gt;Anywhere&lt;/strong&gt; selected — change this to &lt;strong&gt;My IP&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expand &lt;strong&gt;Advanced details&lt;/strong&gt; and paste this into the &lt;strong&gt;User data&lt;/strong&gt; field (this is a script that runs automatically when the instance is first created). First, go to &lt;a href="https://www.minecraft.net/en-us/download/server" rel="noopener noreferrer"&gt;minecraft.net/download/server&lt;/a&gt; and right-click the &lt;code&gt;minecraft_server.jar&lt;/code&gt; link to copy the URL. Replace &lt;code&gt;PASTE_THE_URL_HERE&lt;/code&gt; with it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
dnf &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; java-21-amazon-corretto
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /opt/minecraft
&lt;span class="nb"&gt;cd&lt;/span&gt; /opt/minecraft
curl &lt;span class="nt"&gt;-O&lt;/span&gt; PASTE_THE_URL_HERE
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"eula=true"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; eula.txt
&lt;span class="nb"&gt;chown&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; ec2-user:ec2-user /opt/minecraft

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /etc/systemd/system/minecraft.service &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
[Unit]
Description=Minecraft Server
After=network.target

[Service]
WorkingDirectory=/opt/minecraft
ExecStart=/usr/bin/java -Xmx1G -Xms1G -jar server.jar nogui
Restart=always
User=ec2-user

[Install]
WantedBy=multi-user.target
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;systemctl &lt;span class="nb"&gt;enable &lt;/span&gt;minecraft
systemctl start minecraft
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Click &lt;strong&gt;Launch instance&lt;/strong&gt;, then note your &lt;strong&gt;Instance ID&lt;/strong&gt; (e.g., &lt;code&gt;i-0abc123def456&lt;/code&gt;). You'll need it for the next step.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure security group for Minecraft:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Click on your instance, then click the &lt;strong&gt;Security&lt;/strong&gt; tab and click on the security group link. Go to &lt;strong&gt;Inbound rules → Edit inbound rules&lt;/strong&gt; and add a rule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Custom TCP&lt;/td&gt;
&lt;td&gt;25565 (Minecraft port)&lt;/td&gt;
&lt;td&gt;Anywhere (0.0.0.0/0)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Assign an Elastic IP:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Every time you stop and start your EC2 instance, the public IP changes. That means your friends would need a new IP every time. To fix this, assign an Elastic IP.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In the EC2 console, go to &lt;strong&gt;Network &amp;amp; Security → Elastic IPs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Allocate Elastic IP address&lt;/strong&gt; → &lt;strong&gt;Allocate&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select the new Elastic IP, click &lt;strong&gt;Actions → Associate Elastic IP address&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Choose your instance and click &lt;strong&gt;Associate&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now your server always has the same IP. This is the address you'll share with your friends.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Connect Lambda to Your EC2 Instance
&lt;/h3&gt;

&lt;p&gt;Now that you have your EC2 instance, head back to your Lambda function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add the Instance ID environment variable:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Configuration&lt;/strong&gt; → &lt;strong&gt;Environment variables&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Edit&lt;/strong&gt; → &lt;strong&gt;Add environment variable&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Key: &lt;code&gt;INSTANCE_ID&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Value: your EC2 instance ID&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Save&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Back in the code editor, uncomment the &lt;code&gt;INSTANCE_ID&lt;/code&gt; line and hit &lt;strong&gt;Deploy&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Add IAM permissions&lt;/strong&gt; so Lambda can control your instance. IAM (Identity and Access Management) is how AWS controls what services are allowed to do. Go to &lt;strong&gt;Configuration&lt;/strong&gt; → &lt;strong&gt;Permissions&lt;/strong&gt;, click the role name, hit the &lt;strong&gt;Add permissions&lt;/strong&gt; dropdown, and create an inline policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2012-10-17"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Statement"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:StartInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"ec2:StopInstances"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:YOUR_REGION:YOUR_ACCOUNT_ID:instance/YOUR_INSTANCE_ID"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Effect"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Allow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ec2:DescribeInstances"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"Resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives Lambda permission to start, stop, and check the status of your EC2 instance. Notice we're scoping &lt;code&gt;StartInstances&lt;/code&gt; and &lt;code&gt;StopInstances&lt;/code&gt; to your specific instance ARN instead of using &lt;code&gt;*&lt;/code&gt;, so Lambda can only touch this one instance. &lt;code&gt;DescribeInstances&lt;/code&gt; uses &lt;code&gt;*&lt;/code&gt; because it doesn't support resource-level permissions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Want to restrict who can use the bot commands? Discord lets server admins control that through &lt;strong&gt;Server Settings → Integrations&lt;/strong&gt;. Check out &lt;a href="https://discord.com/blog/slash-commands-permissions-discord-apps-bots" rel="noopener noreferrer"&gt;Discord's command permissions docs&lt;/a&gt; for details.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  9. Try It Out!
&lt;/h3&gt;

&lt;p&gt;Head over to your Discord server and test your bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Type &lt;code&gt;/server start&lt;/code&gt; and wait for the instance to boot up&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;/server status&lt;/code&gt; to confirm it's running&lt;/li&gt;
&lt;li&gt;Open Minecraft, connect to your instance's public IP, and play!&lt;/li&gt;
&lt;li&gt;When you're done, do &lt;code&gt;/server stop&lt;/code&gt; to shut down and save money.&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Lambda and API Gateway fall under the &lt;a href="https://aws.amazon.com/free/" rel="noopener noreferrer"&gt;AWS Free Tier&lt;/a&gt;. The main cost to watch is EC2 if you size up for more demanding games.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe66lc5izjadxznhagtct.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe66lc5izjadxznhagtct.png" alt="Using the Discord bot to control the server" width="546" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We've now got our Minecraft server up and running. Let's hop in! Join the Minecraft server using your EC2 public IP as the server address: &lt;code&gt;&amp;lt;your-ec2-public-ip&amp;gt;:25565&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52uqmycbsoarwox0fp6j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F52uqmycbsoarwox0fp6j.png" alt="Playing Minecraft on the server" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your friends can start or stop the server at any time with the Discord bot. &lt;/p&gt;

&lt;p&gt;Happy crafting!&lt;/p&gt;

&lt;h3&gt;
  
  
  Appendix: Cleanup
&lt;/h3&gt;

&lt;p&gt;If you want to tear everything down, delete these resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://console.aws.amazon.com/ec2/home#Instances" rel="noopener noreferrer"&gt;EC2 instance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.aws.amazon.com/ec2/home#Addresses" rel="noopener noreferrer"&gt;Elastic IP address&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://console.aws.amazon.com/lambda/home#/functions" rel="noopener noreferrer"&gt;Lambda function&lt;/a&gt; and &lt;a href="https://console.aws.amazon.com/lambda/home#/layers" rel="noopener noreferrer"&gt;layer&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://console.aws.amazon.com/apigateway/main/apis" rel="noopener noreferrer"&gt;API Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://console.aws.amazon.com/iam/home#/roles" rel="noopener noreferrer"&gt;IAM inline policy&lt;/a&gt; on the Lambda role&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>beginners</category>
      <category>python</category>
    </item>
  </channel>
</rss>
