Using Pulumi to Streamline Deployment of Dockerized Applications on Azure
Tags: cloud, docker, devops, python
Published: 2023-01-16 14:43:24

**Infrastructure as Code (IaC)** is a method of provisioning and managing infrastructure using code and automation tools, rather than manual configuration. The goal of IaC is to make it easy to provision, update, and manage infrastructure, while also providing a way to version control and audit changes.

With IaC, infrastructure is defined in a code file, such as a YAML or JSON, and then provisioned using a tool such as **Terraform**. This code file acts as a single source of truth for the infrastructure, and can be version controlled, meaning that the history of changes to the infrastructure can be tracked, and rollback to a previous version can be done easily.

But, for this tutorial, I am going to use **Pulumi**, an open-source tool that allows you to define your infrastructure as code and automate the deployment of it on **Azure**. Pulumi enables you to use your preferred programming language to define your infrastructure, making it easy to understand and manage for developers than working with YAMLs. I will be using Python to deploy a simple _Dockerized Flask app_ here.

## Prerequisites
Before you begin, you should have the following things set up:

 - A **Microsoft Azure** account. You can sign up for a free account at [azure.com](https://azure.com). If you are a **student**, you can create a free Azure account and get a **free $100 credit** even without a credit card. For more details: [azure.microsoft.com/en-in/free/students](https://azure.microsoft.com/en-in/free/students/)

 - The **Azure CLI** installed on your computer. You can install the Azure CLI by following the instructions at [learn.microsoft.com/en-us/cli/azure/install-azure-cli](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli). 

 - **Pulumi CLI** installed on your machine. You can install it using: [pulumi.com/docs/get-started/install](https://www.pulumi.com/docs/get-started/install/)

 - **Docker** installed on your machine in order to build and push the Docker image of your application. You can install it from here [docs.docker.com/get-docker](https://docs.docker.com/get-docker/)

 - **Dockerfile**: You will need to have a Dockerfile for your application that you intend to deploy.

I would recommend everyone to go through the [Pulumi Docs](https://www.pulumi.com/registry/) to understand the API, on how to communicate with various services.

## Writing the Code:

Before going to the code, ensure that you are logged in to Azure and Pulumi.

You can login to Azure by entering the following command in your terminal

```bash
az login
```
Similarly, login to Pulumi by entering:

```bash
pulumi login
```

You can initialize a new Pulumi project in python by entering:

```
pulumi new python
```
Follow the prompts in creating a stack.

Now let's go through the `__main__.py` file. It contains the entire logic that is needed for pulumi to provision infrastructure. Remove the given boilerplate code with the one below. We shall go line by line in writing it.

```python
import pulumi
import pulumi_docker as docker
import pulumi_azure_native.resources as resources
import pulumi_azure_native.web as web
```
Importing the necessary libraries: The first few lines of the code contains the import statements for the necessary libraries of the script, i.e, `pulumi`, `pulumi_docker`, `pulumi_azure_native.resources`, and `pulumi_azure_native.web`. These libraries are required for interacting with Azure and Docker using Pulumi.

You need to add `pulumi_docker` and `pulumi_azure_native` to your virtual environment which Pulumi has created in the `venv` folder. Now activate the virtual environment and install those packages:

```bash
source venv/bin/activate # For Linux/Mac
# venv\Scripts\activate # For Windows
pip install pulumi_docker pulumi_azure_native
```

```python
config = pulumi.Config()
dockerHubUserName = config.require("username")
dockerHubPassword = config.require("password")
```

Configuring the necessary parameters: The config variable is used to read the username and password from the Pulumi configuration, which will be used later to authenticate to the Docker registry.

You can add those secrets to your Pulumi configuration by entering the following lines in your terminal:

```bash
pulumi config set --secret username YOUR_DOCKERHUB_USERNAME
pulumi config set --secret password YOUR_DOCKERHUB_ACCESS_TOKEN
```
You can generate an access token for your Docker Hub account on [hub.docker.com/settings/security](https://hub.docker.com/settings/security)

```python
imageName = "flask_azure_pulumi"
imageTag = "v1.0.0"  # Change to whatever version you are creating
azureResourceLocation = (
    "UAE North"  # Change to the region that is closest to your end users
)
```

The imageName variable is set to `flask_azure_pulumi` and imageTag variable is set to `v1.0.0`, you can change it to the version you are creating. The azureResourceLocation variable is set to _UAE North_, but you can change it to the region that is closest to your end users. I have chosen _UAE North_ because it is the closest location to me having an F1 tier.


```python
flaskAzureImage = docker.Image(
    imageName,
    build=docker.DockerBuild(context="app"),
    image_name=f"{dockerHubUserName}/{imageName}:{imageTag}",
    registry=docker.ImageRegistry(
        server="docker.io", username=dockerHubUserName, password=dockerHubPassword
    ),
)
```

Building the Docker image: The flaskAzureImage variable is used to create an instance of the `docker.Image` class. The context for the DockerBuild is set to `app`, which specifies the path of the folder containing the Dockerfile that will be used to build the image.

The `image_name` parameter is set dynamically to `dockerHubUserName/imageName:imageTag` which specifies the name and tag of the image that will be pushed to the Docker registry. The ImageRegistry server is set to _docker.io_ with the corresponding login details.

```python
resource_group = resources.ResourceGroup(
    "pulumi_flask_app", location=azureResourceLocation
)
```

Creating a Resource Group: The `resource_group` variable is used to create an instance of the `resources.ResourceGroup` class. The name is set to `pulumi_flask_app` and the location parameter is set to `azureResourceLocation` which is defined earlier. It specifies the Azure region where the resource group will be created.

```python
plan = web.AppServicePlan(
    "pulumi",
    resource_group_name=resource_group.name,
    kind="Linux",
    location=azureResourceLocation,
    reserved=True,
    sku=web.SkuDescriptionArgs(
        name="F1",
        tier="Free",
    ),
)
```


Creating an App Service Plan: The plan variable is used to create an instance of the `web.AppServicePlan` class. I have selected a Linux-based Web App using the `kind` and `reserved` parameters. The `sku` parameter is set to `web.SkuDescriptionArgs(name="F1", tier="Free")` which specifies that the plan will use the "F1" (Free) tier. Change the tier as per your needs.

```python
flask_alpine = web.WebApp(
    "flask-azure-pulumi",
    resource_group_name=resource_group.name,
    server_farm_id=plan.id,
    site_config=web.SiteConfigArgs(
        app_settings=[
            web.NameValuePairArgs(
                name="WEBSITES_ENABLE_APP_SERVICE_STORAGE", value="false"
            )
        ],
        linux_fx_version=f"DOCKER|{dockerHubUserName}/{imageName}:{imageTag}",
    ),
    https_only=True,
)
```

Creating a Web App: The `flask_alpine variable` is used to create an instance of the `web.WebApp` class. The `server_farm_id` parameter is set to `plan.id` which specifies the App Service Plan that the Web App will be associated with. The `site_config` specifies the configuration of the Web App by providing it with the Docker Image link as specified in `linux_fx_version`. The `https_only` parameter is set to `True` which enforces that the Web App can only be accessed via HTTPS. 

There are more variables that you can adjust and fine tune as per your app requirements and I have just scratched the surface. So, feel free to refer to the [docs](https://www.pulumi.com/registry/packages/azure-native/api-docs/web/webapp/) while creating.

```python
pulumi.export(
    "websiteURL",
    flask_alpine.default_host_name.apply(
        lambda default_host_name: f"https://{default_host_name}"
    ),
)
```

Exporting the Website URL: The pulumi.export function is used to export the URL of the website. This exported value can be accessed later using the `pulumi stack output` command.

## Complete code

You can check out my [Github Repo](https://github.com/dhanushreddy291/pulumi-azure-docker) for the complete merged code.

Overall, this code uses the Pulumi library to automate the deployment of the infrastructure, using Azure and Docker. The script creates a resource group, an App Service Plan, a Web App and pushes the Docker image to the Docker registry. The script also exports the website URL which can be accessed later. The URL for the app that I have created is [flask-azure-pulumic4668d36.azurewebsites.net](https://flask-azure-pulumic4668d36.azurewebsites.net/)


## Conclusion

In this blog post, we have discussed how to use Pulumi to deploy a simple Dockerized application on Azure. We have explained the benefits of using IaC and how Pulumi simplifies the deployment process. By using Pulumi, we can define our infrastructure as code, automate the deployment process, and version control the changes. Additionally, by using Azure App Service and Web App, we can easily host and scale our application in a secure and reliable environment. Pulumi is a powerful tool that enables developers to manage their infrastructure in a programmatic and automated way, saving time and effort while improving consistency and reproducibility.

I hope this tutorial was helpful in showing you how to use Pulumi in setting up Infrastructure effortlessly. Let me know if you have any questions or suggestions for improvement.

In case if you still have any questions regarding this post or want to discuss something with me feel free to connect on [LinkedIn](https://www.linkedin.com/in/dhanushreddy29/) or [Twitter](https://twitter.com/dhanushreddy291).

> If you run an organization and want me to write for you, please connect with me on my Socials 🙃