<?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.us-east-2.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>Build without stopping to set up IAM roles in the new AWS experience</title>
      <dc:creator>Fabricio Flores</dc:creator>
      <pubDate>Tue, 13 Oct 2026 14:54:34 +0000</pubDate>
      <link>https://dev.to/aws/build-without-stopping-to-set-up-iam-roles-in-the-new-aws-experience-1mj2</link>
      <guid>https://dev.to/aws/build-without-stopping-to-set-up-iam-roles-in-the-new-aws-experience-1mj2</guid>
      <description>&lt;p&gt;The new AWS experience removes two pieces of permission setup that often interrupt an early build. First, a project owner can now invite another person with an emailed invite, instead of having to set up an IAM user or IAM Identity Center. Second, when you create a resource using a supported AWS service, &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html" rel="noopener noreferrer"&gt;IAM role manager&lt;/a&gt; can automatically set up the IAM role for you so that your project resources can talk to each other.&lt;/p&gt;

&lt;p&gt;Those actions involve different identities. Project access authorizes the person doing the work. An IAM role authorizes the service or application that runs afterward.&lt;/p&gt;

&lt;p&gt;If you created your account through the new AWS experience, role manager is already turned on. When you create a supported resource, such as a Lambda function, AWS creates or reuses the role it needs and attaches it automatically.&lt;/p&gt;

&lt;h2&gt;
  
  
  Share a project with collaborators
&lt;/h2&gt;

&lt;p&gt;A project owner can share access in two ways: invite someone from the&lt;a href="https://settings.aws.com/team" rel="noopener noreferrer"&gt;&lt;strong&gt;Team&lt;/strong&gt; page&lt;/a&gt; and choose one or more projects, or open a specific project and invite them from its &lt;strong&gt;Members&lt;/strong&gt; area. An invitation from the Team page does not automatically share every project the owner has; access applies only to the selected projects. After accepting, the team member has admin access to the resources and services available in those projects, including the ability to create access controls for services and applications. They cannot perform owner-only team-management, project, root, or financial actions. If you need to customize a team member’s access instead of granting project admin access, you must first &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/activate-advanced-features.html" rel="noopener noreferrer"&gt;activate advanced AWS features&lt;/a&gt;. For more details about sharing projects and team member access, see &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/invite-team-members.html" rel="noopener noreferrer"&gt;Invite team members to collaborate in AWS Settings&lt;/a&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxo3ll40hdr55nybyzhp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fmxo3ll40hdr55nybyzhp.png" alt="Invite team members dialog showing an email field and two selected projects" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To see the invitation process in action, watch &lt;a href="https://youtu.be/xk_TKy5Epzg" rel="noopener noreferrer"&gt;Invite Your Teammate to Your AWS Project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How role manager works
&lt;/h2&gt;

&lt;p&gt;An IAM role defines what an AWS service is allowed to access and do. Setting one up usually means defining which service can assume the role, choosing what that service is allowed to do, and attaching the role to the resource.&lt;/p&gt;

&lt;p&gt;IAM role manager automates that setup. When you create a resource in one of the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html#id_roles_create_role-manager_supported-services" rel="noopener noreferrer"&gt;supported services&lt;/a&gt;, role manager creates a role for it or reuses one that already fits. It then attaches the role as part of the same workflow, so you do not have to stop and set it up separately in IAM.&lt;/p&gt;

&lt;p&gt;For accounts created through the new AWS experience, role manager is enabled by default. You cannot disable it until the account owner &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/activate-advanced-features.html" rel="noopener noreferrer"&gt;activates advanced AWS features&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This restriction is enforced by an AWS-managed service control policy (SCP). In the new experience, AWS manages the SCPs and resource control policies (RCPs) that protect your projects' preconfigured defaults, so you do not set up or edit these guardrails. For details, see &lt;a href="https://docs.aws.amazon.com/accounts/latest/reference/scps-and-rcps-for-projects.html" rel="noopener noreferrer"&gt;Managed policies for your organization&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After advanced features are activated, you can manage role manager from &lt;strong&gt;Account settings&lt;/strong&gt; in the IAM console.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepl6ipvdiy4wacxhqzy4.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fepl6ipvdiy4wacxhqzy4.png" alt="IAM Account settings showing role manager enabled" width="799" height="454"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How role manager sets up a Lambda execution role
&lt;/h2&gt;

&lt;p&gt;Lambda is a useful example because AWS cannot know ahead of time which services your code will call. With role manager enabled, creating a function automatically attaches an execution role that the code can use immediately. You can narrow that role later, once you know what the function actually needs.&lt;/p&gt;

