Skip to content

Run very cheap game servers (Minecraft, LinuxGSM, etc) in AWS EC2 Spot instances controlled by Discord slash commands. Manage using Terraform

License

Notifications You must be signed in to change notification settings

g-otn/GameServerEC2Discord

Repository files navigation

Game Server EC2 Discord

Terraform files to manage cheap EC2 Spot instances to host game servers controlled by Discord chat.

Made for and tested with personal and small servers with few players.

demo.mp4

Table of Contents

Supported games

Supported

LinuxGSM

LinuxGSM is a deployment and managment tool for Linux dedicated game servers.

Not all games supported by LinuxGSM have been tested with this project. See also usage stats.

Others

  • You can run other game servers' Docker containers by using the custom server module option and specifying more technical configurations. See Custom game and custom game example.

Strategy

The idea is reduce costs by mainly:

  1. Start the server only when players want to play, instead of having it running 24/7
  2. Automatically stop the server when there are no players
  3. Avoid paying for a domain/etc by using a DDNS service
  4. Using spot instances

This is achieved by:

  1. Starting the server via Discord slash commands interactions
    • Slash commands work via webhook which don't require a Discord bot running 24/7, so we can use AWS Lambda + Lambda Function URL
  2. Using the Auto-stop feature from itzg/docker-minecraft-server, or watching for active connections in a specific port, alongside a systemd timer
  3. Setting up Duck DNS inside the instance (No-IP could work too)

Workflow

After setup, the process of starting and automatically stopping a game server works as follows:

  1. The player types /start in a Discord server text channel
  2. Discord calls our Lambda function via its Function URL
  3. The Lambda function sends the interaction token alongside the start command to another Lambda via SNS and then ACKs the interaction to try avoiding Discord's 3s interaction response time limit.
  4. The other Lambda which can take its time, in this case, starts the EC2 instance. Other commands such as stop, restart, ip and status can stop, reboot and describe the instance.
  5. The instance starts
  6. The DDNS systemd service updates the domain with the new IP
  7. The game systemd service runs the Docker Compose file to start the server
  8. The instance shutdown systemd timer starts checking if the container is running
  9. After a minute or so (depending on the game, instance, etc), the server is ready to connect and play
  10. After 10 minutes without a connection or after the last player disconnects, the server is shutdown automatically via the Auto-stop feature (Minecraft) or a systemd service (other games).
  11. After a minute or so, the instance shutdown systemd timer/service notices that the container is stopped and shuts down the whole instance.

Diagram

Minecraft: diagram

Other games

diagram others

Cost breakdown

TL;DR

Assuming one server and AWS free tier/offers:

  • Minecraft: ~0.7 USD for 30h of gameplay using 1x 2.7GHz vCPU and 8GiB DDR5 RAM (estimate)
  • Terraria: ~0.5 USD for 30h of gameplay using 1x 3.7 GHz vCPU and 4GiB of DDR5 RAM (estimate)
  • Factorio: ~0.7 USD for 30h of gameplay using 1x 3.7 GHz vCPU and 4GiB of DDR5 RAM (estimate)
  • Satisfactory: ~0.9 USD for 30h of gameplay using 1x 3.7 GHz vCPU and 8GiB of DDR5 RAM (estimate)
  • Valheim: ~1 USD for 30h of gameplay using 2x 3.2 GHz vCPU and 4GiB of DDR5 RAM (estimate)
  • Palworld: ~2.5 USD for 30h of gameplay using 4x 3.2 GHz vCPU and 8GiB of DD5 RAM (estimate)

AWS Pricing Calculator estimates do not include Public IP cost, see tables below.

Notable expenses

Again, these are just estimates and EC2 pricing varies with time due to a number of factors.

  • ☑️ - Covered by 12-month Free Tier (assuming one server)
  • ✅ - Covered by monthly Always Free offers (assuming one server)
