Using Docker To Deploy Applications To Azure Container Instances

This text was taken from the book and a Udemy course The DevOps Toolkit: Catalog, Patterns, And Blueprints

Help us choose the next subject for the course by filling in a survey at https://www.devopsparadox.com/survey

Azure Container Instances are a way to deploy containers in the Cloud. Based on that, you might think that ACI is not much different from other Containers as a Service solutions. But it is. It does not have horizontal scaling, nor any other features often associated with schedulers like Kubernetes. It is limited to the ability to run a single container in isolation. It is very similar to using Docker, except that it is in Azure, and that it saves us from worrying about the infrastructure needed to run containers.

So, if Azure Container Instances are very similar to Docker, why not use docker instead of az CLI? Fortunately, folks at Docker asked themselves the same question and released Docker Desktop that supports ACI. It is available since version 2.3.3+.


Let's give it a spin and see whether it is a viable replacement for az container commands.

The commands that follow will work only in Docker Desktop version 2.3.3 or above. Please double-check which version you are running and upgrade if needed. At the time of this writing (July 2020), version 2.3.3 is available only in the edge channel (Mac and Windows).

Since Docker Desktop does not exist for Linux, you will not be able to follow the instructions if that is your operating system of choice. You are stuck with az container commands.

Let's get going and deploy an application to Azure Container Instances using Docker Desktop.

If you are a Windows user, I will assume that you are running the commands from a Bourne Again Shell (Bash) or a Z Shell (Zsh) and not PowerShell. If you do not like WSL, a Bash emulator like GitBash should do. If none of those is an acceptable option, you might need to modify some of the commands in the examples that follow.

First, we need to login to Azure so that Docker can authenticate properly into our account.

docker login azure

You should see the Azure login screen opened in your browser. Follow the instructions to authenticate yourself.

Please note that I am assuming that you have an Azure resource group and environment variables REGION and RESOURCE_GROUP. Feel free to follow the instructions from the aci.sh Gist to create the resources you will need and set the environment variables.

Next, we will create an Azure Container Instances context.

docker context create aci aci \
    --location $REGION \
    --resource-group $RESOURCE_GROUP

We created an aci context with an uneventful and not very creative name aci. It is set to use the specific location (region) and operate within the specified resource group.

All that is left is to tell Docker to use the newly created context.

docker context use aci

Next, we will list the available contexts and confirm that the newly created one is indeed there.

docker context list

We can see that now there are two contexts of different types. There is moby, the default context pointing to the local Docker Desktop, and aci that uses Azure Container Instances service.

From now on, we could execute commands like docker run to deploy a container to Azure Container Instances service. But we will not do that. It's always a good idea to define everything as code. So, instead of running ad-hoc commands, we'll create a simple Docker Compose definition.

echo "version: \"3.8\"
services:
  frontend:
    image: vfarcic/devops-toolkit-series
    ports:
      - \"80:80\"" \
    | tee docker-compose.yaml

We will not go into details of the Docker Compose format. This is not the time and place. Instead, I will assume that you are already familiar with it. Even if you are not, you can probably guess what each of the fields means.

All that is left is to tell Azure to run the container image defined in that Compose YAML.

docker compose up \
    --project-name devops-toolkit-series

The output is as follows.

[+] Running 2/2
 ⠿ devops-toolkit-series Created  6.5s
 ⠿ frontend              Done    11.1s

That was easy, wasn't it?

Given that Azure Container Instances (ACI) is not a scheduler but a service that runs single-replica containers in isolation, Docker is a perfect fit. It allows us to use the tool (almost) everyone is familiar with (Docker) while leveraging the benefits of running in Cloud and using Containers as a Service (CaaS) in Azure. It is so simple that it is brilliant, as long as we do not need our applications to scale or any other features typically associated with more robust services like Google Cloud Run and AWS ECS with Fargate, or Kubernetes in general.

There are a few issues you might need to be aware of. We cannot specify a domain, nor many of the other arguments specific to Azure Container Instances. Nevertheless, the support for Azure Container Instances in Docker Desktop is fairly new (at least it was when I wrote this in July 2020). I can only assume that it will soon support (almost) all the options available in ACI.

The beauty of the integration between Docker Desktop and Azure Container Instances is that most of the other Docker commands work seamlessly.

For example, we can list all the processes (containers) just as we would do if we would work with the local Docker engine.

docker ps

The output is as follows.

CONTAINER ID                   IMAGE                               COMMAND STATUS  PORTS
devops-toolkit-series_frontend vfarcic/devops-toolkit-series:0.0.1 Running 52.191.81.120:80->80/tcp

We can also inspect the container.

docker inspect \
    devops-toolkit-series_frontend

The output is as follows.

{
    "ID": "devops-toolkit-series_frontend",
    "Status": "Running",
    "Image": "fo3izscp.azurecr.io/devops-toolkit-series:0.0.1",
    "Command": "",
    "CPUTime": 0,
    "CPULimit": 1,
    "MemoryUsage": 0,
    "MemoryLimit": 1073741824,
    "PidsCurrent": 0,
    "PidsLimit": 0,
    "Labels": null,
    "Ports": [
        {
            "HostPort": 80,
            "ContainerPort": 80,
            "Protocol": "tcp",
            "HostIP": "52.191.81.120"
        }
    ],
    "Platform": "Linux"
}

All that is left to validate that the container is indeed running and accessible, is to open the application in a browser. We'll need the host IP and port for that. That information is available in the output of the inspect command we have in front of us. Specifically, we need values of the fields HostIP and HostPort inside the block Ports.

We will use jq to extract what we need and store the data in environment variables.

export IP=$(docker inspect \
    devops-toolkit-series_frontend \
    | jq -r ".Ports[0].HostIP")

export PORT=$(docker inspect \
    devops-toolkit-series_frontend \
    | jq -r ".Ports[0].HostPort")

Next, we will combine the IP and the port into yet another variable that will have the full address of the application.

export ADDR=http://$IP:$PORT

Now comes the moment of truth. Can we access the application?

If you are a Linux or a Windows user, the open command might not work. If that's the case, please replace it with echo and copy and paste the output into your favorite browser.

open $ADDR

You should see the application in your browser. We opened it only to confirm that containers deployed into Azure Container Services through Docker are indeed accessible.

Feel free to navigate through the app and purchase as many books and courses as you can. Once you're done, expense them to your manager. It's a win-win situation (except for your manager).

We can even exec into a container running as an Azure Container Instance.

docker exec -it \
    devops-toolkit-series_frontend sh

There is nothing special I wanted us to do inside that container except to show that we can enter it, so let's get out.

exit

That's it. If you are familiar with Docker, you should be up-and-running in Azure Container Instances in no time. You can leverage all the Docker knowledge that you hopefully already posses.

Let's destroy what we created.

docker compose down \
    --project-name devops-toolkit-series

docker context rm aci --force

Leave a Reply