How to run Minecraft Java Server on Azure Container Instance


Here I explain how to run Minecraft Java server on Azure Container Instance.

Architecture


How to deploy Azure Container Instance

You need container deployment configuraiton yaml file called server-env.yaml. In this file you define how to deploy container instance. For example:

Example of server-env.yaml file:

apiVersion: 2023-05-01
location: swedencentral
name: minecraft
properties:
  containers:
  - name: minecraft
    properties:
      environmentVariables:
        - name: 'EULA'
          value: 'TRUE'
        - name: 'DIFFICULTY'
          value: 'easy'
        - name: 'GAMEMODE'
          value: 'survival'
        - name: 'HARDCORE'
          value: 'false'
      image: itzg/minecraft-server
      volumeMounts:
      - mountPath: /data
        name: filesharevolume
      ports:
      - port: 25565
        protocol: TCP
      resources:
        requests:
          cpu: 1.0
          memoryInGB: 1.5
  ipAddress:
    type: Public
    ports:
    - protocol: tcp
      port: '25565'
    dnsNameLabel: minecraft
    autoGeneratedDomainNameLabelScope: TenantReuse
  osType: Linux
  restartPolicy: OnFailure
  volumes:
  - name: filesharevolume
    azureFile:
      sharename: minecraftdata
      storageAccountName: stminecraftcontainerdata
      storageAccountKey: #{storagekey}#
tags: null
type: Microsoft.ContainerInstance/containerGroups
                  


You can test deploying container with the following command. NOTE! In the above example the storageAccountKey needs to be configured if testing manually. In this exmaple it is tokenized and Azure DevOps pipeline uses Token Replace task to configure it from secret varable during the pipeline run.

az container create --resource-group rg-minecraft-container --file server-env.yaml


Azure DevOps Pipeline Deployment to Azure

You can create Azure DevOps CI/CD pipeline for deploying and managing Azure Container Instance.

First of all the trigger is defined to be none, because we want to manually tell what operation we want to do with the pipeline. For example you decide to play Minecraft server with friends only 2 hours today, you can create new container for the server and destroy it after the gaming session. This way you keep costs to a minimumm.

With parametes you can define what options you have for operations.

Deployment uses external yaml configuration file server-env.yaml and secret values are tokenized. This means that you can store for example storage account key secret with secret value variable and grab it during the pipeline run and replace with template token. You can also integrate Azure Key Vault to Azure DevOps library variables.

Then there is just different Azure CLI commands for operations. Operations could be also done with statefull Terrafomr IaC tool, but in this simple example I decided just to create and destroy without worrying about the configuraitons. Those are still in the version control.
If container will be destroyed, the persisten server data still exists in the Azure storage accoount file share as difined in the container volume configuration. And when recreating the container, it reads the same data and you can continue your game where you left off.

Here is example how I can create, start, stop, destroy and check status of container instance.

Pipeline run looks like this:

Example of azure-pipeline.yaml file:

trigger: none
pr: none

parameters:
  - name: operation
    type: string
    default: start
    values: 
    - create
    - start
    - stop
    - destroy
    - status

variables:
- group: Minecraft

pool:  
  vmImage: ubuntu-latest

jobs:
- job: containerjob
  displayName: Operating Container

  steps:
  - checkout: self 

  - task: replacetokens@5
    inputs:
      rootDirectory: 'container'
      targetFiles: '**/*.yaml'
      encoding: 'auto'
      tokenPattern: 'default'
      writeBOM: true
      actionOnMissing: 'warn'
      keepToken: false
      actionOnNoFiles: 'continue'
      enableTransforms: false
      enableRecursion: false
      useLegacyPattern: false
      enableTelemetry: true
      tokenPrefix: '#{'
      tokenSuffix: '}#'


  - task: AzureCLI@2
    displayName: running operation for container
    inputs:
      azureSubscription: 'TuomasAzureSub'
      scriptType: 'pscore'
      scriptLocation: 'inlineScript'
      ${{ if eq(parameters.operation, 'create') }}:
        inlineScript: |
          az container create --resource-group rg-minecraft-container --file container/server-env.yaml
          echo "Minecraft server address: $(az container show --resource-group rg-minecraft-container --name minecraft |jq -r '.ipAddress.fqdn'):$(az container show --resource-group rg-minecraft-container --name minecraft |jq '.ipAddress.ports[].port')"
      ${{ if eq(parameters.operation, 'start') }}:
        inlineScript: |
          az container start --resource-group rg-minecraft-container --name minecraft
          echo "Minecraft server address: $(az container show --resource-group rg-minecraft-container --name minecraft |jq -r '.ipAddress.fqdn'):$(az container show --resource-group rg-minecraft-container --name minecraft |jq '.ipAddress.ports[].port')"
      ${{ if eq(parameters.operation, 'stop') }}:
        inlineScript: |
          az container stop --resource-group rg-minecraft-container --name minecraft
      ${{ if eq(parameters.operation, 'destroy') }}:
        inlineScript: |
          az container delete --resource-group rg-minecraft-container --name minecraft --yes
      ${{ if eq(parameters.operation, 'status') }}:
        inlineScript: |
          az container logs --resource-group rg-minecraft-container --name minecraft
          echo "Minecraft server address: $(az container show --resource-group rg-minecraft-container --name minecraft |jq -r '.ipAddress.fqdn'):$(az container show --resource-group rg-minecraft-container --name minecraft |jq '.ipAddress.ports[].port')"

                  


Conclusion

With Azure Container Instance, you can simply run, for example, a Java server at a very low cost. When you need a server you spin it up or create it and when you don't need it you can destroy it. You only pay for the CPU seconds you use. The only permanent expences are coming from the file share files of the server data.

There are many advantages of using containers. For example, if you used a virtual machine to run same Java server, you should install the server and update the OS. You should configure linux/windows services to restart Java server during the reboot and manage persistent data files inside the server. With virtual machines you can easily shut it down and start, but with containers it is as easy to just destroy and recreate. Every time you spin up the container you can define how much it can take CPU and memory. Scaling up of the virtual machine is slower and harder. Running virtual machines are more expensive, at least if you need a Windows license.

If you do not need statefull deployment, for example, playing new server world some hours and never come back to same map, you can forget the Azure storage account, file share and volume mapping. Then deployment is even simplier and you do not need to pay storage costs.

This example covers only simple single container deployment. If you need scalable environment, you need application gateway and load balancer with virtual networking. Or if you need more complex microservice architecture I recommend to check Azure Kubernetes Services (AKS).