Deploying a web app used to mean SSHing into a server, copying files, restarting Nginx, and hoping nothing broke. In 2026, the best-practice path is entirely different: push to a Git branch, a pipeline runs your tests, and the platform deploys automatically to a global CDN or container cluster. Getting this right from the start saves hours of future pain.
What changed in 2026
- Preview deployments are standard. Every PR gets its own live URL on Vercel, Netlify, and Railway — reviewers can click a link, not imagine what the change looks like.
- Managed Postgres is table stakes. Supabase, Neon, PlanetScale (Drizzle), and Railway all offer serverless Postgres that scales to zero — no DBA needed.
- Dockerfile + cloud run is the universal escape hatch. If a PaaS can't handle your workload, containerize and move to Cloud Run or Fly.io with minimal changes.
- Secrets management is solved. Every platform (Vercel, Render, AWS, GCP) has encrypted environment variables with per-environment scoping. Use them.
Choose your deployment target
| App type |
Recommended platform |
Alternative |
| Static site / SPA |
Vercel, Netlify, Cloudflare Pages |
S3 + CloudFront |
| Next.js / full-stack JS |
Vercel |
Railway, Render |
| Node/Python/Go API |
Render, Railway |
Cloud Run, Fly.io |
| Containerized backend |
Fly.io, Cloud Run |
AWS ECS, GCP Cloud Run |
| Complex microservices |
AWS ECS / GKE |
Self-managed K8s |
| Postgres database |
Supabase, Neon |
RDS, Railway Postgres |
Start with the highest abstraction that fits. Drop down only when you hit a real limit.
A CI/CD pipeline from scratch
# .github/workflows/deploy.yml
name: Deploy
on:
push:
branches: [main]
jobs:
test-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build
# Platform-specific deploy step
- uses: amondnet/vercel-action@v25
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
The pattern is the same on every platform: checkout → install → test → build → deploy. The last step changes; everything else is universal.
Environment variables done right
# Local development — .env.local (git-ignored)
DATABASE_URL=postgresql://localhost:5432/myapp
API_SECRET=dev-secret-not-real
# Production — set in platform dashboard, NEVER in code
# Vercel: Settings > Environment Variables
# Render: Service > Environment
# Railway: Project > Variables
Rules:
- Never commit secrets to git. Add
.env* to .gitignore immediately.
- Use different values for development and production — never share the prod DB URL locally.
- Use a
.env.example file with placeholder values so teammates know what variables exist.
Deploying a containerized app
# Multi-stage build — small final image
FROM node:22-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:22-alpine AS runner
WORKDIR /app
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
USER appuser
EXPOSE 8080
CMD ["node", "dist/server.js"]
# Build, push to GitHub Container Registry, deploy to Cloud Run
docker build -t ghcr.io/yourname/myapp:latest .
docker push ghcr.io/yourname/myapp:latest
gcloud run deploy myapp --image ghcr.io/yourname/myapp:latest --region us-central1
Post-deploy checklist
Common mistakes
Deploying directly to production without a test environment. Use preview deployments or a staging environment — production breakage is expensive.
Hardcoding database URLs. Checked into git, exposed in error messages, shared across environments. Always use environment variables.
No health check. Load balancers and orchestrators use health checks to know when to route traffic. Add a /health endpoint that returns 200 and checks DB connectivity.
Forgetting database migrations. Schema changes need to run before (or atomically with) the new code deployment. Most frameworks have a migration CLI; run it in the deploy pipeline.
Ignoring costs on serverless. Serverless scales to zero — and to thousands. Set budget alerts; a bug causing infinite retries can generate a large bill overnight.
What to skip
- FTP/SFTP deploys — Git-based CI/CD is standard; manual file uploads break repeatability.
- Self-managed SSL certificates — every platform and CDN handles this automatically; Let's Encrypt is the last resort fallback.
- Monolithic VMs for stateless APIs — containerized or serverless deploys are cheaper, more scalable, and simpler to operate.
FAQ
What is the cheapest way to deploy a side project?
Vercel free tier for frontend, Railway or Render free tier for backend, Supabase free tier for Postgres. All three together cost $0 for low-traffic projects.
When should I move off a PaaS to raw cloud?
When you hit a PaaS limitation (custom networking, specialized hardware, cost at high volume). Most apps never hit that ceiling.
Do I need Kubernetes?
Almost certainly not if you're asking this question. Cloud Run, Fly.io, or Render handle container orchestration without K8s complexity. Kubernetes is for teams with a dedicated platform/ops function.
How do I handle zero-downtime deployments?
Most managed platforms do blue-green or rolling deploys by default. For self-managed containers, configure health checks and a rolling update strategy in your orchestrator.
Where to go next