Heroku was one of the first platforms that made it easy to deploy your app without having to manage the underlying infrastructure - this was a revolution back in the days and introduced the concept of PaaS (Platform-as-a-service) to the wider developer community. The main idea was to abstract away as much of the infrastructure as possible, so developers could focus on building and deploying their applications.

However, a lot of time has passed, Heroku was bought by Salesforce and has become less competitive in terms of pricing and features. This has led to the rise of a new generation of cloud platforms that offer more flexibility and better pricing.

Deploying containers to the cloud.

Deploying containers to the cloud.

In this post, I will compare three platforms: Render, Railway.app, and Fly.io, and share my experience of deploying a containerized Python application on them. For this comparison, I will use a simple FastAPI application that I have containerized. The application returns an HTML with the current deployment platform shown - in fact, it’s almost the same example as my previous faasd deployment article. You can check out the source code in this GitHub repository.

Render

Started in 2018, Render will be the first one of the platforms I’ll be testing. I appreciated the free hobby plan - though they require you to leave payment details, they will not charge you for the initial deployment.

Render offers a range of services:

  • native runtimes: Node, Python, Go, Ruby, Elixir, Rust & PHP
  • Docker runtime
  • Managed databases: Postgres & Redis
  • Background services/workers, cron jobs and CDN for static sites/files

Thought Render offers a native Python runtime, choosing Docker containers offers you a greater level of control over the environment and dependencies deployed with your app (e.g. OS-level packages). Especially for multi-service setups using containers is preferred.

Render offers “Blueprints” (Infrastructure as Code) to define and manage your deployments in a single yaml file. This even allows you to define multiple services, databases as well as background workers in a single place. You can choose the deployment region to keep the final deployed server near to your users as well as define service scaling rules. See the blueprints specification documentation for more details. I love this option in general, as it’s one of the few PaaS providers that offer this feature and it’s likely the eastiest way to “translate” your local Docker Composer file to a cloud deployment.

Render Dashboard

Render Dashboard

After registering on Render you start by choosing the “Blueprint” service type and connect your GitHub repository. It will then note that you are missing a render.yaml in the root of your repo. Add the file and push it to your repository. Render will then automatically try to build and deploy your app on every push to the repository. To avoid deploying on every push, you can use build filters as part of the render.yaml file, to deploy only if specific file paths have changed.

While researching Render online, I often found sentiments suggesting a rough developer experience. However, I did not experience this personally: the documentation was quite comprehensive for my use-case and I was able to quickly write the correct spec render.yaml file. Some caveats to be aware of:

  • you cannot change the name of the service once deployed (you’ll have to delete and re-deploy to do so)
  • the same applies to your region

The free-tier (with 0.5GB of memory) is capable to host small projects and should be good to get you started, however beware it will be spun down after if you don’t get any traffic for a while - any subsequent requests will take a at least 50 seconds to respond. Unfortunately, this makes it unsuitable for any kind of serious use (even for hobbyists). Check out my deployed app on Render.com if you want to see it live (chances are you will encounter the coldstart).

I cannot comment on availability and reliability of the service in general at the moment and will have to wait until I have more experience with it. The prices for Render’s services are quite steep beyond the free-tier but more on that later …

Render Prices

Render Prices

Fly.io

Fly.io is another competitor in the PaaS space. What stands out with them is their CLI-first approach, which is quite different from Render’s git-centric, push & deploy approach. You need to install the flyctl CLI to initialize and launch your app. Upon logging in and initializing your app, the fly.toml file will be created in the root of your repository.

This file manages some important settings such as the choice of internal ports, deployment regions, as well as environment variables.

app = 'paas-comparison'
primary_region = 'ams'

[build]

[http_service]
  internal_port = 8082
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true
  min_machines_running = 0
  processes = ['app']

[[vm]]
  cpu_kind = 'shared'
  cpus = 1
  memory_mb = 1024

[build.args]
  PAAS="FLY"

[env]
  PAAS="FLY"

Pushing to your repository will NOT automatically trigger a build on Fly.io’s infrastructure. You will need to run fly deploy on your machine or CI/CD provider if you want to push a new version of your app. Fly.io offers a remote builder service, which will build your Docker image on a dedicated machine on their infrastructure. This is especially useful if you have a large Docker image that takes a long time to build, and you don’t want to tie up your local machine. The CLI also offers the possibility to build your image locally if you prefer to do so.

The developer experience is somewhat different as most of the interaction happens locally via the flyctl command line tool. This is different from Render, where you would just push your changes to your Git repo to trigger the remote builds (which are also managed by Render). Honestly, I did not see a great difference in the developer experience between the two platforms, but I would add that I prefer the git push approach as it’s more in line with classic CI/CD workflows.

