For a year and a half, I mainly deploy every web application on Vercel. A Serverless platform currently cost me $20/month for the Pro plan, this is because some of my projects have exceeded their fair use policy when I'm on the always-free Hobby plan.
The pricing itself is good to me there's no problem with it, but a year later some of my project has heavily used NextJS's automatic image optimization a lot. When this feature has been announced it can be used without any billing at all, until a year later the mysterious "Image Optimization" panel arrived on Vercel's usage page at first I think to myself "Cool! Now I can see how many images have been optimized"
Then I got a surprise bill from Vercel that cost over $3000!!! This is because I have exceeded the amount of source images quota that has been optimized. By then I realized that Vercel just starting to bill an image optimization cost, free for the first 5000 source images then $9 per 1000 images.
If you exceed a bit (~10000 images) is fine, but when it goes too far then they will get started to bill these costs to you despite it being an open-source feature then you can just implement it by yourself to avoid an image being counted in _next/image
path. So, I have to go into the process of appealing to Vercel support for about a week to get this resolved and they gave free credit for that month.
By then I realized that I have to find an alternative platform fast, with the following requirements
- Zero downtime when deploying new revision
- Scaleable on high workloads
- Billed by actual CPU time that I use, not by per specific feature like Vercel
- Reasonable pricing
This adventure took about a year to research but then I find the perfect solution to this.
Why not just containerize everything???
Part 1: Why containerize?
Well, Docker has been invented for ages. I actually wrote a blog about this as well, but technology and features during that time are very limited. Despite that, it still retains the same concept as before.
Deploy across multiple host-os without breaking, thanks to virtualization
A lot of things happened during these periods to the present. A scaleable containerized platform has been invented everywhere, the multi-staged build has a significant impact on how could we reduce image size even further than just using alpine
image, and multi-platform images enable you to build a single image across multiple CPU architectures from typical x86 (Intel, AMD) to efficient arm64 (Apple Silicon, Raspberry Pi).
Also, if you always have at least 1 container running. It means that you will never be going to have cold start time like Serverless functions which would cost 1-2 seconds before the actual function can start running.
Part 2: How to pack my application into images?
This question has a lot of ways to answer because each programming language (Rust, Golang, Node) to each framework (Static, NextJS, SvelteKit) has its own unique way to create an image. You should research this by yourself there should be a lot of guides spread around the internet already. But in general, you need to build an image that...
- Small: smaller an image, the faster it can deploy a new revision.
- Efficient on idle: When there's no web request into a container, your CPU usage should be as flat as possible.
- Thinking with scalability in mind: For example, your web might be scaleable but your database might not. Then you will be going to have a really bad time.
- Optimizing: Is it possible to reduce CPU, and memory usage on your web even more? Less CPU and memory being used, less billing you will have to pay.
I recommend you to play along with your personal projects and then try to build and upload images to GitHub packages first. Then automate this process with GitHub Actions
Part 3: Where to deploy those images???
Nowadays, they're a lot of cloud solutions for you to deploy at. From Google Could Run, AWS Elastic Container Service, and Azure Kubernetes Service to even self-hosted with Kubernetes or CoreOS by yourself.
Each platform has its own feature and pricing. You should perform research by yourself as well before taking my word because some of your workflows might not be suitable for my way.
Even though the concept is the same, you will have to build an image and then upload it to the container registry (GitHub Packages, Docker Hub, etc.). Then calling some command to trigger the cloud to deploy a new revision.
Part 4: Results
I have a new project where there's an API to generate SVG image dynamically with Serverless API. The process from requesting data from the API server to optimizing artwork and rendering SVG from a template takes ~1.4 seconds without a cache. At first, I deploy on Vercel as usual and I have a problem with the function that takes 2 seconds to finish a cold start. With a total of 3.4 seconds, it's too long for GitHub camo to wait for the full SVG image to complete.
I've containerized the entire application and deployed it on Deploys.app which I can easily deploy automatically with complete GitHub Actions workflows.
As a result, my application is very efficient on idle with an average of near 0 CPU second and ~30MB of memory, and I always have at least 1 replica on standby so I won't going to have any cold boot and my SVG request has achieved ideal 1.5 seconds request time, with an extra feature of Cloudflare Enterprise plan on Deploys.app as well.
If you are interested in how to deploy your application to Deploys.app, there's a useful video from P'Thai demoing how to deploy anything to the platform already I recommend to also watching that as well.