Serverless Strapi v4 with Azure Container Instances (ACI)
Since I started working with Strapi, I always wanted to deploy it in a serverless fashion, That’s actually what I tend to do in most of my projects whenever it is worth it rather than using provisioned servers.
In the past I followed this tutorial on how to deploy Strapi v3 via AWS lambda using the Serverless framework. Great stuff, and amazing tutorial. While it is working pretty well, I thought there was one downside, the variability in response time from the requests (between 3 and 10 seconds), probably due to the cold starts with AWS Lambda.
I could reduce that variability a little by keeping my instance alive longer (by sending periodic requests to API Gateway), but this was not completely satisfying, and some of my customers complained that it was sometimes too slow. My other issue, was that I was using two platforms AWS and Azure, and I don’t like to juggle with multiple providers for a same project.
The release of the v4 was a nice improvement and I thought it could be good to give it a try as well. I then decided to make it work on Azure only this time via Azure Container Instances (ACI) and automate the deployment with Terraform. Doing things that way would incur a slower start on the first request, but good performance once the ACI is started.
This is what I will share with you below.
My main purpose was to use a Pay-Per-Use subscription and thus keep my cost as low as possible while keeping good performances.
Below is an overview of what we will deploy via Terraform:
The different entities:
- An ACR to handle our private Docker container images
- A storage account using a file share for our database (SQLite) and a storage container for our media.
- An ACI containing our running instance of Strapi served by a reversed proxy (Caddy).
- A powershell Azure Function that triggers the start/stop of our ACI
The implementation I adopted has a few specificities, which I expose below.
- Strapi does not handle SSL. As as result, serving the request to Strapi via a reverse proxy was here the easiest solution to circumvent the issue. Caddy was then the perfect fit for the task, and takes care of the SSL certificate for us, which is very convenient.
- The user should be able to connect to the Strapi login page as simply as possible via a single URL. In order to do that, I implemented a PowerShell Azure Function that does that on demand, and redirects the user to the ACI when it is up and running. The user just needs to send a request to the Azure Function.
- The ACI should be stopped after a defined time. The purpose of this project is to deploy Strapi in a serverless fashion with a Pay-per-consumption model. The ACI should then be stopped when it is no longer used/needed. In order to do that, I wrapped Strapi in a node.js application, that will periodically (via cron) verify when the last request to was sent to Strapi. If no request has been sent within the last 10 min, then the container exits and sends a request to the Azure function previously mentioned to stop the container group.
NB: I assume it would have been possible to do a Strapi plugin for that, but I did not dig into that part as of now.
- The deployment should be automated as much as possible to increase the speed and ease of use and reliability. A terraform script is taking care of the deployment of the whole infrastructure. The only part remaining is the actual deployment of the Azure function and build of the Docker container image. No pipeline is included in the solution for that, but this is a common workload depending on your CI/CD provider (GitHub, DevOps..) and should also be automated when pushing to the repo.
Try it out
If you want to try it out, the code is available here and the README contains the necessary steps and instructions in order to deploy the solution.
Most of the work is done by the Terraform scripts including the set up for most of the environment variables for the different resources. However a few manual steps are needed and require running a few edits or command lines:
- Edit variables for Strapi configuration (tokens, keys, secret..) according to your installation
- Build Strapi Docker image and deployment to container registry
- Convert SQLite database to WAL mode
- Deploy the Azure Function code to the Function App
The results are quite satisfying overall.
Start: The ACI takes about ~3 minutes to start when calling the Azure function until we get redirected to the Strapi login page.
NB: As a side note, it may be possible to gain some time on the startup by reducing the size of the Docker image, and using a Docker registry with a higher SKU than the one I have (Basic).
Speed: Strapi is running on an ACI with 0.5 CPU cores and 0.5 GiB and performs as fast as on my local computer, which is very satisfying.
Cost: Since the ACI is stopped most of the time, if a user is using the CMS for a few hours every day, the cost of running this solution will be considerably low (count a few dollars per month).
I hope you enjoyed this short note, and don’t hesitate to let me know what you think in the comments.