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
- Supported games
- Strategy
- Cost breakdown
- Prerequisites
- Setup
- Recommendations and notes
- Troubleshooting
- Notes and acknowledgements
Supported
- Minecraft (via itzg/docker-minecraft-server)
- Terraria (via ryshe/terraria)
- Factorio (via factoriotools/factorio)
- Satisfactory (via wolveix/satisfactory-server)
- Valheim (via mbround18/valheim-docker)
- Palworld (via thijsvanloef/palworld-server-docker)
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.
The idea is reduce costs by mainly:
- Start the server only when players want to play, instead of having it running 24/7
- Automatically stop the server when there are no players
- Avoid paying for a domain/etc by using a DDNS service
- Using spot instances
This is achieved by:
- 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
- Using the Auto-stop feature from itzg/docker-minecraft-server, or watching for active connections in a specific port, alongside a systemd timer
- Setting up Duck DNS inside the instance (No-IP could work too)
After setup, the process of starting and automatically stopping a game server works as follows:
- The player types
/start
in a Discord server text channel - Discord calls our Lambda function via its Function URL
- 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. - The other Lambda which can take its time, in this case, starts the EC2 instance. Other commands such as
stop
,restart
,ip
andstatus
can stop, reboot and describe the instance. - The instance starts
- The DDNS systemd service updates the domain with the new IP
- The game systemd service runs the Docker Compose file to start the server
- The instance shutdown systemd timer starts checking if the container is running
- After a minute or so (depending on the game, instance, etc), the server is ready to connect and play
- 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).
- After a minute or so, the instance shutdown systemd timer/service notices that the container is stopped and shuts down the whole instance.
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.
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 |
- 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)
- This is important mostly due to the monthly free 100GB outbound data transfer from EC2 to the internet. See Always Free offers
- 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:
- With availability
- With time
- Per region
- Per availability zone. See Availability Zones.
- 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
andt2
) in unlimited mode (default). See Burstable instance types.
- For each instance type, Spot prices change:
- Of course, if you use your AWS account for other things you need to account for them too, specially for Always Free offers.
If you have access to the 12-month Free tier, you should automatically benefit from the following offers, and can temporarly ignore some expenses:
-
VPC:
- 750.0 Hrs (of Public IPv4 addresses) for free for 12 months (Global-PublicIPv4:InUseAddress)
-
EBS
- "1.0 GB-mo for free for 12 months (Global-EBS:SnapshotUsage)
- "30.0 GB-Mo for free for 12 months (Global-EBS:VolumeUsage)"
Outbound data transfer
The most important always free offer is the:
- AWS Data Transfer: "100.0 GB are always free per month as part of AWS Free Usage Tier (Global-DataTransfer-Out-Bytes)"
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)
- 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:
- Duck DNS account and domain
- 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)
- For example, you can generate one by running:
Requirements:
- Terraform 1.9+
- Python 3.6+ (due to terraform-aws-lambda)
- Node.js 18+ (to compile Lambda functions)
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.
- Clone and navigate to the project:
git clone https://github.com/g-otn/GameServerEC2Discord.git && cd GameServerEC2Discord
- Initialize Terraform:
terraform init
- Install Node.js dependencies to manually build the lambda functions
npm i
npm run build --workspaces
- 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
- Check the table below and
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.
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.
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.
- Custom server
- Factorio server via LinuxGSM
- Minecraft server
- Minecraft server with plugins, etc
- Palworld
- Valheim
-
Run
terraform plan
and revise the resources to be created -
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.
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!
-
Go to the Lambda console on the region you chose, find the
interaction-handler
Lambda and copy it's Function URL. -
Go to your Application on the Discord Developer portal > General Information and paste the URL into
Interactions Endpoint URL
and click save.
-
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. -
In the project top folder create a
.env
file
touch .env
- 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.
- Create the
servers.json
file which will be used to create option choices in the Discord slash commands, by running thecreate-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 themain.tf
filediscordGuildId
: In which Discord server (guild) the option to start/stop/etc the server will appearchoiceDisplayName
: 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:
- 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:
- 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)
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.
-
If you want to enable snapshots:
- Set the
data_volume_snapshots
module variable totrue
. - Run
terraform apply
again, the DLM Lifecycle Policy will be created.
- Set the
-
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
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.
- 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.
Please read if applicable!
Minecraft
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.
- Connect to your running server instance using SSH (See SSH)
- Attach your terminal to the Minecraft server terminal by running
docker attach minecraft
- Run
whitelist add <player name>
to whitelist someone. You could also give yourself admin usingop <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).
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 |
- 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
You should at least set up a server password so only you and your friends can join the server.
- Connect to your running server instance using SSH (See SSH)
- Edit the file at
/srv/terraria/data/config.json
- You may need to use
sudo
to edit the file. (e.gsudo nano /srv/terraria/data/config.json
)
- You may need to use
- Set a password in the
Settings.ServerPassword
field - 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
You should set at least set up a server password so only you and your friends can join the server.
- Connect to your running server instance using SSH (See SSH)
- Edit the file at
/srv/factorio/data/config/server-settings.json
- You may need to use
sudo
to edit the file. (e.gsudo nano /srv/factorio/data/config/server-settings.json
)
- You may need to use
- Set a password in the
game_password
field - 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
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
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
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 setUSE_DEPOT_DOWNLOADER
compose_game_environment
variable totrue
, 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.echmod +x /srv/palworld/data/PalServer.sh
)
LinuxGSM
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
.
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.
- 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.
- Some instance types are not available in every region. (e.g
-
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
- If you haven't run
-
Replace
us-east-2
with the desired region in every occurance in the pasted code. This includesalias
andregion
(s) attributes,provider.aws
reference andbase_region
module name. -
Also change the
az
attribute to a valid AZ in the chosen region.
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
.
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.
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 |
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 allLinux Spot
-related,Clock Speed
andPhysical Processor
columns; Sort byLinux Spot Average cost
- Grouping "Cost" by "Daily" can facilitate visualize how much (the instance alone) would cost for 24h of gameplay.
- Tip: Hide Name,
- 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
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.
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.
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.
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
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
orarm64
architectures
- The container can be run on
- 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):
- Copy and paste a new server module usage in the
servers.tf
file. - Set common server-specific values such as
id
,az
,hostname
and other DDNS config. - Set
game
tocustom
and define an alphanumericcustom_game_name
(e.gCustomGame
). - Set the game's networking using
main_port
and addsg_ingress_rules
as needed. - Set the game's available storage using
data_volume_size
and backup frequency and retetion using thedata_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.
- 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 matchmain_port
andsg_ingress_rules
- Storage: Define a volume matching
data_mount_path
, which is based from lowercase value ofcustom_game_name
(e.g/srv/customgame
, See server/main.tf). - Environment: Fill in environment variables required by your image.
- Set the
instance_type
and matchingarch
. See Server instance type. - To test the instance for the first time, disable
auto_shutdown
. Don't forget to re-enable it! - Run
terraform init
since this is a new module - Follow Applying and Discord interactions again to create the resources and update your Discord app slash commands.
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 thehostname
you put in the Terraformservers.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.
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 viewerdocker 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 containersudo 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 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
This project was made for studying purposes mainly. The following repos and articles were very helpful in the learning and development process:
- doctorray117/minecraft-ondemand - The main motivation for this project, I wanted to do something similar but less complex and even cheaper (without Route 53, EFS, DataSync, Twilio and Minecraft watchdog)
- JKolios/minecraft-ondemand-terraform - Gave me an general idea of what I had to do
- mamoit/minecraft-ondemand-terraform - I almost went with this solution instead of creating my own, but I wanted to use EC2 directly instead of ECS + Fargate for slightly cheaper costs
- Lemmons/minecraft-spot - Some Cloud-init and Terraform reference
- vincss/mcEmptyServerStopper - I was using this before I migrated to Docker and itzg/docker-minecraft-server
- Giving kids control of an EC2 instance via discord - Gave me the push to use Discord to reduce costs and simplify some of the workflow, and almost made me use GCP instead of AWS
- Thanks megamush in the AWS Discord for giving me some help and suggestions