aci, docker, nodejs

How To Deploy Containers to Azure ACI using Docker CLI and Compose

Written by Peter McKee

Running containers in the cloud can be hard and confusing. There are so many options to choose from and then understanding how all the different clouds work from virtual networks to security. Not to mention orchestrators. It’s a learning curve to say the least.

At Docker we are making the Developer Experience (DX) more simple. As an extension of that we want to provide the same beloved Docker experience that developers use daily and integrate it with the cloud. Microsoft’s Azure ACI provided an awesome platform to do just that.

In this tutorial, we take a look at running single containers and multiple containers with Compose in Azure ACI. We’ll walk you through setting up your docker context and even simplifying logging into Azure. At the end of this tutorial, you will be able to use familiar Docker commands to deploy your applications into your own Azure ACI account.


To complete this tutorial, you will need:

Run Docker Container on ACI

The integration with Azure ACI is very similar to working with local containers. The development teams have thought very deeply about the developer experience and have tried to make the UX for working with ACI as close as possible to working with local containers.

Let’s run a simple Nginx web server on Azure ACI.

Log into Azure

You do not need to have the Azure CLI installed on your machine to run Docker images in ACI. Docker takes care of everything.

The first thing you need to do is to login to Azure.

$ docker login azure

This will open a browser window which will allow you to login to Azure.

Select your account and login. Once you are logged in, you can close the browser window.

Azure ACI Context

Docker has the concept of a context. You can think of a context as a place where you can run docker containers.It’s a little more complicated than this but this is a good enough description for now. In this tutorial, we use our local context and the new ACI context.

Let’s first take a look at what contexts we currently have on our local development machine. Run the following command to see a list of contexts.

$ docker context list
NAME                TYPE                DESCRIPTION                               DOCKER ENDPOINT               KUBERNETES ENDPOINT                                 ORCHESTRATOR

default *           moby                Current DOCKER_HOST based configuration   unix:///var/run/docker.sock   https://kubernetes.docker.internal:6443 (default)   swarm

Depending on if you have already created another context, you should only see one context. This is the default context that points to your local Docker engine labeled as “moby”. You can identify the current context that will be used for docker commands by the “*” beside the name of the active context.

Now let’s create an ACI context that we can run containers with. We’ll use the create aci command to create our context.

Let’s take a look at the help for creating an aci context.

$ docker context create aci --help
Create a context for Azure Container Instances

  docker context create aci CONTEXT [flags]

      --description string       Description of the context
  -h, --help                     help for aci
      --location string          Location (default "eastus")
      --resource-group string    Resource group
      --subscription-id string   Location

Global Flags:
      --config DIRECTORY   Location of the client config files DIRECTORY (default "/Users/peter/.docker")
  -c, --context string     context
  -D, --debug              enable debug output in the logs
  -H, --host string        Daemon socket(s) to connect to

Underneath the Flags section of the help, you can see that we have the option to set the location, resource-group, and subscription-id.

You can pass these flags into the create command. If you do not, the docker cli will ask you these questions in interactive mode. Let’s do that now.

$ docker context create aci myaci

The first thing the cli will ask is what subscription you would like to use. If you only have one then docker will use that one.

Using only available subscription : Azure subscription 1 (b3c07e4a-774e-4d8a-b071-xxxxxxxxxxxx)

Now we need to select the resource group we want to use. You can either choose one that has been previously created or choose “create a new resource group”. I’ll choose to create a new one.

Resource group "c3eea3e7-69d3-4b54-83cb-xxxxxxxxxxxx" (eastus) created

Okay, our aci context is set up. Let’s list our contexts.

$ docker context list

You should see the ACI context you just created.

Run Containers on ACI

Now that we have our ACI context set up, we can now run containers in the cloud. There are two ways to tell Docker which context you want your commands to be applied to.

The first is to pass the –context flag. The other is to tell Docker which context we want to use with all subsequent commands by switching contexts. For now, let’s use the –context flag.

$ docker --context myaci run -d --name web -p 80:80 nginx
[+] Running 2/2
 ⠿ web                         Created                                                                           
 ⠿ single--container--aci      Done                                                                                

Here you can see that Docker interacted with ACI and created a container instance named “web” and started a single instance.

Open your Azure portal and navigate to container instances.

We can also run Docker CLI commands that you are already familiar with such as ps and logs.

Switch Contexts

Let’s take a look at our running containers. But before we do that let’s switch our active context to the ACI context we setup above so we do not have to keep typing –context with every command.