I like that Fly.io offers a Hobby tier with $5 worth of compute per month. Looking at their pricing site, this is roughly enough to cover a 1GB memory instance with 1 (shared) vCPU and 100GB of data transfer. They have also recently introduced GPU support, if you are looking into hosting deep learning models or other GPU-accelerated workloads.

This is the link of my deployment on Fly.io: https://paas-comparison.fly.dev/

Fly.io Dashboard

Fly.io Dashboard

Railway.app

The third and last provider wer are going to look at is Railway. Railway’s setup is quite similar to Render’s: you connect your GitHub repository, and they auto-detect your Dockerfile to start a build and deployment. The first deployment is not made public, so you first have to select to generate a public URL for your service. The deployment process is quite fast, and I did not have any issues with it, except that I forgot to listen to the PORT environment variable in my Dockerfile (choosing your own hard-coded port will not work). This was explained in the documentation, and I was able to resolve the issue shortly after. This is reflected in the final CMD line of my Dockerfile that needed to be slightly adjusted to listen to the PORT environment variable:

CMD ["sh", "-c", "uvicorn main:app --host 0.0.0.0 --port $PORT"]
Render Dashboard

Render Dashboard

The configuration file can be either a toml or json file and looks like this:

{
    "$schema": "https://railway.app/railway.schema.json",
    "build": {
        "builder": "DOCKERFILE",
        "dockerfilePath": "Dockerfile"
    },
    "deploy": {
        "numReplicas": 1,
        "sleepApplication": false,
        "restartPolicyType": "ON_FAILURE",
        "restartPolicyMaxRetries": 10
    }
}

I was a bit surprised that I couldn’t add environment variables or secrets to this file. Railway also does not support docker-compose-like multi-service deployments in a single file. I think this is a big advantage of Render’s approach, and I would have liked to see this feature in Railway as well.

Besides this, every push to master will trigger a new deployment, same as with others!

Railway’s hobby plan also includes $5 worth of credits per month. This will buy you a modest 300MB memory and 0.1 vCPU and very little network egress. As far as I could see, they will not limit or downscale your service if you don’t use it for a while, which is a big plus compared to Render.

Railway Prices

Railway Prices

See my app on Railway!

Pricing Comparison - Is it worth it?

In this table, I list the free-tier resource specs of all three platforms. The prices are in USD and are based on the information available on the respective websites at the time of writing. I am explicitly comparing the service offerings for container-based apps and not the language-native runtimes.

ServiceFree Tier
Render0.5 GB memory, 0.1 CPU
Fly.io1 GB memory, 1 CPU
Railway0.3 GB memory, 0.1 CPU

Conclusion

I appreciated the ease of deploying my apps with a few clicks on all three platforms. When I encountered issues (usually around setting environment variables properly or specifying correct ports), the documentation was quite helpful, and I was able to resolve the issues quickly. All three platforms show very similar approaches in how deployments are managed. They all offer development URLs with SSL certificates and the ability to manage custom domains. What stood out for me was Render’s approach of offering a docker-compose-like file to define multi-service deployments. Fly.io seems to offer the best value for money with their hobby tier, and Railway is a good alternative if you are looking for a more git-centric approach. Unfortunately, Render spinning down the free-tier after a while of inactivity is a significant downside for me, and I would not use it for any hobby projects.

Another commonality of the platforms is their steep prices if you are going beyond the free-tier and want to host multiple small projects. In general, PaaS vendors are in a tough spot with their offering and pricing: they are essentially (at least in the beginning) public cloud resellers; for example, Render uses a mix of AWS and GCP under the hood. Their abstractions and processes on top of these clouds have to become so well managed and efficient that they can offer a competitive price to the end-user in the long run.

For hobby developers, these platforms might, therefore, be too expensive. I recommend looking into self-hosting and renting your own VPS - at Hetzner charges around 30€ per month for a dedicated server with 32GB memory and Intel i7 6700 CPU - at Render you will have to pay roughly 10x that price for the same resources.

But the economics are different if you are a startup looking for a hosting partner. In this case, it can make sense to go with them, and you need to balance the following trade-off: do you hire dedicated personnel to manage your infrastructure or do you pay a premium for the convenience of a PaaS?

Ultimately, the customer middle-ground between budget-conscious hobbyist and VC-backed startup is quite small: if you have bigger budgets, you will gain a lot of flexibility and cost-savings by going lower level and managing your infrastructure. However, there is certainly an appealing argument to be made for expensive PaaS if you want to keep your engineering teams small and laser-focused on product work. When $$$ is abundant, you want to invest in product work and not burden the team with managing infra that is not vital to your company’s core competencies.

If you decide to go with a self-hosted PaaS, you can look into offerings like Coolify, CapRover, faasd, or Dokku. I might write a comparison of these in the future.

What’s your experience with PaaS providers? Are you using them or do you prefer different approaches? Let me know in the comments below!