Deployment
Website Rebuild with Astro
Chapter 11 · Deployment
Chapter 1's output: 'server' plus @astrojs/node already made this deployment story real from day one — this chapter just runs it in production.
Building & Running the Standalone Server
The Node adapter's 'standalone' mode produces a real, self-contained HTTP server — dist/server/entry.mjs — listening on its own port (4321 by default), no different in role from Rails' own Puma process or Django's own gunicorn.
Secrets: The Plainest Story of All Five
AUTH_SECRET is what Auth.js uses to sign sessions — the direct equivalent of Django's SECRET_KEY, Laravel's APP_KEY, or Rails' master.key. Astro's own version of this story is the simplest of the five: a plain .env file, matching Next.js, Django, and Laravel's own convention exactly — not Rails' own reversed, encrypted-commit approach.
Keeping the Process Alive: PM2
A standalone Node process needs a real supervisor to restart it on crash or reboot — the same underlying need gunicorn has in the Django rebuild's own deployment chapter, just answered here with PM2 instead of a systemd service unit.
Apache's Own Third Mechanism: mod_proxy_http
mod_proxy_http forwards requests over plain HTTP to any backend server — genuinely different from Laravel's own mod_proxy_fcgi (a FastCGI-specific protocol talking to a PHP-FPM process pool) and from Rails' own mod_passenger (embedding process management directly into Apache itself). Astro's Node server is just an ordinary HTTP server, so plain HTTP proxying is the natural fit — the same underlying idea Django's own gunicorn-behind-a-reverse-proxy architecture uses, but here reusing the site's own already-live Apache the way Laravel and Rails did, rather than standing up nginx from nothing the way Django's own Chapter 11 had to.
Migrations in Production
Five Deployment Stories, Compared
| Next.js | Django | Laravel | Rails | Astro | |
|---|---|---|---|---|---|
| App server process | Node (PM2) | gunicorn | PHP-FPM | Passenger-embedded | Node adapter (PM2) |
| Reverse proxy | nginx (new) | nginx (new) | Apache (already live) | Apache (already live) | Apache (already live) |
| Apache module used | N/A | N/A | mod_proxy_fcgi | mod_passenger | mod_proxy_http |
| Secrets model | Plain .env | Plain .env | Plain .env | Encrypted, committed | Plain .env |
mod_proxy_fcgi, mod_passenger, mod_proxy_http) achieving the same real goal. Django and Next.js, needing a genuinely different runtime Apache had no existing path for, stood up nginx from nothing instead.
Hands-On Exercises
Build the production server (npm run build, then run dist/server/entry.mjs directly), and confirm it serves the site correctly on its own configured port.
📄 View solutionConfigure PM2 to run the server, then simulate a crash (kill the process directly) and confirm PM2 restarts it automatically.
📄 View solutionConfigure the Apache VirtualHost with mod_proxy_http reverse-proxying to the Node server, and confirm the site is reachable through Apache's own domain rather than the raw Node port directly.
📄 View solutionChapter 11 Quick Reference
dist/server/entry.mjs— the Node adapter's own standalone production server- Plain
.env— the simplest secrets story of all five siblings, matching Next.js/Django/Laravel - PM2 — the same underlying need as Django's own
gunicornsupervisor mod_proxy_http— a third distinct Apache mechanism, genuinely different frommod_proxy_fcgiandmod_passenger- Three "modernize in place" stories — Laravel, Rails, and Astro all reuse the site's own live Apache; Django and Next.js stood up nginx from nothing
- Next chapter: Capstone — A Complete, Working Flexible-Routing Site