$ docker context use myaci

Now let’s run the ps command without passing the –context flag.

$ docker ps
CONTAINER ID        IMAGE               COMMAND             STATUS              
web                            nginx                                                              Running   >80/tcp

Nice, since we told Docker to use the myaci context, we see a list of containers running in our Azure account and not on our local machine.

Let’s make sure our container is running. Copy the IP address of the container from the above ps output and paste it into your browser address bar. You can see our Nginx web server running!

Like I mentioned above, we can also take a look at the container’s logs.

$ docker logs web

To stop and remove the container, run the following command.

$ docker rm web


That was pretty easy and the integration is smooth. With a few docker commands that you are already familiar with and a couple new ones, we were able to run a container in ACI from our development machine pretty quickly and simply.

But we’re not done!

Docker Compose

We can also run multiple containers using Docker Compose. With the ACI integration, we now have the ability to run compose commands from the docker cli against ACI. Let’s do that next.

Fork the Code Repository

I’m using a simple Python Flask application that logs timestamps to a Redis database. Let’s fork the repository and then clone the git repository to your local machine.

Open your favorite browser and navigate to:

Click on the “fork” button in the top right corner of the window. This will make a “copy” of the demo repository into your GitHub account.

On your forked version of the repository, click the green “Code” button and copy the github url.

Open up a terminal on your local machine and run the following git command to clone the repository to your local development machine.

Make sure you replace the <<github username>> with your GitHub username.

git clone<<github username>>/timestamper.git

Build and Run Locally

Make sure you are in the root directory for the timestamper project and follow the following steps to build the images and start the application with Docker Compose.

First we need to add your Docker ID to the image in our docker-compose.yml file. Open the docker-compose.yml file in an editor and replace <<username>> with your Docker ID.

Next, we need to make sure we are using the local Docker context.

$ docker context use default

Now we can build and start our application using docker-compose.

$ docker-compose up --build
Building frontend
Step 1/7 : FROM python:3.7-alpine
 ---> 6ca3e0b1ab69
Step 2/7 : WORKDIR /app
frontend_1  |  * Running on (Press CTRL+C to quit)
frontend_1  |  * Restarting with stat
frontend_1  |  * Debugger is active!
frontend_1  |  * Debugger PIN: 622-764-646

Docker will build our timestamper image and then run the Redis database and our timestamper containers.

Navigate to http://localhost:5000 and click the Timestamp! button a couple of times.

Compose on ACI

Now let’s run our application on ACI using the new docker compose integration.

We’ll first need to push our image to Docker Hub so ACI can pull the image and run it. Run the following command to push your image to your Docker Hub account.

$ docker-compose push
Pushing frontend (pmckee/timestamper:latest)...
The push refers to repository []
6e899582609b: Pushed
50644c29ef5a: Layer already exists
latest: digest: sha256:3ce2607f101a381b36beeb0ca1597cce9925d17a0f826cac0f7e0365386a3042 size: 2201

Now that our image is on Hub, we can use compose to run the application on ACI.

First let’s switch to our ACI context.

$ docker context use myaci

Remember, to see a list of contexts and which is being used, you can run the list contexts command.

$ docker context list

Okay, now that we are using the ACI context, let’s start our application in the cloud.

$ docker compose up
[+] Running 3/3
 ⠿ timestamper     Created
 ⠿ frontend        Done
 ⠿ backend         Done

Let’s verify that our application is up and running. To get the IP address of our frontend, let’s list our running containers.

$ docker ps
CONTAINER ID           IMAGE                COMMAND             STATUS              PORTS

timestamper_frontend       pmckee/timestamper                       
Running   >5000/tcp

timestamper_backend         redis:alpine                             

Copy the IP address and port listed above and paste into your favorite browser.

Let’s take a look at the logs for our Redis container.

$ docker logs timestamper_backend
1:C 13 Jul 2020 18:21:12.044 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
1:M 13 Jul 2020 18:21:12.046 # Server initialized
1:M 13 Jul 2020 18:21:12.047 * Ready to accept connections

Yes, sir! That is a Redis container running in ACI! Pretty cool.

After you play around a bit, you can take down the compose application by running compose down.

$ docker compose down


We saw how simple it is now to run a single container or run multiple containers using Compose on Azure with our ACI integration. If you want to help influence or suggest features, you can do that on our public Roadmap.

If you want to learn more about Compose and all the cool things that are happening around the OpenSource initiative, please checkout Awesome Compose and the OpenSource Compose specification.