&lt;p&gt;Because those permissions are unknown at creation time, role manager starts the role with the AWS managed &lt;code&gt;PowerUserAccess&lt;/code&gt; policy. This gives the function broad access to AWS services, but it does not allow the function to manage IAM, AWS Organizations, or account settings. The role's trust policy allows only Lambda to assume it.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fixw97u5mpig8ity2siq6.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fixw97u5mpig8ity2siq6.png" alt="Lambda Create function page with helloWorld as the function name and Python 3.14 selected" width="800" height="371"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After the function is created, open its &lt;strong&gt;Configuration&lt;/strong&gt; tab and choose &lt;strong&gt;Permissions&lt;/strong&gt;. The &lt;strong&gt;Execution role&lt;/strong&gt; panel shows the &lt;code&gt;PowerUserRole&lt;/code&gt; that role manager attached.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2wg364drfic4mncjfzn.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg2wg364drfic4mncjfzn.png" alt="Lambda Configuration permissions showing PowerUserRole as the execution role" width="800" height="725"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the role link to IAM. On the role's &lt;strong&gt;Permissions&lt;/strong&gt; tab, choose &lt;code&gt;PowerUserAccess&lt;/code&gt; to inspect the AWS managed policy and the access it grants.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjfe7axy9tkh1pfgoh2b.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjfe7axy9tkh1pfgoh2b.png" alt="IAM policy details showing the PowerUserAccess managed policy and its policy document" width="800" height="577"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Roles created by role manager are standard IAM roles, so you can inspect their trust policies and permissions, edit them, or delete them. Each role records the AWS-managed template it came from, which lets you identify roles created by role manager. AWS CloudTrail records each role's creation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Refine permissions before production
&lt;/h2&gt;

&lt;p&gt;Least privilege means giving a workload only the actions and resource access it needs. Role manager's &lt;code&gt;PowerUserAccess&lt;/code&gt; policy is useful while you are still discovering those needs, but it is intentionally broader than most production functions require.&lt;/p&gt;

&lt;p&gt;Use &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-generation.html" rel="noopener noreferrer"&gt;IAM Access Analyzer policy generation&lt;/a&gt; to help refine the role's permissions toward least privilege. Editing the role removes it from role manager's control and makes it a standard role that you manage.&lt;/p&gt;

&lt;p&gt;After activating advanced AWS features, you can disable role manager without affecting existing applications or removing their roles. Existing roles stay in place, but new resources use the standard service or IAM role-creation workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Role manager automates the initial IAM role setup for supported AWS services. It creates or reuses a role and attaches it while you create the resource, but the result remains a standard IAM role that you can inspect and control.&lt;/p&gt;

&lt;p&gt;Together with AWS-managed project sharing, this reduces the access setup required for both collaborators and the services they build. Before production, review how the workload actually behaves and use IAM Access Analyzer policy generation to replace broad permissions with a more focused policy.&lt;/p&gt;

&lt;p&gt;To get started, create a resource in a &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create_role-manager.html#id_roles_create_role-manager_supported-services" rel="noopener noreferrer"&gt;supported service&lt;/a&gt;. In accounts created through the new AWS experience, role manager is already enabled. For more information about managing roles yourself, see &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_create.html" rel="noopener noreferrer"&gt;IAM role creation&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>infrastructure</category>
      <category>security</category>
    </item>
    <item>
      <title>How I built this serverless API with the new AWS</title>
      <dc:creator>Fabricio Flores</dc:creator>
      <pubDate>Thu, 08 Oct 2026 21:56:17 +0000</pubDate>
      <link>https://dev.to/aws/how-i-built-this-serverless-api-with-the-new-aws-56jc</link>
      <guid>https://dev.to/aws/how-i-built-this-serverless-api-with-the-new-aws-56jc</guid>
      <description>&lt;p&gt;AI coding agents have shortened the path from an idea to working code. The &lt;a href="https://signin.aws.amazon.com/signup?request_type=builderId&amp;amp;trk=0a3e63d3-e29a-4c69-b70d-caa08a783f72&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;updated AWS experience&lt;/a&gt; is designed to make deployment keep pace. It connects your agent to an AWS project, configures the AWS tools it needs, and handles permissions between the resources it creates.&lt;/p&gt;

&lt;p&gt;I used that workflow to turn a short prompt into a Python API using Amazon API Gateway, AWS Lambda, and Amazon DynamoDB. The build starts by connecting the agent to AWS and ends with a working endpoint that I call from my terminal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connect a coding agent to the AWS project
&lt;/h2&gt;

&lt;p&gt;After signing in, I followed the instructions in the &lt;strong&gt;Setup Agent Toolkit for AWS&lt;/strong&gt; dialog and pasted its setup prompt into my coding agent.&lt;/p&gt;

&lt;p&gt;The agent then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;installed the AWS Command Line Interface (AWS CLI),&lt;/li&gt;
&lt;li&gt;used &lt;code&gt;aws login&lt;/code&gt; to sign me in,&lt;/li&gt;
&lt;li&gt;configured the AWS MCP server and core AWS skills, and&lt;/li&gt;
&lt;li&gt;added customizable rules for building on AWS.&lt;/li&gt;
&lt;/ul&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zqtaje453zxh3lyr6zp.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F2zqtaje453zxh3lyr6zp.png" alt="Setup Agent Toolkit for AWS dialog containing instructions to paste into a coding agent" width="800" height="647"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Describe the API in one prompt
&lt;/h2&gt;

&lt;p&gt;With the agent connected, I described the service I wanted:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjyekiwjqdtxnkgydlfih.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjyekiwjqdtxnkgydlfih.png" alt="Prompt asking a coding agent to create and deploy a serverless API on AWS" width="796" height="57"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I specified Python, asked for a stateful response I could test, and told the agent to show me the architecture before deploying. I left the AWS service choices open.&lt;/p&gt;

&lt;h2&gt;
  
  
  Review the proposed architecture
&lt;/h2&gt;

&lt;p&gt;Before creating resources, the agent proposed this architecture:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbei5vecgnfc36hyl4iw.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbei5vecgnfc36hyl4iw.png" alt="Serverless API architecture showing a client calling Amazon API Gateway, AWS Lambda, and Amazon DynamoDB" width="800" height="140"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The detailed proposal defined &lt;code&gt;GET /counter&lt;/code&gt; as an atomic increment of a value stored in DynamoDB. API Gateway would expose the public endpoint, a Python Lambda function would handle each request, and an on-demand DynamoDB table would maintain state across calls. The atomic update would prevent duplicate values during concurrent requests.&lt;/p&gt;

&lt;p&gt;The plan also included conservative throttling, a least-privilege IAM role for the table update and function logging, CloudWatch log retention, local tests, and deployment through an AWS CloudFormation stack.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9tbydh5dwx17f973pcf.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9tbydh5dwx17f973pcf.png" alt="Coding agent presenting the detailed serverless architecture and asking for approval before deployment" width="800" height="266"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The agent explicitly said that no AWS resources had been changed and asked me to approve the architecture and public endpoint before continuing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Let the agent build and deploy
&lt;/h2&gt;

&lt;p&gt;After I approved the plan, the agent deployed the &lt;code&gt;serverless-counter-api&lt;/code&gt; CloudFormation stack in &lt;code&gt;us-east-2&lt;/code&gt;. The deployment included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a public API Gateway endpoint,&lt;/li&gt;
&lt;li&gt;a Python 3.12 Lambda function,&lt;/li&gt;
&lt;li&gt;a DynamoDB table to store the counter, and&lt;/li&gt;
&lt;li&gt;an IAM role giving the function the access it needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API was intentionally public for this demo. Anyone with the URL could invoke it and generate usage, so a production API should use authorization appropriate to its callers and data.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqy37tr0pevqeai4r6afe.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqy37tr0pevqeai4r6afe.png" alt="Coding agent listing the AWS resources deployed for the serverless service" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Call the live endpoint
&lt;/h2&gt;

&lt;p&gt;The agent verified the deployment by calling the endpoint three times. Each call returned HTTP 200 with &lt;code&gt;{"value": 1}&lt;/code&gt;, &lt;code&gt;{"value": 2}&lt;/code&gt;, and &lt;code&gt;{"value": 3}&lt;/code&gt;. A consistent DynamoDB read confirmed that the stored value was &lt;code&gt;3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Later, I set &lt;code&gt;API_URL&lt;/code&gt; to the &lt;code&gt;/prod/counter&lt;/code&gt; invoke URL and called it three times in a loop:&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0k4d2br1ak8mfe95ljr.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg0k4d2br1ak8mfe95ljr.png" alt="Terminal showing three calls to the deployed API returning values 7, 8, and 9" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Those calls returned &lt;code&gt;{"value": 7}&lt;/code&gt;, &lt;code&gt;{"value": 8}&lt;/code&gt;, and &lt;code&gt;{"value": 9}&lt;/code&gt;. The increasing values confirmed that the service was live and maintaining state across requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I kept control of
&lt;/h2&gt;

&lt;p&gt;I chose what the API should do, reviewed the proposed architecture, and approved the public deployment. The agent handled the tooling setup, implementation, deployment, and first tests.&lt;/p&gt;

&lt;p&gt;Before wrapping up, I verified the result myself: Lambda updated DynamoDB atomically and returned the new value, the permissions were limited to what the function needed, and &lt;code&gt;GET /counter&lt;/code&gt; worked. The agent reported success, but the final call was mine.&lt;/p&gt;

&lt;p&gt;Try this with a coding agent you already use. &lt;a href="https://signin.aws.amazon.com/signup?request_type=builderId&amp;amp;trk=0a3e63d3-e29a-4c69-b70d-caa08a783f72&amp;amp;sc_channel=el" rel="noopener noreferrer"&gt;Get started here&lt;/a&gt;, then give your agent a small AWS project and ask to see the plan before it deploys anything. You make the calls and check the result. Let the agent handle the setup and get the first version running.&lt;/p&gt;

&lt;p&gt;Now go build something 😄.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <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>