Minecraft
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 r8g.medium spot instance $0.022 $0.66 $0.66
☑️ EBS 4GB root volume + 5GB game data volume - $0.72 - $0.72
EBS (Optional) Snapshots of 5GB game data volume - ~$0.25 ~$0.25
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $2.68 $0.66 $0.97
Terraria
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 m8g.medium spot instance $0.015 $0.45 $0.45
☑️ EBS 4GB root volume + 1GB game data volume - $0.4 - $0.4
☑️ EBS (Optional) Snapshots of 1GB game data volume - ~$0.05 ~$0.05
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $1.95 $0.45 $0.45
Factorio
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 m7a.medium spot instance $0.021 $0.63 $0.63
☑️ EBS 4GB root volume + 2GB game data volume - $0.48 - $0.48
EBS (Optional) Snapshots of 2GB game data volume - ~$0.1 ~$0.1
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $2.13 $0.63 $0.58
Satisfactory
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 r7a.medium spot instance $0.028 $0.84 $0.84
☑️ EBS 4GB root volume + 6GB game data volume - $0.8 - $0.48
EBS (Optional) Snapshots of 6GB game data volume - ~$0.3 ~$0.3
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $2.99 $0.84 $0.78
Valheim
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 c7i.large spot instance $0.031 $0.93 $0.93
☑️ EBS 4GB root volume + 6GB game data volume - $0.8 - $0.48
EBS (Optional) Snapshots of 6GB game data volume - ~$0.3 ~$0.3
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $3.08 $0.93 $0.78
Palworld
12FT AF Service Sub-service / description Price/hour Price 30h/mo Price 30h/mo w/ free tier/offers Price 0h/mo (not in use, no free tier/offers)
EC2 c7i-flex.large spot instance $0.08 $2.4 $2.4
☑️ EBS 4GB root volume + 6GB game data volume - $0.8 - $0.8
EBS (Optional) Snapshots of 6GB game data volume - ~$0.3 ~$0.3
☑️ VPC Public IPv4 address $0.005 $0.15 -
VPC ~10GB of outbound network data (example) $0.003 $0.9 -
Total $0.12 $4.55 $2.4 $1.1

Things to keep in mind

  • Last updated: October 2024 (please check the AWS Pricing Calculator estimates)
  • Region assumed is us-east-2 (Ohio)
  • Prices are in USD
  • Prices do not include Tax
  • Assumes usage of Always Free monthly offers (different from 12 month Free Tier)
  • The most important price factors are play time, instance type and storage size
  • For the EC2 prices, keep in mind about:
    • For each instance type, Spot prices change:
    • You can always change the instance type, but don't forget to change the other related Terraform variables!
    • Surplus vCPU usage credits charges when using burstable instances (t4g, t3a, t3 and t2) in unlimited mode (default). See Burstable instance types.
  • Of course, if you use your AWS account for other things you need to account for them too, specially for Always Free offers.

12-month Free Tier

If you have access to the 12-month Free tier, you should automatically benefit from the following offers, and can temporarly ignore some expenses:

Always Free offers

Outbound data transfer

The most important always free offer is the:

Otherwise due to the current price rates and regular gameplay network usage, it would probably cost more than the instance itself, although it varies per game.

You have to keep this in mind if you're creating more than a couple of servers, or download something big from a server while it's running. (e.g downloading large save files to sync multiplayer session)

Tip

If necessary, you could pay attention to individual player network download metrics (e.g 20kb/s), and then calculate how much will be used per 30h of gameplay.

Misc

Some of the services used are more than covered by the "always free" monthly offers, namely:

Lambda; SNS; KMS; CloudWatch / X-Ray; Data transfer between regions (i.e from Lambda and SNS to game server in another region)

Prerequisites

  • Basic Terraform, Linux and SSH usage knowledge
    • Dedicated game server setup knowledge (updating configurations, uploading saves, etc)
  • An AWS account
    • AWS credentials allowing Terraform to create resources on your account (example)
  • An Discord app on the Developer portal
    • A Discord server in which you have enough permissions to manage applications and channels
  • DDNS service credentials, such as:
  • A SSH keypair for SSH-ing into your instance
    • For example, you can generate one by running: ssh-keygen -t ed25519 -C "GameServerEC2Discord" or using an online tool (example)

