Lessons learned - deploying on Azure

| Nico Lutz

Getting our products into production also requires deploying them to a agreed upon hoster. This time a client wanted us to host a combination of database, backend and frontend on Azure. While Azure is a very popular hosting provider I learned some lessons a long the way that I thought would be worth sharing.

Deploying a database (such as postgres) with the cli or an infrastructure as code service such as terraform is pretty straightforward. Conceptualizing the rest of the stack requires more thinking and tossing azures documentation. In the end we decided for a simple (native) python webapp for the backend and static webapp for the frontend.

Backend

Based on your deployment method Azure uses Kudu and/or Oryx. Amongst others there are several ways to deploy, one way is to simple deploy from local via zip - which is of course not the ci/cd way to do things, but is nice to get something going and showing results to the client.

To provide some context the backend is a simple Django backend, initialized with poetry and running on python 3.9. The fact that we used poetry was the first stopper for me. It seems like the native python build searches for a requirements.txt in the root directory and otherwise fails to install the dependencies. No problem since generating a requirements.txtfrom poetry is easy:

poetry export -f requirements.txt -o requirements.txt --without-hashes

Now we zipped the source code and moved it to the webapp instance. But still no luck in getting the app running. It turns out one needs to set the environment variable SCM_DO_BUILD_DURING_DEPLOYMENT=true otherwise Oryx won't build your requirements text. Another option I tried was using a start up script to install the dependencies directly from poetry but be aware there is a time limit on how long that script is allowed to take. So after installing some dependencies Oryx will stop the process since it takes to long before actually reaching the end.

None of this is explicitly stated in the get started tutorials - I learned this by finding the documentation on how Oryx actually builds the code. There you also find instructions on how Oryx searches for files etc.

For webapps that require a storage besides the database make sure to setup something permanent like blob storage on Azure or use the /home directory in your webapp - anything else will be deleted when updating the app.

For diagnostics make sure to checkout the "Diagnose and solve problems" tab in the portal. This gives you more fine grained diagnostic logs not only the application but also on the platform level and can give you hints on where you app might fail during start up.

And lastly a tip for developers who want to use the Django admin panel but don't have a way to serve the static content without relying on nginx, Amazon S3 or other external services. Whitenoise allows e.g. Django to serve any static content, such as an admin panel. On Django it is as easy as adding a middleware to the project.

MIDDLEWARE = [
    # ...
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    # ...
]

Frontend

I had less problems on the frontend, but wanted to highlight the swa-cli which gives developers great control on pushing static webapps from local to a static webapp. Just build your app locally:

swa build

and push using a deployment token(az staticwebapp secrets list --name loop-frontend --query "properties.apiKey"):

swa build && swa deploy --deployment-token=<token> --env=<slot>

Lastly besides the start hick-ups (I could have just used a docker images) I like that it is very easy on azure to run the apps in different environments. In theory we can now start for each branch a different staging environment. And the same goes for the backend. In addition it is relatively easy to point the database/backend/frontend to each other by leveraging environment variables. That being said most of the topics converted here are just for getting things started - ideally one sets up a ci/cd pipeline with correct access rights and makes sure only certain branches and users can push to the webapp instance. But that is another topic...