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

npm run build node ./dist/server/entry.mjs

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

# .env — excluded via .gitignore DATABASE_URL=mysql://user:pass@localhost/website AUTH_SECRET=a-real-random-secret-value

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

npm install -g pm2 pm2 start ./dist/server/entry.mjs --name website-astro pm2 save pm2 startup

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

# Apache VirtualHost — the site's own already-running Apache <VirtualHost *:443> ServerName osztromok.com ProxyPreserveHost On ProxyPass / http://127.0.0.1:4321/ ProxyPassReverse / http://127.0.0.1:4321/ SSLEngine on SSLCertificateFile /etc/letsencrypt/live/osztromok.com/fullchain.pem SSLCertificateKeyFile /etc/letsencrypt/live/osztromok.com/privkey.pem </VirtualHost>
A third distinct Apache mechanism, not a repeat of Laravel's or Rails'
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

npx drizzle-kit migrate

Five Deployment Stories, Compared

Next.jsDjangoLaravelRailsAstro
App server processNode (PM2)gunicornPHP-FPMPassenger-embeddedNode adapter (PM2)
Reverse proxynginx (new)nginx (new)Apache (already live)Apache (already live)Apache (already live)
Apache module usedN/AN/Amod_proxy_fcgimod_passengermod_proxy_http
Secrets modelPlain .envPlain .envPlain .envEncrypted, committedPlain .env
Three "modernize in place" stories, one architecture
Laravel, Rails, and now Astro all reuse the site's own already-live Apache — three genuinely different mechanisms (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

Exercise 1

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 solution
Exercise 2

Configure PM2 to run the server, then simulate a crash (kill the process directly) and confirm PM2 restarts it automatically.

📄 View solution
Exercise 3

Configure 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 solution

Chapter 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 gunicorn supervisor
  • mod_proxy_http — a third distinct Apache mechanism, genuinely different from mod_proxy_fcgi and mod_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