Setup

Requirements:

  • Terraform 1.9+
  • Python 3.6+ (due to terraform-aws-lambda)
  • Node.js 18+ (to compile Lambda functions)

(Recommended) Creating an AWS billing alarm

It's recommended to set up a billing alarm on your AWS account to avoid unwanted surprises in case the servers start get way more expensive than estimated, somehow.

In that situation, you can be notified and manually shut down your servers if necessary to troubleshoot what's costing more than it's supposed to and why.

Please check this tutorial to set up alarms. In case you will only use your AWS account for this project, set it to 3 USD if you're running one Minecraft server for example.

Project setup

  1. Clone and navigate to the project:
git clone https://github.com/g-otn/GameServerEC2Discord.git && cd GameServerEC2Discord
  1. Initialize Terraform:
terraform init
  1. Install Node.js dependencies to manually build the lambda functions
npm i
npm run build --workspaces

Terraform variables

  1. Create a file named terraform.tfvars and fill the required variables.
    • Check the table below and variables.tf to see which variables are available and what is their purpose
    • Check example.tfvars for a full example

Required root module variables

Name Description
aws_access_key AWS Access Key you created for Terraform to use
aws_secret_key AWS Secret Key you created for Terraform to use
ssh_public_key Public key data in 'Authorized Keys' format to allow SSH-ing into the instances
discord_app_id Discord App ID for Discord API usage
discord_app_public_key Discord App public key for webhook validation
discord_bot_token Discord App bot token for Discord API auth
duckdns_token Required if you're using Duck DNS. See DDNS

After setting up the required variables, you still need to customize the main.tf file.

Specify AWS regions and servers via Terraform

After setting up the project and filling in the root Terraform variables, you must customize your desired AWS regions and servers by modifying the servers.tf and regions.tf files.

These files are responsible for creating the game servers, and the resources in a specific AWS region required for the server to run.

By default, us-east-2 is already configured in regions.tf. If you don't want to change your region you can leave that file as is. See also Creating server on another region.

But you must modify the servers.tf file to create your server. Go to that file and either modify or comment out the example server. Notice that it references specific values and providers of an AWS region.

Required variables for each server module

You'll need to set these for each server you want to create.

Name Description
id Unique alphanumeric id for the server
game The game this server is going to host. Valid values: linuxgsm, minecraft, terraria, factorio, valheim, palworld, custom. Read LinuxGSM or Custom game if applicable.
az Which availability zone from the chosen region to place the server in. It may be significant price-wise.
hostname Full hostname to be used. (e.g "myserver.duckdns.org"). Required unless DDNS is none

Some other variables may be required depending of the values of specific variables. Please check the server/variables.tf file.

Please check if there is any "Terraform config" Game specific notes for the game you chose, in case there is any extra Terraform variables you should set. (e.g server name and password via docker compose environment)

Other "Common values" are also required but are the same between servers or/and regions. (you can just copy and paste them) See Examples.

Examples

Applying

  1. Run terraform plan and revise the resources to be created

  2. Run terraform apply after a while the instance and the game server should be running and accessible

If the server is not acessible try connecting into the instance, disabling auto shutdown and check logs for errors. See Useful info and commands.

Note

For extra security, SSH-ing and ICMP pinging the instances are only accepted from IPv4 where the Terraform config was applied (e.g your computer). This means once your IPv4 changes, you must run terraform apply again to update the security groups rules, or do it manually. Otherwise you won't be able to ping / SSH.

(Recommended) Discord interactions

Discord interactions

Now that the server is up and running, it should already shut itself down automatically after a while with no players. However you currently still need to start the server via AWS console.

This is techinically optional, but to make starting your server easier, you could set up your Discord server to be able to manage them!

