├── _footer.md ├── examples ├── basic │ ├── _footer.md │ ├── _header.md │ ├── main.tf │ └── README.md ├── curseforge │ ├── _footer.md │ ├── _header.md │ ├── main.tf │ └── README.md └── .terraform-docs.yml ├── terraform.tf ├── outputs.tf ├── locals.tf ├── LICENSE ├── main.law.tf ├── .gitignore ├── .terraform-docs.yml ├── _header.md ├── main.storage.tf ├── main.aci.tf ├── variables.tf └── README.md /_footer.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/basic/_footer.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/curseforge/_footer.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /terraform.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.9" 3 | required_providers { 4 | azapi = { 5 | source = "Azure/azapi" 6 | version = "~> 2.2" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.6" 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "container_public_ip_address_and_port" { 2 | description = "The public IP address and port number of the Minecraft server." 3 | value = "${azapi_resource.container_instance.output.properties.ipAddress.ip}:${var.port}" 4 | } 5 | 6 | output "container_public_fqdn_and_port" { 7 | description = "The public FQDN of the Minecraft server." 8 | value = "${azapi_resource.container_instance.output.properties.ipAddress.fqdn}:${var.port}" 9 | } 10 | -------------------------------------------------------------------------------- /examples/basic/_header.md: -------------------------------------------------------------------------------- 1 | # Basic example 2 | 3 | This example shows how to deploy the server in its basic form. 4 | 5 | **Make sure to change your OPS player name in the `ops` section.** 6 | 7 | You can download this example to a local directory and run the following commands to deploy the server: 8 | 9 | > You will need to be logged in to your Azure account and have the Azure CLI installed as well as Terraform. 10 | 11 | ```shell 12 | terraform init 13 | terraform apply 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/curseforge/_header.md: -------------------------------------------------------------------------------- 1 | # Curseforge example 2 | 3 | This example shows how to deploy a server with a modpack from Curseforge. 4 | It also increases the view distance to 24 chunks. 5 | 6 | **Make sure to change your OPS player name in the `ops` section.** 7 | 8 | You can download this example to a local directory and run the following commands to deploy the server: 9 | 10 | > You will need to be logged in to your Azure account and have the Azure CLI installed as well as Terraform. 11 | 12 | ```shell 13 | terraform init 14 | terraform apply 15 | ``` 16 | -------------------------------------------------------------------------------- /locals.tf: -------------------------------------------------------------------------------- 1 | locals { 2 | storage_account_key1 = sensitive(azapi_resource_action.storage_account_list_keys.output.keys[0].value) 3 | storage_account_key2 = sensitive(azapi_resource_action.storage_account_list_keys.output.keys[1].value) 4 | } 5 | 6 | locals { 7 | file_share_name = "minecraftdata" 8 | } 9 | 10 | locals { 11 | container_environment_variables = concat( 12 | [ 13 | for key, value in var.minecraft_server_environment_variables : { 14 | name = key 15 | value = value 16 | secureValue = null 17 | } 18 | ], 19 | [ 20 | for key, value in nonsensitive(var.minecraft_server_environment_variables_sensitive) : { 21 | name = key 22 | value = null 23 | secureValue = sensitive(value) 24 | } 25 | ] 26 | ) 27 | } 28 | 29 | locals { 30 | log_analytics_workspace_primary_key = sensitive(azapi_resource_action.log_analytics_workspace_keys.output.primarySharedKey) 31 | log_analytics_workspace_secondary_key = sensitive(azapi_resource_action.log_analytics_workspace_keys.output.secondarySharedKey) 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Matt White 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /main.law.tf: -------------------------------------------------------------------------------- 1 | resource "random_id" "log_analytics_workspace_name" { 2 | byte_length = 6 3 | prefix = "law" 4 | } 5 | 6 | resource "azapi_resource" "log_analytics_workspace" { 7 | type = "Microsoft.OperationalInsights/workspaces@2023-09-01" 8 | parent_id = var.resource_group_resource_id 9 | name = random_id.log_analytics_workspace_name.hex 10 | location = var.location 11 | body = { 12 | properties = { 13 | features = { 14 | immediatePurgeDataOn30Days = true 15 | } 16 | publicNetworkAccessForQuery = "Enabled" 17 | publicNetworkAccessForIngestion = "Enabled" 18 | retentionInDays = 30 19 | sku = { 20 | name = "PerGB2018" 21 | } 22 | } 23 | } 24 | response_export_values = ["properties.customerId"] 25 | } 26 | 27 | resource "azapi_resource_action" "log_analytics_workspace_keys" { 28 | resource_id = azapi_resource.log_analytics_workspace.id 29 | type = "Microsoft.OperationalInsights/workspaces@2023-09-01" 30 | action = "sharedKeys" 31 | method = "POST" 32 | response_export_values = ["*"] 33 | } 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Local .terraform directories 2 | **/.terraform/* 3 | 4 | # .tfstate files 5 | *.tfstate 6 | *.tfstate.* 7 | 8 | # Crash log files 9 | crash.log 10 | crash.*.log 11 | 12 | # Exclude all .tfvars files, which are likely to contain sensitive data, such as 13 | # password, private keys, and other secrets. These should not be part of version 14 | # control as they are data points which are potentially sensitive and subject 15 | # to change depending on the environment. 16 | *.tfvars 17 | *.tfvars.json 18 | 19 | # Ignore override files as they are usually used to override resources locally and so 20 | # are not checked in 21 | override.tf 22 | override.tf.json 23 | *_override.tf 24 | *_override.tf.json 25 | 26 | # Ignore transient lock info files created by terraform apply 27 | .terraform.tfstate.lock.info 28 | 29 | # Include override files you do wish to add to version control using negated pattern 30 | # !example_override.tf 31 | 32 | # Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan 33 | # example: *tfplan* 34 | *tfplan* 35 | 36 | # Ignore CLI configuration files 37 | .terraformrc 38 | terraform.rc 39 | 40 | # As this is a module, ignore the lock file 41 | .terraform.lock.hcl 42 | -------------------------------------------------------------------------------- /.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ### To generate the output file to partially incorporate in the README.md, 2 | ### Execute this command in the Terraform module's code folder: 3 | # terraform-docs -c .terraform-docs.yml . 4 | 5 | formatter: "markdown document" # this is required 6 | 7 | version: "~> 0.18" 8 | 9 | header-from: "_header.md" 10 | footer-from: "_footer.md" 11 | 12 | recursive: 13 | enabled: false 14 | path: modules 15 | 16 | sections: 17 | hide: [] 18 | show: [] 19 | 20 | content: |- 21 | {{ .Header }} 22 | 23 | 24 | {{ .Requirements }} 25 | 26 | {{ .Resources }} 27 | 28 | 29 | {{ .Inputs }} 30 | 31 | {{ .Outputs }} 32 | 33 | {{ .Modules }} 34 | 35 | {{ .Footer }} 36 | 37 | output: 38 | file: README.md 39 | mode: replace 40 | template: |- 41 | 42 | {{ .Content }} 43 | 44 | output-values: 45 | enabled: false 46 | from: "" 47 | 48 | sort: 49 | enabled: true 50 | by: required 51 | 52 | settings: 53 | anchor: true 54 | color: true 55 | default: true 56 | description: false 57 | escape: true 58 | hide-empty: false 59 | html: true 60 | indent: 2 61 | lockfile: false 62 | read-comments: true 63 | required: true 64 | sensitive: true 65 | type: true 66 | -------------------------------------------------------------------------------- /_header.md: -------------------------------------------------------------------------------- 1 | # terraform-azure-minecraft-server 2 | 3 | This is a Terraform module to deploy a Minecraft server on Azure. 4 | 5 | ## Features 6 | 7 | - Azure Files for persistent storage 8 | - Azure Container Instances for the server itself 9 | - Uses the industry standard [`itzg/minecraft-server`]() container image 10 | 11 | ## Design Principles 12 | 13 | - Simple to use 14 | - Cost effective 15 | 16 | ## Configuration 17 | 18 | Most configuration is done via environment variables. See the [server properties](https://docker-minecraft-server.readthedocs.io/en/latest/configuration/server-properties/) for details. 19 | 20 | ```hcl 21 | module "minecraft_server" { 22 | source = "matt-FFFFFF/minecraft-server/azure" 23 | version = "..." # Change this to your desired version 24 | resource_group_resource_id = "/subscriptions/..." 25 | location = "swedencentral" 26 | 27 | minecraft_server_environment_variables = { 28 | EULA = "true" 29 | MEMORY = "4G", 30 | DIFFICULTY = "normal", 31 | SERVER_NAME = "Minecraft Server", 32 | OPS = "yourPlayerHandle" 33 | VIEW_DISTANCE = "32" 34 | } 35 | } 36 | ``` 37 | -------------------------------------------------------------------------------- /examples/.terraform-docs.yml: -------------------------------------------------------------------------------- 1 | ### To generate the output file to partially incorporate in the README.md, 2 | ### Execute this command in the Terraform module's code folder: 3 | # terraform-docs -c .terraform-docs.yml . 4 | 5 | formatter: "markdown document" # this is required 6 | 7 | version: "~> 0.18" 8 | 9 | header-from: "_header.md" 10 | footer-from: "_footer.md" 11 | 12 | recursive: 13 | enabled: false 14 | path: modules 15 | 16 | sections: 17 | hide: [] 18 | show: [] 19 | 20 | content: |- 21 | {{ .Header }} 22 | 23 | ```hcl 24 | {{ include "main.tf" }} 25 | ``` 26 | 27 | 28 | {{ .Requirements }} 29 | 30 | {{ .Resources }} 31 | 32 | 33 | {{ .Inputs }} 34 | 35 | {{ .Outputs }} 36 | 37 | {{ .Modules }} 38 | 39 | {{ .Footer }} 40 | output: 41 | file: README.md 42 | mode: replace 43 | template: |- 44 | 45 | {{ .Content }} 46 | 47 | output-values: 48 | enabled: false 49 | from: "" 50 | 51 | sort: 52 | enabled: true 53 | by: required 54 | 55 | settings: 56 | anchor: true 57 | color: true 58 | default: true 59 | description: false 60 | escape: true 61 | hide-empty: false 62 | html: true 63 | indent: 2 64 | lockfile: false 65 | read-comments: true 66 | required: true 67 | sensitive: true 68 | type: true 69 | -------------------------------------------------------------------------------- /examples/basic/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.9" 3 | required_providers { 4 | azapi = { 5 | source = "Azure/azapi" 6 | version = "~> 2.2" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.6" 11 | } 12 | } 13 | } 14 | 15 | resource "random_id" "rg" { 16 | byte_length = 6 17 | prefix = "rg" 18 | } 19 | 20 | resource "azapi_resource" "rg" { 21 | type = "Microsoft.Resources/resourceGroups@2024-03-01" 22 | name = random_id.rg.hex 23 | location = "swedencentral" 24 | 25 | } 26 | 27 | # When you copy this example, remove the source line with the relative path and 28 | # uncomment the source lines with the registry path and version 29 | module "minecraft_server" { 30 | source = "../../" 31 | # source = https://registry.terraform.io/modules/matt-FFFFFF/minecraft-server 32 | # version = "0.3.0" 33 | location = azapi_resource.rg.location 34 | resource_group_resource_id = azapi_resource.rg.id 35 | container_request_memory_in_gb = 5 36 | minecraft_server_environment_variables = { 37 | DIFFICULTY = "normal" 38 | EULA = "true" 39 | MEMORY = "4G" 40 | MODE = "survival" 41 | OPS = "yourPlayerHandle" # Set this to your Minecraft player handle to allow you to run admin commands in the server 42 | SERVER_NAME = "Minecraft Server" 43 | VERSION = "1.21.4" # This is the version of minecraft server 44 | VIEW_DISTANCE = "15" # This is the view distance of the server in chunks, this is a sensible default btu can be increased if you have a powerful server 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /main.storage.tf: -------------------------------------------------------------------------------- 1 | resource "random_id" "storage_account" { 2 | byte_length = 6 3 | prefix = "stg" 4 | } 5 | 6 | resource "azapi_resource" "storage_account" { 7 | type = "Microsoft.Storage/storageAccounts@2023-05-01" 8 | body = { 9 | kind = "StorageV2" 10 | properties = { 11 | accessTier = "Hot" 12 | allowSharedKeyAccess = true 13 | defaultToOAuthAuthentication = false 14 | isHnsEnabled = false 15 | isNfsV3Enabled = false 16 | isSftpEnabled = false 17 | minimumTlsVersion = "TLS1_2" 18 | networkAcls = { 19 | defaultAction = "Allow" 20 | } 21 | publicNetworkAccess = "Enabled" 22 | supportsHttpsTrafficOnly = true 23 | } 24 | sku = { 25 | name = "Standard_LRS" 26 | } 27 | } 28 | location = var.location 29 | name = coalesce(var.storage_account_name, random_id.storage_account.hex) 30 | parent_id = var.resource_group_resource_id 31 | } 32 | 33 | resource "azapi_resource" "file_share" { 34 | type = "Microsoft.Storage/storageAccounts/fileServices/shares@2023-05-01" 35 | body = { 36 | properties = { 37 | enabledProtocols = "SMB" 38 | shareQuota = 5 39 | } 40 | } 41 | name = local.file_share_name 42 | parent_id = "${azapi_resource.storage_account.id}/fileServices/default" 43 | } 44 | 45 | resource "azapi_resource_action" "storage_account_list_keys" { 46 | resource_id = azapi_resource.storage_account.id 47 | type = "Microsoft.Storage/storageAccounts@2023-05-01" 48 | action = "listKeys" 49 | method = "POST" 50 | response_export_values = ["keys"] 51 | } 52 | -------------------------------------------------------------------------------- /examples/curseforge/main.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 1.9" 3 | required_providers { 4 | azapi = { 5 | source = "Azure/azapi" 6 | version = "~> 2.2" 7 | } 8 | random = { 9 | source = "hashicorp/random" 10 | version = "~> 3.6" 11 | } 12 | } 13 | } 14 | 15 | resource "random_id" "rg" { 16 | byte_length = 6 17 | prefix = "rg" 18 | } 19 | 20 | resource "azapi_resource" "rg" { 21 | type = "Microsoft.Resources/resourceGroups@2024-03-01" 22 | name = random_id.rg.hex 23 | location = "swedencentral" 24 | 25 | } 26 | 27 | # When you copy this example, remove the source line with the relative path and 28 | # uncomment the source lines with the registry path and version 29 | module "minecraft_server" { 30 | source = "../../" 31 | # source = https://registry.terraform.io/modules/matt-FFFFFF/minecraft-server 32 | # version = "0.3.0" 33 | location = azapi_resource.rg.location 34 | resource_group_resource_id = azapi_resource.rg.id 35 | container_request_memory_in_gb = 9 36 | minecraft_server_environment_variables = { 37 | CF_PAGE_URL = "https://www.curseforge.com/minecraft/modpacks/the-vanilla-experience/files/5967958" # This is the URL of the modpack you want to install 38 | DIFFICULTY = "normal", 39 | EULA = "true" 40 | MEMORY = "6G", # If you have a lot of mods, you may need to increase this value, and also the memory allocated to the container 41 | MODE = "survival", 42 | OPS = "yourPlayerHandle" # Set this to your Minecraft player handle to allow you to run admin commands in the server 43 | SERVER_NAME = "Minecraft Server", 44 | TYPE = "AUTO_CURSEFORGE" # This tells the module to install the modpack from CurseForge and to detect the server type automatically 45 | VERSION = "1.21.4" # This is the version of minecraft server 46 | VIEW_DISTANCE = "24" # This is the view distance of the server in chunks, this is quite high and may need to be lowered depending on the server performance 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /main.aci.tf: -------------------------------------------------------------------------------- 1 | resource "random_id" "container_instance" { 2 | byte_length = 6 3 | prefix = "ci" 4 | } 5 | 6 | resource "random_id" "container_dns_prefix" { 7 | byte_length = 4 8 | } 9 | 10 | resource "azapi_resource" "container_instance" { 11 | type = "Microsoft.ContainerInstance/containerGroups@2023-05-01" 12 | body = { 13 | properties = { 14 | containers = [ 15 | { 16 | name = "minecraft" 17 | properties = { 18 | environmentVariables = local.container_environment_variables, 19 | image = var.container_image 20 | ports = [ 21 | { 22 | port = var.port 23 | protocol = "TCP" 24 | }, 25 | ], 26 | volumeMounts = [ 27 | { 28 | name = "filesharevolume", 29 | mountPath = "/data" 30 | } 31 | ] 32 | resources = { 33 | requests = { 34 | cpu = var.container_request_cpu 35 | memoryInGB = var.container_request_memory_in_gb 36 | } 37 | } 38 | } 39 | }, 40 | ] 41 | ipAddress = { 42 | dnsNameLabel = coalesce(var.container_dns_prefix, random_id.container_dns_prefix.hex) 43 | ports = [ 44 | { 45 | port = var.port 46 | protocol = "TCP" 47 | }, 48 | ] 49 | type = "Public" 50 | } 51 | diagnostics = { 52 | logAnalytics = { 53 | workspaceId = azapi_resource.log_analytics_workspace.output.properties.customerId 54 | workspaceKey = azapi_resource_action.log_analytics_workspace_keys.output.primarySharedKey 55 | } 56 | } 57 | osType = "Linux" 58 | restartPolicy = "Always" 59 | volumes = [ 60 | { 61 | name = "filesharevolume", 62 | azureFile = { 63 | shareName = local.file_share_name, 64 | storageAccountName = azapi_resource.storage_account.name, 65 | storageAccountKey = local.storage_account_key1 66 | } 67 | } 68 | ] 69 | } 70 | } 71 | location = var.location 72 | name = coalesce(var.container_instance_name, random_id.container_instance.hex) 73 | parent_id = var.resource_group_resource_id 74 | response_export_values = [ 75 | "properties.ipAddress.ip", 76 | "properties.ipAddress.fqdn", 77 | ] 78 | replace_triggers_refs = [ 79 | "properties.containers[].properties.resources.requests.memoryInGB", 80 | "properties.containers[].properties.resources.requests.cpu", 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "location" { 2 | type = string 3 | description = "The location of the resources." 4 | nullable = false 5 | } 6 | 7 | variable "resource_group_resource_id" { 8 | type = string 9 | description = "The full id of the resource group in which to deploy the resources." 10 | nullable = false 11 | validation { 12 | error_message = "The input value must be a valid resource id for a resource group." 13 | condition = can(regex("^(/subscriptions/[^/]+/resourceGroups/[^/]+)$", var.resource_group_resource_id)) 14 | } 15 | } 16 | 17 | variable "container_image" { 18 | type = string 19 | default = "docker.io/itzg/minecraft-server" 20 | description = "The image to use for the Minecraft server container. This should be based on the `itzg/minecraft-server` image." 21 | nullable = false 22 | } 23 | 24 | variable "container_instance_name" { 25 | type = string 26 | default = null 27 | description = "The name of the container instance. Leave as `null` to use the an auto-generated name." 28 | } 29 | 30 | variable "container_request_cpu" { 31 | type = number 32 | default = 2 33 | description = "The number of CPU cores to request for the Minecraft server container." 34 | nullable = false 35 | } 36 | 37 | variable "container_request_memory_in_gb" { 38 | type = number 39 | default = 5 40 | description = "The amount of memory in GB to request for the Minecraft server container." 41 | nullable = false 42 | } 43 | 44 | variable "minecraft_server_environment_variables" { 45 | type = map(string) 46 | default = { 47 | EULA = "true" 48 | } 49 | description = < for more information. 55 | DESCRIPTION 56 | nullable = false 57 | } 58 | 59 | variable "minecraft_server_environment_variables_sensitive" { 60 | type = map(string) 61 | default = {} 62 | description = "A map of sensitive environment variables to pass to the Minecraft server container. The key is the name of the environment variable, and the value is the value of the environment variable." 63 | nullable = false 64 | sensitive = true 65 | } 66 | 67 | variable "port" { 68 | type = number 69 | default = 25565 70 | description = "The port on which to expose the Minecraft server." 71 | nullable = false 72 | } 73 | 74 | variable "storage_account_name" { 75 | type = string 76 | default = null 77 | description = "The name of the storage account. Leave as `null` to use the an auto-generated name." 78 | } 79 | 80 | variable "log_analytics_workspace_name" { 81 | type = string 82 | default = null 83 | description = "The name of the log analytics workspace. Leave as `null` to use the an auto-generated name." 84 | } 85 | 86 | variable "container_dns_prefix" { 87 | type = string 88 | default = null 89 | description = "The DNS prefix to use for the container instance. Leave as `null` to use the an auto-generated name." 90 | } 91 | -------------------------------------------------------------------------------- /examples/basic/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Basic example 3 | 4 | This example shows how to deploy the server in its basic form. 5 | 6 | **Make sure to change your OPS player name in the `ops` section.** 7 | 8 | You can download this example to a local directory and run the following commands to deploy the server: 9 | 10 | > You will need to be logged in to your Azure account and have the Azure CLI installed as well as Terraform. 11 | 12 | ```shell 13 | terraform init 14 | terraform apply 15 | ``` 16 | 17 | ```hcl 18 | terraform { 19 | required_version = "~> 1.9" 20 | required_providers { 21 | azapi = { 22 | source = "Azure/azapi" 23 | version = "~> 2.2" 24 | } 25 | random = { 26 | source = "hashicorp/random" 27 | version = "~> 3.6" 28 | } 29 | } 30 | } 31 | 32 | resource "random_id" "rg" { 33 | byte_length = 6 34 | prefix = "rg" 35 | } 36 | 37 | resource "azapi_resource" "rg" { 38 | type = "Microsoft.Resources/resourceGroups@2024-03-01" 39 | name = random_id.rg.hex 40 | location = "swedencentral" 41 | 42 | } 43 | 44 | # When you copy this example, remove the source line with the relative path and 45 | # uncomment the source lines with the registry path and version 46 | module "minecraft_server" { 47 | source = "../../" 48 | # source = https://registry.terraform.io/modules/matt-FFFFFF/minecraft-server 49 | # version = "0.3.0" 50 | location = azapi_resource.rg.location 51 | resource_group_resource_id = azapi_resource.rg.id 52 | container_request_memory_in_gb = 5 53 | minecraft_server_environment_variables = { 54 | DIFFICULTY = "normal" 55 | EULA = "true" 56 | MEMORY = "4G" 57 | MODE = "survival" 58 | OPS = "yourPlayerHandle" # Set this to your Minecraft player handle to allow you to run admin commands in the server 59 | SERVER_NAME = "Minecraft Server" 60 | VERSION = "1.21.4" # This is the version of minecraft server 61 | VIEW_DISTANCE = "15" # This is the view distance of the server in chunks, this is a sensible default btu can be increased if you have a powerful server 62 | } 63 | } 64 | ``` 65 | 66 | 67 | ## Requirements 68 | 69 | The following requirements are needed by this module: 70 | 71 | - [terraform](#requirement\_terraform) (~> 1.9) 72 | 73 | - [azapi](#requirement\_azapi) (~> 2.2) 74 | 75 | - [random](#requirement\_random) (~> 3.6) 76 | 77 | ## Resources 78 | 79 | The following resources are used by this module: 80 | 81 | - [azapi_resource.rg](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 82 | - [random_id.rg](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 83 | 84 | 85 | ## Required Inputs 86 | 87 | No required inputs. 88 | 89 | ## Optional Inputs 90 | 91 | No optional inputs. 92 | 93 | ## Outputs 94 | 95 | No outputs. 96 | 97 | ## Modules 98 | 99 | The following Modules are called: 100 | 101 | ### [minecraft\_server](#module\_minecraft\_server) 102 | 103 | Source: ../../ 104 | 105 | Version: 106 | 107 | -------------------------------------------------------------------------------- /examples/curseforge/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Curseforge example 3 | 4 | This example shows how to deploy a server with a modpack from Curseforge. 5 | It also increases the view distance to 24 chunks. 6 | 7 | **Make sure to change your OPS player name in the `ops` section.** 8 | 9 | You can download this example to a local directory and run the following commands to deploy the server: 10 | 11 | > You will need to be logged in to your Azure account and have the Azure CLI installed as well as Terraform. 12 | 13 | ```shell 14 | terraform init 15 | terraform apply 16 | ``` 17 | 18 | ```hcl 19 | terraform { 20 | required_version = "~> 1.9" 21 | required_providers { 22 | azapi = { 23 | source = "Azure/azapi" 24 | version = "~> 2.2" 25 | } 26 | random = { 27 | source = "hashicorp/random" 28 | version = "~> 3.6" 29 | } 30 | } 31 | } 32 | 33 | resource "random_id" "rg" { 34 | byte_length = 6 35 | prefix = "rg" 36 | } 37 | 38 | resource "azapi_resource" "rg" { 39 | type = "Microsoft.Resources/resourceGroups@2024-03-01" 40 | name = random_id.rg.hex 41 | location = "swedencentral" 42 | 43 | } 44 | 45 | # When you copy this example, remove the source line with the relative path and 46 | # uncomment the source lines with the registry path and version 47 | module "minecraft_server" { 48 | source = "../../" 49 | # source = https://registry.terraform.io/modules/matt-FFFFFF/minecraft-server 50 | # version = "0.3.0" 51 | location = azapi_resource.rg.location 52 | resource_group_resource_id = azapi_resource.rg.id 53 | container_request_memory_in_gb = 9 54 | minecraft_server_environment_variables = { 55 | CF_PAGE_URL = "https://www.curseforge.com/minecraft/modpacks/the-vanilla-experience/files/5967958" # This is the URL of the modpack you want to install 56 | DIFFICULTY = "normal", 57 | EULA = "true" 58 | MEMORY = "6G", # If you have a lot of mods, you may need to increase this value, and also the memory allocated to the container 59 | MODE = "survival", 60 | OPS = "yourPlayerHandle" # Set this to your Minecraft player handle to allow you to run admin commands in the server 61 | SERVER_NAME = "Minecraft Server", 62 | TYPE = "AUTO_CURSEFORGE" # This tells the module to install the modpack from CurseForge and to detect the server type automatically 63 | VERSION = "1.21.4" # This is the version of minecraft server 64 | VIEW_DISTANCE = "24" # This is the view distance of the server in chunks, this is quite high and may need to be lowered depending on the server performance 65 | } 66 | } 67 | ``` 68 | 69 | 70 | ## Requirements 71 | 72 | The following requirements are needed by this module: 73 | 74 | - [terraform](#requirement\_terraform) (~> 1.9) 75 | 76 | - [azapi](#requirement\_azapi) (~> 2.2) 77 | 78 | - [random](#requirement\_random) (~> 3.6) 79 | 80 | ## Resources 81 | 82 | The following resources are used by this module: 83 | 84 | - [azapi_resource.rg](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 85 | - [random_id.rg](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 86 | 87 | 88 | ## Required Inputs 89 | 90 | No required inputs. 91 | 92 | ## Optional Inputs 93 | 94 | No optional inputs. 95 | 96 | ## Outputs 97 | 98 | No outputs. 99 | 100 | ## Modules 101 | 102 | The following Modules are called: 103 | 104 | ### [minecraft\_server](#module\_minecraft\_server) 105 | 106 | Source: ../../ 107 | 108 | Version: 109 | 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # terraform-azure-minecraft-server 3 | 4 | This is a Terraform module to deploy a Minecraft server on Azure. 5 | 6 | ## Features 7 | 8 | - Azure Files for persistent storage 9 | - Azure Container Instances for the server itself 10 | - Uses the industry standard [`itzg/minecraft-server`]() container image 11 | 12 | ## Design Principles 13 | 14 | - Simple to use 15 | - Cost effective 16 | 17 | ## Configuration 18 | 19 | Most configuration is done via environment variables. See the [server properties](https://docker-minecraft-server.readthedocs.io/en/latest/configuration/server-properties/) for details. 20 | 21 | ```hcl 22 | module "minecraft_server" { 23 | source = "matt-FFFFFF/minecraft-server/azure" 24 | version = "..." # Change this to your desired version 25 | resource_group_resource_id = "/subscriptions/..." 26 | location = "swedencentral" 27 | 28 | minecraft_server_environment_variables = { 29 | EULA = "true" 30 | MEMORY = "4G", 31 | DIFFICULTY = "normal", 32 | SERVER_NAME = "Minecraft Server", 33 | OPS = "yourPlayerHandle" 34 | VIEW_DISTANCE = "32" 35 | } 36 | } 37 | ``` 38 | 39 | 40 | ## Requirements 41 | 42 | The following requirements are needed by this module: 43 | 44 | - [terraform](#requirement\_terraform) (~> 1.9) 45 | 46 | - [azapi](#requirement\_azapi) (~> 2.2) 47 | 48 | - [random](#requirement\_random) (~> 3.6) 49 | 50 | ## Resources 51 | 52 | The following resources are used by this module: 53 | 54 | - [azapi_resource.container_instance](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 55 | - [azapi_resource.file_share](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 56 | - [azapi_resource.log_analytics_workspace](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 57 | - [azapi_resource.storage_account](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource) (resource) 58 | - [azapi_resource_action.log_analytics_workspace_keys](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource_action) (resource) 59 | - [azapi_resource_action.storage_account_list_keys](https://registry.terraform.io/providers/Azure/azapi/latest/docs/resources/resource_action) (resource) 60 | - [random_id.container_dns_prefix](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 61 | - [random_id.container_instance](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 62 | - [random_id.log_analytics_workspace_name](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 63 | - [random_id.storage_account](https://registry.terraform.io/providers/hashicorp/random/latest/docs/resources/id) (resource) 64 | 65 | 66 | ## Required Inputs 67 | 68 | The following input variables are required: 69 | 70 | ### [location](#input\_location) 71 | 72 | Description: The location of the resources. 73 | 74 | Type: `string` 75 | 76 | ### [resource\_group\_resource\_id](#input\_resource\_group\_resource\_id) 77 | 78 | Description: The full id of the resource group in which to deploy the resources. 79 | 80 | Type: `string` 81 | 82 | ## Optional Inputs 83 | 84 | The following input variables are optional (have default values): 85 | 86 | ### [container\_dns\_prefix](#input\_container\_dns\_prefix) 87 | 88 | Description: The DNS prefix to use for the container instance. Leave as `null` to use the an auto-generated name. 89 | 90 | Type: `string` 91 | 92 | Default: `null` 93 | 94 | ### [container\_image](#input\_container\_image) 95 | 96 | Description: The image to use for the Minecraft server container. This should be based on the `itzg/minecraft-server` image. 97 | 98 | Type: `string` 99 | 100 | Default: `"docker.io/itzg/minecraft-server"` 101 | 102 | ### [container\_instance\_name](#input\_container\_instance\_name) 103 | 104 | Description: The name of the container instance. Leave as `null` to use the an auto-generated name. 105 | 106 | Type: `string` 107 | 108 | Default: `null` 109 | 110 | ### [container\_request\_cpu](#input\_container\_request\_cpu) 111 | 112 | Description: The number of CPU cores to request for the Minecraft server container. 113 | 114 | Type: `number` 115 | 116 | Default: `2` 117 | 118 | ### [container\_request\_memory\_in\_gb](#input\_container\_request\_memory\_in\_gb) 119 | 120 | Description: The amount of memory in GB to request for the Minecraft server container. 121 | 122 | Type: `number` 123 | 124 | Default: `5` 125 | 126 | ### [log\_analytics\_workspace\_name](#input\_log\_analytics\_workspace\_name) 127 | 128 | Description: The name of the log analytics workspace. Leave as `null` to use the an auto-generated name. 129 | 130 | Type: `string` 131 | 132 | Default: `null` 133 | 134 | ### [minecraft\_server\_environment\_variables](#input\_minecraft\_server\_environment\_variables) 135 | 136 | Description: A map of environment variables to pass to the Minecraft server container. 137 | The key is the name of the environment variable, and the value is the value of the environment variable. 138 | 139 | For the container to work, you must include the `EULA` environment variable with the value `true`. 140 | See for more information. 141 | 142 | Type: `map(string)` 143 | 144 | Default: 145 | 146 | ```json 147 | { 148 | "EULA": "true" 149 | } 150 | ``` 151 | 152 | ### [minecraft\_server\_environment\_variables\_sensitive](#input\_minecraft\_server\_environment\_variables\_sensitive) 153 | 154 | Description: A map of sensitive environment variables to pass to the Minecraft server container. The key is the name of the environment variable, and the value is the value of the environment variable. 155 | 156 | Type: `map(string)` 157 | 158 | Default: `{}` 159 | 160 | ### [port](#input\_port) 161 | 162 | Description: The port on which to expose the Minecraft server. 163 | 164 | Type: `number` 165 | 166 | Default: `25565` 167 | 168 | ### [storage\_account\_name](#input\_storage\_account\_name) 169 | 170 | Description: The name of the storage account. Leave as `null` to use the an auto-generated name. 171 | 172 | Type: `string` 173 | 174 | Default: `null` 175 | 176 | ## Outputs 177 | 178 | The following outputs are exported: 179 | 180 | ### [container\_public\_fqdn\_and\_port](#output\_container\_public\_fqdn\_and\_port) 181 | 182 | Description: The public FQDN of the Minecraft server. 183 | 184 | ### [container\_public\_ip\_address\_and\_port](#output\_container\_public\_ip\_address\_and\_port) 185 | 186 | Description: The public IP address and port number of the Minecraft server. 187 | 188 | ## Modules 189 | 190 | No modules. 191 | 192 | --------------------------------------------------------------------------------