What is Platform as a Service? The Sweet Spot Between "Too Easy" and "Too Much Work"
Platform as a Service sits between raw servers and fully managed software. You push code, the platform runs it. No Nginx config, no OS patches, no 3 AM server alerts. Here is what PaaS actually gives you, what it costs you in control, and how to decide if it is the right layer for your application.
There is a moment every developer hits when they realize their application actually has users. Real users. People who are going to notice if the thing goes down.
Up until that moment, it does not matter where you are running your code. Local machine, a friend's server, some janky free tier — whatever. But the second you have real users depending on your app, you need real infrastructure. And the question becomes: how much infrastructure do you actually want to own?
I have been on both ends of this. I spent years at SolidShellSecurity managing physical servers for clients — every patch, every config, every 3 AM alert was on us. I know exactly what it costs in time and attention to run infrastructure properly. I also watched a Discord bot I was working on get deployed to Heroku — a Platform as a Service — and just... run. No server setup. No Nginx config. No firewall rules. Just push code, get a URL, done.
Both experiences were real. Both were appropriate for what they were. Understanding why is what this article is about.
What Platform as a Service Actually Is
Platform as a Service — PaaS — is the middle layer in the cloud computing stack. The provider manages the underlying infrastructure and runtime environment. You provide the application code. Everything above the platform boundary is yours; everything below it is someone else's problem.
The three layers in plain terms:
SaaS → You use software (Gmail, Slack, Ghost.io, Notion)
PaaS → You deploy code (Heroku, Railway, Render, Fly.io)
IaaS → You manage servers (AWS EC2, DigitalOcean, Hetzner)
On IaaS, you get a virtual machine with root access and you are responsible for everything: the OS, the web server, the database, the deployment pipeline, the monitoring, the security patches. All of it. We covered what that actually costs in attention in What is Infrastructure as a Service.
On SaaS, the vendor handles literally everything and you just use the product. You do not deploy code at all.
PaaS is the explicit middle ground. You write and deploy application code. The platform handles:
- Operating system provisioning and patching
- Runtime installation and version management
- Web server configuration (Nginx, load balancer)
- SSL/TLS certificate provisioning and renewal
- Horizontal scaling based on traffic
- Health checking and automatic container restarts
- Log aggregation and routing
- Networking between services
You handle:
- Your application code
- Environment variables and secrets
- Database migrations
- Background job workers
- Domain name configuration
That division of responsibility is the entire value proposition of PaaS. The platform takes the operational work off your plate so you can focus on building the application.
The Discord Bot That Outgrew Heroku
In 2018 and 2019, I was helping with a Discord RPG bot written in Python. The bot used PostgreSQL for player data and was initially hosted on Heroku. The developer needed help with performance and features as the bot scaled from a few hundred servers to tens of thousands.
The deployment workflow on Heroku was frictionless:
# Heroku deployment — the entire operational experience
git add .
git commit -m "fix: player stat calculation"
git push heroku main
# Heroku builds the container, runs health checks, deploys
# Total time: about 2 minutes
# Operational knowledge required: git
No server. No SSH. No Nginx configuration. No systemd unit files. The Heroku CLI and a git push was the entire deployment pipeline.
The bot scaled from 3 servers to 50,000 servers, then to 100,000+ servers. During that growth, every hour spent on the application itself — performance improvements, new gameplay features, database query optimization — was directly valuable. Every hour not spent configuring servers was reclaimed for building. That trade was correct for that stage.
Eventually the economics changed. When you are paying Heroku's per-dyno pricing at scale, a VPS handles equivalent load for a fraction of the cost. That migration is a different story — and it is when understanding IaaS becomes necessary. But the early phase on PaaS was genuinely the right call.
The Platforms Worth Knowing
Heroku — The Pioneer
Heroku launched in 2007 and defined what PaaS meant for a generation of developers. You connect your GitHub repository, Heroku detects your runtime from a Procfile or buildpack, builds a container, and runs it. No configuration required.
# Procfile — the entire Heroku deployment configuration
web: node dist/index.js
worker: node dist/workers/queue-processor.js
release: npm run db:migrate
The release process type runs on every deployment before traffic switches over. Database migrations happen automatically before the new code goes live.
Heroku's pricing model changed significantly after Salesforce acquired them and then eliminated the free tier in 2022. The cheapest dyno now starts at $5/month and scales from there. For small applications or prototypes, the value equation has shifted. For established teams who built workflows around Heroku's DX and addons ecosystem, the cost at modest scale is still often justified by operational savings.
The addons ecosystem is Heroku's other major value: managed PostgreSQL, Redis, email services, monitoring, log drains — all provisioned through heroku addons:create and connected to your app through environment variables automatically.
Railway — The Modern Heroku
Railway launched in 2020 and has become the PaaS recommendation for most new projects. The developer experience is polished, the pricing is usage-based (you pay for what you use rather than per-dyno), and the deployment model feels current rather than carrying fifteen years of technical decisions.
# railway.toml — optional, Railway auto-detects most configs
[build]
builder = "nixpacks"
[deploy]
startCommand = "node dist/index.js"
healthcheckPath = "/health/live"
healthcheckTimeout = 30
restartPolicyType = "on-failure"
restartPolicyMaxRetries = 3
Railway provides databases (PostgreSQL, MySQL, MongoDB, Redis) as first-class services within your project. Environment variable interpolation means your API service can reference ${{Postgres.DATABASE_URL}} and Railway injects the connection string automatically. No copying connection strings between services.
The GitHub integration deploys automatically on every push to main. Preview deployments on pull requests let your team review changes before they merge. The platform generates a URL for each PR deployment.
# Railway CLI — the entire operational interface
railway login
railway init
railway up # Deploy current directory
railway logs # Stream logs
railway shell # Shell into running container
railway run node dist/scripts/seed.js # Run a one-off command
For new projects in 2025, Railway is where I would start before scaling to Heroku-level needs or dropping down to IaaS.
Render — Predictable Pricing
Render occupies similar territory to Railway with a different pricing model — fixed plans rather than usage-based. If predictable monthly billing matters to your budgeting, Render's structure is cleaner to reason about.
Render's standout feature is zero-downtime deploys by default. The platform spins up the new version, runs health checks, and only cuts over traffic once the new container passes them. The old container keeps running until the new one is healthy. For production deployments where downtime is unacceptable, this matters.
# render.yaml — Infrastructure as Code for Render
services:
- type: web
name: my-api
env: node
buildCommand: npm ci && npm run build
startCommand: node dist/index.js
healthCheckPath: /health/live
envVars:
- key: NODE_ENV
value: production
- key: DATABASE_URL
fromDatabase:
name: my-postgres
property: connectionString
scaling:
minInstances: 1
maxInstances: 5
targetMemoryPercent: 80
targetCPUPercent: 60
databases:
- name: my-postgres
databaseName: myapp
plan: starter
The render.yaml file defines your entire stack: services, databases, environment variables, scaling rules. Commit it to your repository and the infrastructure is reproducible.
Fly.io — Edge and Global Distribution
Fly.io takes a different approach: rather than running your containers in one region, it runs them close to your users globally. Deploy once and Fly distributes your containers to regions automatically based on where your traffic originates.
# fly.toml
app = "my-api"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
[http_service.concurrency]
type = "requests"
hard_limit = 200
soft_limit = 150
[[vm]]
memory = "512mb"
cpu_kind = "shared"
cpus = 1
[checks]
[checks.health]
interval = "30s"
timeout = "10s"
grace_period = "30s"
method = "GET"
path = "/health/live"
protocol = "https"
# Fly CLI
fly launch # Initialize app from Dockerfile or buildpack
fly deploy # Deploy
fly logs # Stream logs
fly scale count 3 # Scale to 3 instances
fly scale memory 1024 # Scale memory
fly ssh console # Shell into running machine
fly postgres create # Provision managed PostgreSQL
fly secrets set KEY=value # Set environment variables
auto_stop_machines = true lets Fly shut down machines when there is no traffic and start them back up on demand. For applications with variable traffic or staging environments, this dramatically reduces cost — you only pay for compute when the machine is actually handling requests.
Vercel and Netlify — Frontend PaaS
Vercel and Netlify are PaaS products focused specifically on frontend applications and serverless functions. If you are building a Next.js application, Vercel is the canonical deployment target — it is built by the same team.
# Vercel deployment — literally one command
npx vercel
# or
git push # auto-deploys if connected to GitHub
Preview deployments for every pull request. Global CDN for static assets. Serverless functions for API routes. Edge Functions that run on Vercel's global network with sub-millisecond cold starts. Image optimization. Incremental Static Regeneration.
For teams building with Next.js, React, or Vue, Vercel and Netlify occupy a specific PaaS niche where the platform is deeply integrated with the framework and the DX is exceptional.
What PaaS Does Not Handle
The operational simplicity of PaaS has limits. Understanding where the platform's responsibility ends is as important as understanding what it covers.
Databases. PaaS platforms typically provide managed databases as an add-on (Heroku Postgres, Railway's PostgreSQL service, Render's managed databases), but the database itself is its own service with its own operational considerations: connection pooling, backup policies, query performance, index management. The platform provisions it; you run your migrations and tune your queries.
Background jobs. Long-running workers, cron jobs, and queue processors are separate processes. You define them separately from your web server (as a worker process type in a Procfile, a separate service in Railway, etc.) and they run on their own compute. Fly.io scheduled jobs require separate Machine definitions. The platform runs them, but you define and maintain them.
Custom runtimes. If your application requires a specific system library, a custom binary, or a tool not in the standard buildpack, you need a Dockerfile. Every platform supports Docker — and when you use Docker, you are closer to IaaS than you think in terms of responsibility. You own the Dockerfile and everything in it.
Networking between services. Some platforms route traffic between services on private networks automatically. Others require explicit configuration. If your API needs to reach a database on the same platform, verify how the platform handles service discovery before assuming it works automatically.
Large file storage. PaaS platforms run ephemeral containers. Files written to the container filesystem disappear when the container restarts. For file uploads, generated PDFs, user avatars — you need object storage (AWS S3, DigitalOcean Spaces, Cloudflare R2) separate from the PaaS deployment.
Deploying a Node.js API on Railway
A complete walkthrough of deploying a production application:
// src/index.ts
import express from 'express';
import { Pool } from 'pg';
const app = express();
const port = parseInt(process.env.PORT ?? '3000');
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
ssl: process.env.NODE_ENV === 'production'
? { rejectUnauthorized: true }
: false,
});
app.use(express.json());
app.get('/health/live', (req, res) => {
res.json({ status: 'alive', timestamp: new Date().toISOString() });
});
app.get('/health/ready', async (req, res) => {
try {
await pool.query('SELECT 1');
res.json({ status: 'ready', database: 'connected' });
} catch (err) {
res.status(503).json({ status: 'not ready', database: 'disconnected' });
}
});
app.get('/api/products', async (req, res) => {
try {
const result = await pool.query(
'SELECT id, name, slug, price, stock FROM products WHERE status = $1 ORDER BY created_at DESC LIMIT $2',
['active', parseInt(req.query.limit as string ?? '20')]
);
res.json({ data: result.rows });
} catch (err) {
console.error('Database error:', err);
res.status(500).json({ error: 'Internal server error' });
}
});
app.listen(port, () => {
console.log(`Server listening on port ${port}`);
});
# Dockerfile
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig*.json ./
RUN npm ci
COPY src/ src/
RUN npm run build && npm prune --production
FROM node:22-alpine AS production
WORKDIR /app
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 -G nodejs
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
USER nodejs
ENV NODE_ENV=production
EXPOSE 3000
CMD ["node", "dist/index.js"]
# railway.toml
[build]
dockerfilePath = "Dockerfile"
[deploy]
healthcheckPath = "/health/ready"
healthcheckTimeout = 30
restartPolicyType = "on-failure"
restartPolicyMaxRetries = 3
Railway detects the railway.toml, builds the Dockerfile, and deploys. The DATABASE_URL environment variable is set automatically when you connect a Railway PostgreSQL service. The health check ensures traffic only routes to the container once it can connect to the database.
The PaaS Cost Calculation
The honest comparison between PaaS and IaaS is a total cost calculation that includes engineering time, not just infrastructure bills.
A $24/month DigitalOcean VPS handles more traffic than a $50/month Heroku setup. That is true. What that comparison omits: the hours spent setting up and maintaining the VPS. If a developer costs $100/hour loaded, and the VPS requires 2 hours of setup and 1 hour of maintenance per month, the VPS costs $124/month in the first month and $124/month ongoing. The Heroku setup costs $50/month and 20 minutes of developer time.
At small scale, PaaS is almost always the economically correct choice when you value developer time honestly. The crossover happens as scale increases — both because infrastructure costs grow and because the engineering investment in IaaS gets amortized across a larger base.
The practical decision for most projects: start on PaaS. Move to IaaS when the cost difference at your current scale justifies the operational overhead you are taking on. The Discord bot migration from Heroku to a VPS happened because the scale was real, the cost difference was real, and the team had the capacity to handle the infrastructure. At the beginning, Heroku was correct. At the end, it was not.
Serverless: The Evolution of PaaS
Serverless computing is a model where you deploy individual functions rather than long-running processes. The platform allocates compute resources for each invocation and charges per execution rather than per running instance.
AWS Lambda, Vercel Edge Functions, Cloudflare Workers, and Fly.io Machines with auto_stop are all serverless or serverless-adjacent models. The appeal: you pay nothing when your code is not running, and scaling is automatic.
The tradeoffs: cold starts (a function that has not run recently takes longer to respond while the container initializes), execution time limits (Lambda has a 15-minute maximum), limited local state (no in-process caching across invocations without external stores), and complexity around database connections (each function invocation may open a new connection — use a connection pooler like PgBouncer or a serverless-optimized database).
Serverless makes sense for workloads that are genuinely event-driven and variable: webhooks, scheduled batch jobs, image processing triggered by uploads, APIs with highly variable traffic. For a consistently-trafficked web API where you are handling hundreds of requests per second all day, a long-running process on PaaS or IaaS is simpler and often cheaper.
When PaaS Is the Wrong Choice
PaaS is not the answer for everything.
Stateful applications with complex local storage needs. If your application writes to the filesystem and expects those files to persist, PaaS's ephemeral containers require rearchitecting to use object storage. That rearchitecting is worth doing — it is the correct design — but it is work.
Highly custom runtime requirements. Specific Linux kernel parameters, custom kernel modules, particular versions of system libraries — PaaS sandboxes you away from the OS. Dockerfile-based deployments give you more control but still within the platform's constraints.
Cost at significant scale. Once you are running many containers continuously at high traffic volumes, the per-container PaaS markup over raw IaaS can become significant. This threshold varies dramatically by platform and usage pattern.
Applications requiring specific geographic placement. If your application needs to run in a specific data center for latency or compliance reasons that your PaaS provider does not offer, IaaS gives you the location control PaaS may not.
For the majority of web applications, APIs, and background workers at small to medium scale: PaaS is correct. The operational work it eliminates is genuinely expensive work, and the platforms in 2025 are mature enough that the trade is straightforward. The friction comes when you grow past what the platform handles naturally or when your requirements are specific enough that the platform's opinions conflict with yours.
PaaS for the CoderOasis Stack
CoderOasis itself runs on a VPS rather than PaaS — the scale is stable enough that the cost difference is real and the operational overhead is manageable. Ghost CMS, PostgreSQL, Nginx, automated backups, TLS certificates — all managed on a single VPS. The What is IaaS article walks through exactly what that setup looks like and what it costs in ongoing maintenance.
But if I were starting a new project from scratch — a new API, a new web application, something with uncertain scale trajectory — I would start on Railway or Render. Push code, get a URL, connect a database, iterate on the application. Move to IaaS if and when the economics justify it. Start with the layer that eliminates operational overhead until the cost of that overhead becomes real.
That is the correct sequence. Not "always PaaS" or "always IaaS." Appropriate infrastructure for the stage of the project.
Environment Variables and Secrets on PaaS
PaaS platforms are built around the twelve-factor app methodology, which specifies that configuration lives in the environment, not in code. Every platform provides a way to set environment variables that are injected into running containers.
# Railway CLI
railway variables set DATABASE_URL="postgresql://user:pass@host:5432/db"
railway variables set JWT_ACCESS_SECRET="your-32-character-minimum-secret"
railway variables set NODE_ENV="production"
railway variables list
# Vercel CLI
vercel env add DATABASE_URL production
vercel env ls
# Fly.io CLI
fly secrets set DATABASE_URL="postgresql://..."
fly secrets set JWT_ACCESS_SECRET="..."
fly secrets list
Never commit secrets to your repository. Never hardcode connection strings in your application code. The pattern on every PaaS platform is identical: set secrets through the platform's UI or CLI, read them in your application from process.env (Node.js), os.environ (Python), or whatever the language equivalent is.
For secrets that multiple services need (like a shared JWT secret or a database URL), PaaS platforms handle this differently. Railway lets you reference another service's environment variables directly. Render has shared environment groups. Fly.io has secrets that scope to the application. The mechanism varies; the principle is the same.
Database Migrations on PaaS
The release phase — running migrations before the new code goes live — is critical to zero-downtime deployments. Every PaaS platform has a mechanism for this.
# Heroku Procfile
web: node dist/index.js
worker: node dist/workers/queue.js
release: node dist/database/migrate.js
# Fly.io fly.toml
[deploy]
release_command = "node dist/database/migrate.js"
// Railway — set as a service command or pre-deploy hook
// package.json
{
"scripts": {
"start": "node dist/index.js",
"migrate": "node dist/database/migrate.js"
}
}
The release command runs in a one-off container before the new version of your app receives traffic. If migrations fail, the deployment is aborted and the old version continues serving requests. This makes migration failures safe — a bad migration does not cause your application to go down, it just stops the deployment.
Write migrations that are backward compatible with the previous version of your code. If you rename a column, add the new column first, deploy the code that reads both old and new, then drop the old column in a second migration. This allows zero-downtime deploys even for schema changes.
Logs, Metrics, and Observability on PaaS
PaaS platforms aggregate logs from all your containers and surface them through a unified interface. The operational visibility is genuinely better than tailing logs on individual VMs.
# Railway
railway logs # Stream all services
railway logs --service api # Filter to specific service
railway logs --deployment abc123 # Logs for a specific deploy
# Fly.io
fly logs # Stream all instances
fly logs --instance abc123 # Specific instance
# Heroku
heroku logs --tail # Stream logs
heroku logs --tail --dyno web.1 # Specific dyno
heroku logs -n 500 # Last 500 lines
For production-grade observability, most PaaS platforms support log drains — forwarding your logs to an external service:
# Heroku log drain to Papertrail
heroku drains:add syslog+tls://logs.papertrailapp.com:12345
# Fly.io ships to any syslog endpoint or directly to Grafana Loki
# Configure in fly.toml or through the Fly dashboard
Your application should emit structured JSON logs:
// Structured logging — parse-friendly for log aggregation services
const logger = {
info: (msg: string, meta: Record<string, unknown> = {}) =>
console.log(JSON.stringify({ level: 'info', msg, ...meta, ts: new Date().toISOString() })),
error: (msg: string, meta: Record<string, unknown> = {}) =>
console.error(JSON.stringify({ level: 'error', msg, ...meta, ts: new Date().toISOString() })),
warn: (msg: string, meta: Record<string, unknown> = {}) =>
console.warn(JSON.stringify({ level: 'warn', msg, ...meta, ts: new Date().toISOString() })),
};
// Usage
logger.info('Request completed', {
method: req.method,
path: req.path,
status: res.statusCode,
duration: Date.now() - startTime,
userId: req.user?.id,
});
logger.error('Database error', {
error: err.message,
query: 'SELECT * FROM products',
userId: req.user?.id,
});
JSON logs are searchable, filterable, and parseable by every log aggregation service. Free-text log lines require regex parsing. JSON lines are directly queryable.
PaaS and Docker
Every major PaaS platform supports Docker deployment. When you provide a Dockerfile, the platform builds it and runs the resulting image. This gives you more control than a buildpack while keeping the operational benefits of PaaS.
# Production Dockerfile for a Node.js API
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json tsconfig*.json ./
RUN npm ci
COPY src/ src/
RUN npm run build && npm prune --production
FROM node:22-alpine AS production
RUN apk add --no-cache tini
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001 -G nodejs
WORKDIR /app
COPY --from=builder --chown=nodejs:nodejs /app/dist ./dist
COPY --from=builder --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --from=builder --chown=nodejs:nodejs /app/package.json .
USER nodejs
ENV NODE_ENV=production
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD wget -qO- http://localhost:3000/health/live || exit 1
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["node", "dist/index.js"]
The Dockerfile-based deployment on Railway, Render, or Fly.io means the same container image you test in CI runs in production. No buildpack magic, no surprises about what runtime versions the platform uses. The What is Docker article covers the full picture of container building and deployment patterns.
Scaling on PaaS
Horizontal scaling — adding more containers — is the primary lever on PaaS. Most platforms handle this automatically based on CPU or memory usage, or manually through configuration.
# Railway — scale via settings in dashboard or CLI
# Set replica count in service settings
# Fly.io
fly scale count 5 # Run 5 instances
fly scale count 1 # Scale back to 1
# Auto-scaling based on concurrency
# In fly.toml:
# [http_service.concurrency]
# type = "requests"
# hard_limit = 200 # Add instance when requests/instance > 200
# soft_limit = 150
# Heroku
heroku ps:scale web=3 # Scale to 3 web dynos
heroku ps:scale web=1 # Scale back
# Render — configure auto-scaling rules in the dashboard
# Min instances, max instances, CPU/memory thresholds
For stateless applications, horizontal scaling works without coordination between instances. For stateful applications — anything that stores session data or application state in process memory — you must externalize state to Redis or a database before horizontal scaling works correctly. Two instances with different in-memory session stores will lose sessions as requests route to different containers.
Choosing Between PaaS Providers
The decision between Railway, Render, Fly.io, Heroku, and Vercel comes down to a few practical questions.
What runtime and framework? Vercel is the natural choice for Next.js and edge-first frontends. Fly.io is the natural choice for latency-sensitive APIs that need global distribution. Railway and Render are general-purpose and work well for standard web APIs and workers.
What is your scaling pattern? If traffic is highly variable (burst traffic, scheduled batch processing, APIs that spike), Fly.io's machine auto-start/stop model or serverless approaches minimize cost during idle periods. If traffic is stable, fixed-pricing models on Render are easier to budget.
How important is preview deployments? Every platform supports them but the quality varies. Vercel's preview deployment experience is the best in the industry — unique URL per PR, easy sharing with stakeholders, automatic comment on the PR with the deployment URL.
What is the team's existing infrastructure? If you are already on AWS for other services, AWS Elastic Beanstalk or App Runner (both PaaS-like services) keep everything in one bill and one access control system. Migrating away from AWS for one service is often not worth the added operational complexity of multiple cloud accounts.
There is no wrong answer among the major platforms. They all deploy containers, they all handle TLS, they all provide logs and health checking. Pick one, deploy your application, and learn the operational model. The real learning happens when you hit the first constraint of the platform you chose.
PaaS Is Not a Permanent Answer
The trajectory for most applications: start on PaaS, learn your application's behavior under production load, then make an informed decision about whether to stay on PaaS or move to IaaS as scale demands.
The Discord bot I mentioned at the beginning eventually moved off PaaS. 100,000+ Discord servers, a constantly running long-lived process, PostgreSQL queries that had been tuned over two years of production experience, Redis caching that was critical to response times — at that scale, the economics of IaaS were compelling and the team had the capacity to manage it.
The move to IaaS was the right call at that point. The initial deployment to PaaS was also the right call at its point. The mistake is not in choosing one or the other — it is in choosing without thinking about what stage you are at and what problems you are actually trying to solve.
PaaS removes operational complexity. IaaS gives you maximum control. SaaS removes everything. The right layer depends on your team size, your application's requirements, your budget, and where you want your time to go. Most new projects belong on PaaS. Some stay there forever. Some grow into IaaS. A few never leave PaaS and that is completely fine — the goal is a working application, not a particular hosting arrangement.
The Future of PaaS
The category is actively evolving. Several trends are reshaping how PaaS works in 2025.
AI-assisted deployment. Platforms are integrating AI tooling for detecting configuration issues, suggesting scaling parameters based on traffic patterns, and identifying performance problems in deployed applications. Fly.io's machine management, Railway's usage analytics, and Render's performance insights all use data from your deployments to surface recommendations.
Edge computing integration. The line between traditional PaaS and edge computing platforms is blurring. Cloudflare Workers, Vercel Edge Functions, and Fly.io's machine placement all enable running code close to users globally. For Next.js applications that use Vercel, middleware and edge API routes run on Vercel's global edge network with sub-millisecond cold starts. This is PaaS at the edge — the platform abstracts away the infrastructure, you deploy code.
Infrastructure from code. Platforms like Pulumi and CDK let you define your entire cloud infrastructure in real programming languages — TypeScript, Python, Go — rather than YAML configuration files. Some PaaS platforms are moving toward this model: Railway's railway.toml, Fly.io's fly.toml, and Render's render.yaml are early versions of infrastructure-as-code integrated directly into the platform. The trajectory is toward defining infrastructure in the same repository as application code, with the platform responsible for interpreting and applying it.
Developer experience as competitive advantage. The PaaS market in 2025 is competing primarily on developer experience. Deployment speed, log quality, preview deployment UX, database management tooling, and CLI ergonomics are the real differentiators between platforms with similar underlying infrastructure. Railway and Fly.io have invested heavily here. The result is that modern PaaS deployment workflows are genuinely better than anything available five years ago.
The right question is not whether PaaS or IaaS is correct in the abstract. It is whether the operational work that IaaS demands is work your team should be doing right now, given what else you need to build. Most of the time, the answer is no. Deploy on PaaS. Build the product. Revisit the infrastructure decision when the evidence says you should.
Comparing PaaS to Self-Hosted Ghost
CoderOasis runs on a self-managed VPS — IaaS. Ghost CMS, Nginx, PostgreSQL, automated backups. I covered why I made that choice in Why I Switched from WordPress to Ghost. The short version: the operational overhead of a single-server VPS is low enough that the cost savings over Ghost.io (Ghost's managed SaaS/PaaS offering) are real at CoderOasis's scale.
Ghost.io is Ghost running as a PaaS — you push content, Ghost handles hosting, TLS, backups, updates. The price is $25-$199/month depending on traffic. My self-hosted Ghost on a VPS costs roughly $6/month in infrastructure. The difference is the time I spend maintaining the server — which is genuinely minimal but not zero.
For a publication just starting, Ghost.io is correct. Zero setup, zero maintenance, start publishing immediately. For a publication at CoderOasis's scale where the infrastructure is understood and the savings are meaningful: self-hosting is correct. The platform decision is not permanent. Ghost.io would be the right call if I stopped having time to maintain infrastructure. The VPS is the right call while I do.
That is the actual framework for the PaaS decision. Not "what does the industry recommend" but "does the value of what the platform removes justify what it costs relative to what you would do with the time and money saved." For most teams starting out and most teams operating at moderate scale: yes. For teams that have operational capacity and are at the scale where IaaS economics are compelling: maybe not.
Start where the evidence points. Move when the evidence changes.