Registering interaction endpoint

  1. Go to the Lambda console on the region you chose, find the interaction-handler Lambda and copy it's Function URL.

  2. Go to your Application on the Discord Developer portal > General Information and paste the URL into Interactions Endpoint URL and click save.

Creating the guild commands

  1. Invite your app to a Discord server (guild) using the OAuth2 link found at Installation. Make sure applications.commands is set in the Default Install Settings.

  2. In the project top folder create a .env file

touch .env
  1. Fill the environment variables required by add-slash-commands.js:
DISCORD_APP_ID=123456789
DISCORD_APP_BOT_TOKEN=MTABCDE

Guild ID is the Discord server ID; App ID and bot token can be found in the Discord Developer Portal.

  1. Create the servers.json file which will be used to create option choices in the Discord slash commands, by running the create-servers-file npm script:
npm run create-servers-file

[!NOTE] If you're using Terraform workspaces, you can fill the TF_STATE_PATH env var in the .env file

This will do a quick and dirty match on your Terraform state file to get the server IDs you created and create a skeleton of the servers.json file for you.

If the create-servers-file script doesn't work for some reason, you can create the file manually.

The servers.json file should contain a list of all the servers you created with Terraform, with each list item containing the following information:

  • gameServerId: The ID of a specific server you created via the main.tf file
  • discordGuildId: In which Discord server (guild) the option to start/stop/etc the server will appear
  • choiceDisplayName: How the server is going to show up in the Discord chat command autocomplete.

For example, the server.example.json file would result in something like this:

server.example.json result

  1. After creating the necessary files, run the setup-discord-app npm script.
npm run setup-discord-app

