Deploying Astro Applications to the Cloud
Learn how to deploy your Astro applications to popular cloud platforms.

The first deploy is a small celebration. The hundredth is a routine you barely notice — and that’s the goal. This guide walks through the platforms, environment hygiene, and continuous deployment patterns that turn shipping into the boring part of your week.
Choosing a Platform
There is no objectively best host. Pick the one whose defaults match your shape of app.
- Vercel — a first-class Astro adapter, edge functions, and on-demand rendering baked in. Almost always the right answer for marketing and content sites.
- Netlify — strong static + serverless story, generous free tier.
- AWS Amplify / Lambda — full control, the highest ceiling, the steepest config.
- Cloudflare Pages + Workers — global edge, low cost, fast cold starts.
- Self-hosted (Docker on Fly.io / Railway / Render) — when you need a long-running process or a database in the same box.
If you are unsure, ship on Vercel and migrate later. The migration is rarely the hard part — premature platform decisions are.
Vercel Deployment
Connect a Git repo, push, and Vercel infers the rest. The build command, output directory, and Node version come from astro.config.mjs and your package.json.
# install the CLI for local previews
npm i -g vercel
# link a local project to a Vercel project
vercel link
# deploy a preview
vercel
# promote to production
vercel --prod
For preview deployments per pull request, you don’t need to do anything — Vercel creates one automatically and posts the URL on the PR.

Environment Variables
Three layers, three audiences:
- Build-time public —
PUBLIC_*keys are inlined into the client bundle. Never put secrets here. - Build-time server — read inside
getStaticPaths, server endpoints, and.astrocomponent scripts. Stay on the server. - Runtime server — needed at request time only; load lazily so misconfiguration crashes early in dev, not silently in prod.
# .env — never commit this file
DATABASE_URL=postgres://...
STRIPE_SECRET_KEY=sk_live_***
# .env.example — commit this with empty values
DATABASE_URL=
STRIPE_SECRET_KEY=
Pair every environment access with a startup check:
const env = import.meta.env;
const required = ["DATABASE_URL", "STRIPE_SECRET_KEY"];
for (const key of required) {
if (!env[key]) {
throw new Error(`Missing env var: ${key}`);
}
}
A misconfigured deploy that boots and 500s is worse than one that refuses to start.
Continuous Deployment
The pattern that has aged best:
maindeploys to production on green CI.- Every pull request gets a preview URL with isolated data.
- Reverts are a one-click
git revert— not a panicked redeploy.
A minimal GitHub Actions workflow that runs lint, type-check, and tests before letting Vercel ship:
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test -- --coverage
Caching & On-Demand Rendering
Astro is static-first: pages are pre-rendered to HTML at build time and served straight from a CDN. Most performance regressions come from rendering at request time when you didn’t need to. Decide page by page whether content can be baked at build time or must be fresh per request.
For mostly-static content sites, leaning on the static build is the simplest win. When a page truly needs request-time data, opt it out of pre-rendering:
---
// src/pages/blog/[slug].astro
export const prerender = false; // render on each request
const post = await fetchPost(Astro.params.slug);
---
<Article post={post} />
To refresh content after a CMS publish, trigger a rebuild via a deploy hook, or set Cache-Control headers on an on-demand endpoint:
// src/pages/api/revalidate.ts
export const prerender = false;
export async function POST({ request }: { request: Request }) {
const { tag } = await request.json();
await fetch(import.meta.env.DEPLOY_HOOK_URL, { method: "POST" });
return Response.json({ revalidated: true, tag });
}
Monitoring and Logging
You can’t operate what you can’t see. The minimum viable telemetry:
- Structured logs with a
request_idyou can grep across services. - Error reporting (Sentry, Highlight, or whatever your team prefers).
- Web vitals piped to a real dashboard, not a one-off spreadsheet.
- Uptime checks from at least two regions.
import { onCLS, onINP, onLCP } from "web-vitals";
function report(metric) {
fetch("/api/vitals", {
method: "POST",
body: JSON.stringify(metric),
keepalive: true,
});
}
onCLS(report);
onINP(report);
onLCP(report);
Scaling Your Application
Before you scale, measure. The shape of your traffic decides the shape of your infrastructure.
- Read-heavy → CDN + ISR, push static where you can.
- Write-heavy → connection-pool the database (PgBouncer, Prisma’s pool), shard on a stable key.
- Compute-heavy → push slow work to a queue, return fast.
A read replica costs less than a rewrite. A queue costs less than a refactor.
Closing Thoughts
Deployment is plumbing, and good plumbing is invisible. Aim for “boring”: predictable previews, fast rollbacks, and the same set of steps every time. The team you build it with — and the future-you who has to debug it on a Saturday — will thank you for the restraint.
About the author
The Plake Team
Writes long-form essays on craft, software, and the slow web. Curates the Sunday digest read by 88,000+ readers.
Read moreKeep reading