The script should load the servers.json file, call the Discord API and register the slash command interactions which the Lambda will be ready to handle:

  1. You should now be able to use the /start, /stop, /restart, /ip or /status commands into one of the text channels to manage the instance.
    • You may need do additional permission/role setup depending on your Discord server configuration (i.e if the app can't use the text channel)

Automatic backups

Daily snapshots of the data volume may be taken via Data Lifecycle Manager. This is disabled by default to save costs. (costs varies a lot depending on game, due to required storage space)

However depending on your region, you must enable regional STS endpoint. us-east-2 (Ohio) for example, requires it. Otherwise the DLM policy will error when it tries to create the snapshot.

  1. If you want to enable snapshots:

    • Set the data_volume_snapshots module variable to true.
    • Run terraform apply again, the DLM Lifecycle Policy will be created.
  2. If applicable, enable the STS regional endpoint for the regions you're using via the IAM Console. See Activating and deactivating AWS STS in an AWS Region

See also Restoring a backup

Game specific post-setup

Since you're running a public server, technically anyone on the internet can join your server and do anything (grief, cheat, crash the server, etc). This is most likely not desirable and you might want to do game-specific configuration to limit the server for you and your friends such as setting a password or whitelist.

These are done in most cases by SSH-ing into your instance and then running commands or modifying some game server configuration files. (See also SSH)

You'll also want to do this to load an existing save / world, depending on the game.

  1. Please check the "post-setup" section on each games' Game-specific notes for things you may want to do.

Check also Useful info and commands.

Recommendations and notes

Game-specific notes

Please read if applicable!

Minecraft

Minecraft post-setup

You should at least set up a whitelist so only your friends can join the server.

You can do that by op-ing yourself by creating an whitelist on the Minecraft server console.

  1. Connect to your running server instance using SSH (See SSH)
  2. Attach your terminal to the Minecraft server terminal by running docker attach minecraft
  3. Run whitelist add <player name> to whitelist someone. You could also give yourself admin using op <your player name> to run more commands from within your game chat.

If you're running an offline server, you could also consider setup an auth plugin such as AuthMeReloaded (Spigot).

Recommended RAM

In the variables you can set the JVM Heap size (Xms and Xmx options) via the compose_game_environment variable - INIT/MAX_MEMORY option and the Docker deploy resource memory limit before the OS kills your container via compose_game_limits - memory. See example.tfvars

Firstly, around 200MB is not really available in the instance for usage.

Then I recommended reserving at least 300MB for idle OS, Docker, etc to try prevent the instance from freezing. The remaining will be your Docker memory limit for the container. You could also not set a Docker limit at all.

Finally, save around 600MiB-1GiB for the JVM / Off-heap memory. Examples:

Instance memory Available memory Docker limit (optional) Heap size Recommended players (Vanilla)
2GiB 1.8GiB 1.6GB 1GB 1-2
4GiB 3.8GiB 3.6GB 2.8GB 1-4
8GiB 7.8GiB 7.6GB 6.2GB 2-8

Recommended Minecraft server plugins

  • DiscordSRV - We're already using Discord, so why not? However it seems this plugin overrides the interactions, so you'll have to create another Discord app on the developer portal just for this. See Installation
  • AFK-Kicker - Or any other plugin which can kick afk players, so the server doesn't stays on if nobody is playing
  • TabTPS - Or any other plugin for easy in-game information display of server load, etc

[!TIP] For Minecraft servers, once you run the docker compose once, you can comment the PLUGINS option from the docker compose file inside the instance, to avoid errors if the plugin every fails to download or check for updates. (Until of course, you want to add/remove a plugin)

Terraria

Terraria post-setup

You should at least set up a server password so only you and your friends can join the server.

  1. Connect to your running server instance using SSH (See SSH)
  2. Edit the file at /srv/terraria/data/config.json
    • You may need to use sudo to edit the file. (e.g sudo nano /srv/terraria/data/config.json)
  3. Set a password in the Settings.ServerPassword field
  4. Restart the server by running docker compose restart terraria-terraria-1 or by restarting the whole instance via AWS Console or Discord /restart slash command.

See also TShock Config Settings and Setting Up Your Server.

Factorio

Factorio post-setup

You should set at least set up a server password so only you and your friends can join the server.

  1. Connect to your running server instance using SSH (See SSH)
  2. Edit the file at /srv/factorio/data/config/server-settings.json
    • You may need to use sudo to edit the file. (e.g sudo nano /srv/factorio/data/config/server-settings.json)
  3. Set a password in the game_password field
  4. Restart the server by running docker compose restart terraria-terraria-1 or by restarting the whole instance.

Consider also setting a name and description by editing the same file.

See also TShock Config Settings and Setting Up Your Server.

Satisfactory

Satisfactory post-setup

The server can be set up in-game via "Server Manager" in the main menu.

In there you should at least set up a server password (different from admin password) so only you and your friends can join the server.

Valheim

Valheim Terraform config

By default, the server is created with a password of valheim and should be visible in the server list depending on your region. (default server name is the server Terraform module id)

You may change the server password, among other things such as server name via the compose_game_environment server module Terraform variable.

  compose_game_environment = {
    "NAME" : "My GSED Valheim Server",
    "PASSWORD" : "friendsonly"
  }

See "Valheim server" in Examples.

Palworld

Palworld Terraform config

By default, the server is created with a password of palworld, admin password worldofpalsadmin and visible in the server list. (default server name is the server Terraform module id)

You may change the server and admin password, among other things such as server name via the compose_game_environment server module Terraform variable:

  compose_game_environment = {
    "SERVER_NAME" : "My GSED Palworld server"
    "SERVER_DESCRIPTION" : "Welcome to my server"
    "SERVER_PASSWORD" : "palpalpal"
    "ADMIN_PASSWORD" : "worldworld",
    "COMMUNITY" : false # hide server from community list
  }

See "Palworld server" in Examples.

[!IMPORTANT] If you choose an arm64 instance architecture, you'll probably have to set USE_DEPOT_DOWNLOADER compose_game_environment variable to true, to avoid this error.

However, you then may still encounter another error related to Linux file permissions: ./PalServer.sh is not executable. and the server won't start. This seems like a problem with DepotDownloader. To work around this, SSH into your instance and manually make the file executable. (i.e chmod +x /srv/palworld/data/PalServer.sh)

LinuxGSM

LinuxGSM post-setup

Please check your game specific post-setup documentation if available, also consider checking out LinuxGSM own docs (left menu) which contains very useful information for some games.

As a general tip, your game data, saves and configurations files might be structured using different folder structure and file names than the ones used in game-specific docker images.

You can change LinuxGSM settings at /srv/linuxgsm/data/config-lgsm/<short name>server/<short name>server.cfg such as servername and serverpassword.

Regions

Sometimes you want to change the AWS region you server is located at, due to latency and/or price. By default this project configures us-east-2 (Ohio) which is generally cheap.

This project supports multiple AWS regions, so you can have a server in us-east-2 (Ohio) and another two in eu-north-1 (Frankfurt), for example.

Before creating servers in another region

  • Check if the instance type for your server supported
    • Some instance types are not available in every region. (e.g r8g family)
    • You should check the default instance type by viewing the local.game_defaults in server/main.tf
    • If needed, override instance_type server module variable to override the default values.

Creating server on another region

  1. In the regions.tf file, copy and paste the example provider + module blocks below them.

    • If you haven't run terraform apply yet, you can also just change the values from the existing one
  2. Replace us-east-2 with the desired region in every occurance in the pasted code. This includes alias and region(s) attributes, provider.aws reference and base_region module name.

  3. Also change the az attribute to a valid AZ in the chosen region.

Availability zones

A server is constrained to one availability zone (AZ) due to EBS (storage). You cannot change it after creation without recreating it and losing data.

The reason you may want to choose a specific AZ is due to price difference between AZs in the region. It's worth checking because the price diference can be significant! Keep in mind EC2 pricing still varies with time due to a number of other factors.

See Spot Instance pricing history in "AWS Console > EC2 > Instances > Spot Requests > Pricing History", then choose the desired instance type or the default instance type in this project for the game you want.

Check if this variation is significant and choose the current cheapest AZ for you.

When choosing an specific AZ, don't forget to update the azs variable in the base_region module in servers.tf if necessary, and then properly update all server module az variables in servers.tf.

Server ports

Each game requires different ports to be open.

Any extra port besides ICMP, SSH and main_port you want to open needs to be set both in sg_ingress_rules (VPC Security group rules) and compose_game_ports (Docker compose service ports) variables in a way in which they match.

For example, here's a configuration that opens the port 22222 and 33333:

module "myserver" {
  // ...
  main_port = 11111

  // it's not necessary to include main_port configuration in these variables
  compose_game_ports = ["22222:22222", "33333:33333/udp"]
  sg_ingress_rules = {
    "admin panel" : {
      description = "Admin panel using TCP"
      from_port   = 22222
      to_port     = 22222
      ip_protocol = "tcp"
      cidr_ipv4   = "0.0.0.0/0"
    }
    "video feed" : {
      description = "Some video data using UDP"
      from_port   = 33333
      to_port     = 33333
      ip_protocol = "udp"
      cidr_ipv4   = "0.0.0.0/0"
    }
  }
}
Note about Minecraft ports

By default the Minecraft container exposes port 25565, so if you want to run the server in another port you should either change only the host port (like 12345:25565 where 12345 is the custom port) or change the SERVER_PORT variable.

DDNS

The ddns_service server module variable can be used to set which DDNS service to use for that server. Currently only Duck DNS is supported.

You may also disable it, which will make the server only accessible via its public IPv4 address, which changes each time the instance starts.

You also have to set the hostname variable with the hostname which will be used.

Duck DNS variables
Name Required Description
duckdns_token Yes Duck DNS account token

Server instance type

Choosing the instance type is has significant impact on the performance of the game server and its cost.

Each supported game comes with a default instance type, but it can be changed. Don't forget to update the relevant Docker Compose variables (environment in case of Minecraft and deploy limits) to match the chosen instance type.

When choosing an EC2 instance type, consider:

  • CPU architecture (e.g arm64 are generally cheaper but some games don't support it)
  • Available vCPU and RAM (based on game server requirements)
    • Keep in mind some of the server resources are used for the OS, Docker, etc and are not available to the game server itself. For example a 8GB instance might have ~6.5GB available
  • CPU frequency (GHz) / System single-core and multi-core performance scores
  • Instance generation (newer generations are more performant)
  • Spot price and price history
    • Sometimes, for some reason, the instance type is significally cheaper or more expensive in a specific availability zone than the others. See Availability Zones.
  • Spot interruption frequency (if it's too high there's more chance per month of the server going down while you're playing)
  • Some instance families are not available in most regions

To help choose a instance type different from the defaults, check out:

  • The Vantage website
    • Tip: Hide Name, Windows-related, Network Performance, On-demand and Reserved columns; Show all Linux Spot-related, Clock Speed and Physical Processor columns; Sort by Linux Spot Average cost
    • Grouping "Cost" by "Daily" can facilitate visualize how much (the instance alone) would cost for 24h of gameplay.
  • Geekbench Browser - Easier to check CPU performance by looking at benchmark scores
  • Spot Instance advisor - Official way to check spot interruption frequency
  • aws-pricing.com Instance Picker - Similar to Vantage

Some examples of families you could choose:

  • x86_64: r7a, r7i, c7a, c7i-flex, c7i, m7a, r7iz
  • arm64: r8g, c8g, m8g, r7g, c7g, m7g

Burstable instance types

If you choose a burstable instance types (t4g, t3a, t3 and t2), be aware of surplus vCPU usage credits charges when using burstable instances (t4g, t3a, t3 and t2) in unlimited mode (default). See Earn CPU credits and When to use unlimited mode versus fixed CPU.

Make sure you have a good grasp of concepts such as unlimited vs fixed mode, vCPU credits, baseline performance, breakeven CPU usage and launch credits before choosing an instance of these families. They can be cheaper but if used wrong can incur an extra charges and end of up more expensive than fixed performance instance types.

As a general tip, avoid micro, small and medium instances of these families because the baseline is too low for most games.

Renaming and deleting

When you want to delete a server, you can't just comment out the server module usage due to how Terraform providers works.

You can delete by using terraform destroy --target=module.<server module name>.

You can also do the same to delete base_region module resources. (e.g terraform destroy --target=module.us-east-2)

After deleting a server, Terraform will create a "final snapshot" of the server data volume, which you can use to restore the server.

Restoring a backup

Backups is done via EBS volume snapshots. You can restore the game server data by specifying the snapshot_id of the desired snapshot in the server module, which will force a replacement of the EBS "data" volume, where the new one will use that snapshot as a base.

LinuxGSM

You may want to run a game server via LinuxGSM, since it supports a large amount of game servers.

The setup is similar but simpler than a custom game server. Most of the values you'll find by searching "(game name) dedicated server requirements".

Here's the additional variables you must to specify in the servers.tf module declaration in order to create a linuxgsm server:

Name Description
game Must be linuxgsm
linuxgsm_game_shortname The "shortname" of the desired game, found in LinuxGSM's server list
instance_type The EC2 instance type. The instance must be powerful enough to run your chosen game server. See Server instance type
arch The architecture of the chosen EC2 instance type (arm64 or x86_64). Some games do not support arm64. See also server/main.tf
main_port Main port for server connections. If the game requires more than one port, see Server ports
data_volume_size The required storage in GB by the game data + save files, it varies greatly from game to game. See server/main.tf for some examples.

See also Examples

Custom game

You can manually configure the Compose file, ports, storage and other resources for a desired game server, by using the custom value in the game variable.

The game server must meet the following criteria:

  • It can run on Linux 64-bits
  • It is containerized using Docker (for example, based on CM2.Network steamcmd or steamcmd/steamcmd)
    • The container can be run on x86_64 or arm64 architectures
  • It can handle rare sudden shutdowns
    • This is due to the nature of Spot instances or if someone requests it via Discord slash commands
    • This means it should be able to shut down gracefully and preferrably auto-save periodically
  • It makes sense for this project:
    • This project allows part of the friend group to play on the server, without requiring the host player who has the save file to be online at all play sessions. Examples are Multiplayer "Open World Survival Craft" / Sandbox games.
    • Some games though, can only be played or only make sense to play when the all players are together.

To define a custom game server (see also custom game example):

  1. Copy and paste a new server module usage in the servers.tf file.
  2. Set common server-specific values such as id, az, hostname and other DDNS config.
  3. Set game to custom and define an alphanumeric custom_game_name (e.g CustomGame).
  4. Set the game's networking using main_port and add sg_ingress_rules as needed.
  5. Set the game's available storage using data_volume_size and backup frequency and retetion using the data_volume_snapshot_* variables.
    • If your volume is too big and/or the game data changes too much between snapshots (e.g big save files are compressed each time), consider lowering snapshot retention. Check AWS pricing calculator.
    • If the Docker image you're going to use is too big (more than a couple of GBs), you may need to increase the root volume size.
  6. Configure the Docker Compose file which will be used within the instance by setting up at least the compose_game_elements variable:
    • Image: Set the game's docker image/tag
    • Networking: Define the container ports to match main_port and sg_ingress_rules
    • Storage: Define a volume matching data_mount_path, which is based from lowercase value of custom_game_name (e.g /srv/customgame, See server/main.tf).
    • Environment: Fill in environment variables required by your image.
  7. Set the instance_type and matching arch. See Server instance type.
  8. To test the instance for the first time, disable auto_shutdown. Don't forget to re-enable it!
  9. Run terraform init since this is a new module
  10. Follow Applying and Discord interactions again to create the resources and update your Discord app slash commands.

Troubleshooting

SSH

You most likely will want to SSH into your instance at least once to maybe upload an existing save / world or to modify server configuration.

I recommend using some UI application to help navigate and manage files via SSH like VS Code Remote Explorer.

SSH into your instance using the ec2-user user. Example:

ssh -i "~/.ssh/<my private key>.pem" "ec2-user@<myserver>.duckdns.org"
  • Replace <my private key> with the name of your private key, assuming it's saved at ~/.ssh
  • replace <myserver>.duckdns.org with the hostname you put in the Terraform servers.tf config for that server

Note

Your SSH client may fail when connecting due to the IP changing between server restarts. You can delete the ~/.ssh/known_hosts file as a quick workaround. You may also need to clear your DNS cache.

Useful info and commands

These notes and commands are for when you are connected to the instance via SSH.

Game data EBS volume is mounted at /srv/<game id> (e.g /srv/minecraft);

Docker compose container name is <game id>-<game main service name>-1 (e.g minecraft)

Commands (using Minecraft server as an example):

  • htop: Task manager / resource usage viewer
  • docker stats: Shows current RAM usage vs deploy limit
  • `docker attach minecraft: (Minecraft only) Attach terminal to Minecraft server console
  • docker logs minecraft -f: Latest logs from the container
  • sudo systemctl stop auto_shutdown.timer: Stops the systemd timer which prevents the instance from being shut down automatically until next reboot. Don't forget to shutdown/reboot manually or start the timer again!
  • sudo conntrack -L --dst-nat | grep -w <game main port> | grep -w ESTABLISHED: Lists currently estabilished network connections with the container

CloudWatch

CloudWatch log groups are created for the Lambda and VPC flow logs. They can help you troubleshoot problems with connectivity and Discord interactions.

X-Ray tracing is also enabled (mainly for debugging the project), however you need to manually set up SNS permissions so the traces show up correctly in the Trace Map / etc. Or else the trace will stop at SNS and a new trace will begin in the Lamba context

Notes and acknowledgements

This project was made for studying purposes mainly. The following repos and articles were very helpful in the learning and development process: