ในบทความนี้
- 🐕 Code เขียนเสร็จแล้ว... แล้วจะเอาขึ้นที่ไหน?
- 📊 Hosting Options — เปรียบเทียบทุกแบบ
- 1️⃣ Static Hosting — Vercel / Netlify / Cloudflare Pages
- 2️⃣ PaaS — Railway / Render / Fly.io
- 3️⃣ VPS — Virtual Private Server
- 4️⃣ Reverse Proxy — Nginx vs Caddy
- 5️⃣ SSL/TLS — HTTPS ที่ทุกเว็บต้องมี
- 6️⃣ Domain Management — DNS
- 7️⃣ CDN — Content Delivery Network
- 8️⃣ Deployment Strategies — วิธี Deploy ที่ไม่พัง
- 9️⃣ Full Production Setup — จากศูนย์ถึง Production
- 🔟 Monitoring — รู้ก่อนที่จะพัง
- 🛡️ Security Checklist — ก่อนจะ Go Live
- 🗺️ Decision Flowchart — เลือก Hosting ยังไง?
- 🔑 Key Takeaways
In this post
- 🐕 The Code Is Finished... So Where Does It Go?
- 📊 Hosting Options — Every Kind Compared
- 1️⃣ Static Hosting — Vercel / Netlify / Cloudflare Pages
- 2️⃣ PaaS — Railway / Render / Fly.io
- 3️⃣ VPS — Virtual Private Server
- 4️⃣ Reverse Proxy — Nginx vs Caddy
- 5️⃣ SSL/TLS — The HTTPS Every Site Must Have
- 6️⃣ Domain Management — DNS
- 7️⃣ CDN — Content Delivery Network
- 8️⃣ Deployment Strategies — Shipping Without Breaking Things
- 9️⃣ Full Production Setup — From Zero to Production
- 🔟 Monitoring — Know Before It Breaks
- 🛡️ Security Checklist — Before You Go Live
- 🗺️ Decision Flowchart — How Do You Pick a Host?
- 🔑 Key Takeaways
🐕 Code เขียนเสร็จแล้ว... แล้วจะเอาขึ้นที่ไหน?
ตัวอย่าง code:
Developer ที่เริ่มต้น:
"เว็บเสร็จแล้ว! ทำยังไงให้คนอื่นเข้าได้?"
- 🤔 ซื้อ server เอง?
- 🤔 ใช้ Vercel/Netlify?
- 🤔 เช่า VPS?
- 🤔 ไป AWS/GCP?
- 🤔 Domain ยังไง? HTTPS ยังไง?
❌ เลือกผิด = จ่ายแพง, ช้า, พังบ่อย
✅ เลือกถูก = ประหยัด, เร็ว, scale ได้
โพสต์นี้จะพาไปรู้จักทุกทางเลือก
ตั้งแต่ free tier ไปจนถึง enterprise-grade
📊 Hosting Options — เปรียบเทียบทุกแบบ
ตัวอย่าง code:
| แบบ | ราคาเริ่มต้น | Control | Scale | ดูแลเอง | เหมาะกับ |
|---|---|---|---|---|---|
| Static Host (Vercel/Netlify) | $0 | ต่ำ | อัตโนมัติ | ไม่ต้อง | Static/SPA, JAMstack |
| PaaS (Railway/Render/Fly.io) | $0-7/mo | ต่ำ-กลาง | อัตโนมัติ | น้อย | Web apps, API, MVP |
| VPS (DO/Linode/Hetzner) | $4-6/mo | สูง | manual | ปานกลาง | custom setup |
| Cloud (AWS/GCP/Azure) | $0-????? | สูงมาก | อัตโนมัติ | เยอะ | Enterprise |
| Bare Metal | $50+/mo | สูงสุด | manual | เยอะมาก | High perf, ML, Gaming |
- Side project / portfolio → Vercel/Netlify (ฟรี)
- Startup MVP → Railway/Render (ง่าย, ถูก)
- ต้อง customize เยอะ → VPS (control ดี, ราคาดี)
- Enterprise / scale → Cloud (AWS/GCP/Azure)
- ML training / gaming → Bare Metal / GPU Cloud
1️⃣ Static Hosting — Vercel / Netlify / Cloudflare Pages
ทำไมถึงฟรี?
ตัวอย่าง code:
Traditional Hosting:
💸 Server ต้องทำงานตลอด = ต้องจ่ายตลอด
Static Hosting:
💰 แค่เก็บไฟล์ + ส่งผ่าน CDN = ถูกมาก (ฟรีได้)
Vercel vs Netlify vs Cloudflare Pages
ตารางนี้ช่วยให้เห็นเร็วมากว่าแต่ละแพลตฟอร์มเด่นคนละมุม: Vercel เหมาะกับ Next.js, Netlify ใช้ง่ายสำหรับทีมเล็ก, ส่วน Cloudflare Pages เด่นเรื่อง edge และต้นทุน bandwidth.
รันคำสั่งเหล่านี้เพื่อ deploy:
| Vercel | Netlify | CF Pages | |
|---|---|---|---|
| จุดเด่น | Next.js best-in-class | ง่ายที่สุด | เร็วที่สุด |
| Free BW | 100 GB/mo | 100 GB/mo | Unlimited |
| Free Builds | 6000 min/mo | 300 min/mo | 500 builds/mo |
| Serverless | ✅ Edge+Node | ✅ Functions | ✅ Workers |
| Edge Runtime | ✅ v8 | ✅ Deno | ✅ v8 |
| Git Integration | GitHub/GitLab/Bitbucket | GitHub/GitLab/Bitbucket | GitHub/GitLab |
| Analytics | ✅ (paid) | ✅ (paid) | ✅ (free basic) |
| Pro Price | $20/mo/seat | $19/mo/seat | $5/mo (paid) |
| เหมาะกับ | Next.js projects | มือใหม่/Static sites | speed + low cost |
Deploy บน Vercel — ง่ายแค่ 3 ขั้นตอน
รันคำสั่งเหล่านี้เพื่อ deploy:
# 1. Install CLI
npm i -g vercel
# 2. Deploy (ครั้งแรก)
cd my-next-app
vercel
# → ตอบคำถาม → ได้ preview URL ทันที
# 3. Deploy production
vercel --prod
# → ได้ URL: my-next-app.vercel.app
# หรือ... แค่ push to GitHub
# Vercel auto-deploy ทุก push ให้เลย 🎉
Vercel Project Config (vercel.json)
ตัวอย่าง code:
{
"framework": "nextjs",
"buildCommand": "npm run build",
"outputDirectory": ".next",
"regions": ["sin1"],
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-store" }
]
},
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" }
]
}
],
"rewrites": [
{ "source": "/api/:path*", "destination": "/api/:path*" }
],
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
]
}
2️⃣ PaaS — Railway / Render / Fly.io
เมื่อต้องการ Backend + Database
รันคำสั่งเหล่านี้เพื่อ deploy:
Static Host: HTML/CSS/JS only ❌ ไม่มี backend
PaaS: Full app + DB + Background jobs ✅
PaaS จัดการให้:
- 🖥️ Server provisioning
- 📦 Container orchestration
- 🗄️ Database management
- 🔐 SSL certificates
- 📊 Logs & monitoring
- 🔄 Auto-deploy from Git
- 📈 Auto-scaling (some)
เปรียบเทียบ PaaS ยอดนิยม
ถ้าคุณกำลังเริ่ม MVP ตารางนี้คือจุดตัดสินใจหลัก: ดู free tier, region, และความสะดวกเรื่อง DB/cron ก่อนเลือก เพื่อไม่ต้องย้ายแพลตฟอร์มเร็วเกินไป.
ตัวอย่าง code:
| Railway | Render | Fly.io | |
|---|---|---|---|
| Free Tier | $5 credit/month | 750 hrs/mo (spin down) | 3 shared VMs free |
| Starter | $5/mo | $7/mo | $0 + usage |
| DB included | ✅ Postgres/MySQL/Redis | ✅ Postgres/Redis | ✅ via Supabase |
| Regions | US/EU/AP | US/EU/SG | 35 regions |
| Cron Jobs | ✅ | ✅ (paid) | ✅ (machines) |
| Private Net | ✅ | ✅ | ✅ WireGuard |
| เหมาะกับ | Startup/Prototype | มือใหม่/Simple app | Global low-latency |
Deploy ด้วย Railway
รันคำสั่งเหล่านี้เพื่อ deploy:
# Install CLI
npm i -g @railway/cli
# Login
railway login
# Init project
railway init
# Add Postgres
railway add --database postgres
# Deploy
railway up
# ได้ URL: my-app.up.railway.app 🎉
Fly.io — Deploy ใกล้ user ที่สุด
รันคำสั่งเหล่านี้เพื่อ deploy:
# Install
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Launch (สร้าง fly.toml อัตโนมัติ)
fly launch
# Deploy
fly deploy
# Scale ไปหลาย region
fly regions add sin nrt
fly scale count 2
# ดู status
fly status
ตัวอย่าง code:
# fly.toml
app = "my-api"
primary_region = "sin"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
PORT = "3000"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
3️⃣ VPS — Virtual Private Server
ทำไมยังมีคนใช้ VPS?
ตัวอย่าง code:
PaaS ง่ายแต่...
- 💸 แพงขึ้นเรื่อยๆ เมื่อ scale
- 🔒 Limited customization
- 📦 Vendor lock-in
- 🐌 Cold starts (บาง service)
VPS ให้:
- 💰 ราคาคงที่ ไม่ว่า traffic เท่าไร
- 🔧 Full root access — ทำอะไรก็ได้
- 📦 Run อะไรก็ได้ (Docker, custom stack)
- 📚 เรียนรู้ Linux/DevOps จริงๆ
Trade-off: ต้องดูแล server เอง
(security updates, backup, monitoring)
VPS Providers เทียบราคา (2025)
ส่วนนี้เหมาะกับคนที่อยากคุมระบบเองและ optimize ค่าใช้จ่ายระยะยาว โดยเฉพาะเมื่อ traffic เริ่มนิ่งและต้องการ performance ที่คาดเดาได้.
ตัวอย่าง code:
| Provider | RAM | vCPU | SSD | ราคา/เดือน | จุดเด่น |
|---|---|---|---|---|---|
| Hetzner | 2 GB | 2 | 40 GB | €3.79 | ถูกที่สุด |
| DigitalOcean | 1 GB | 1 | 25 GB | $6 | เริ่มง่าย |
| Linode | 2 GB | 1 | 50 GB | $5 | community |
| Vultr | 1 GB | 1 | 25 GB | $5 | หลาย region |
| Oracle Cloud | 1 GB | 1 | 50 GB | FREE! | Always Free |
| Contabo | 4 GB | 2 | 50 GB | €4.99 | specs/ราคา |
VPS Setup — จากศูนย์ถึง Production
ไฟล์ configuration มีหน้าตาแบบนี้:
# === Step 1: เข้า server ===
ssh root@your-server-ip
# === Step 2: Security basics ===
# สร้าง user ใหม่ (อย่าใช้ root!)
adduser deploy
usermod -aG sudo deploy
# Setup SSH key
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
# ปิด password login + root login
sudo nano /etc/ssh/sshd_config
# PermitRootLogin no
# PasswordAuthentication no
sudo systemctl restart sshd
# === Step 3: Firewall ===
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
# === Step 4: Install Docker ===
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker deploy
# === Step 5: Deploy with Docker Compose ===
ตัวอย่าง code:
# docker-compose.yml
version: "3.8"
services:
app:
image: my-app:latest
build: .
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
ports:
- "3000:3000"
depends_on:
- db
- redis
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=securepassword
- POSTGRES_DB=mydb
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
pgdata:
รันคำสั่งเหล่านี้เพื่อ deploy:
# Deploy!
docker compose up -d
# Update (zero-downtime)
docker compose pull
docker compose up -d --no-deps --build app
4️⃣ Reverse Proxy — Nginx vs Caddy
ทำไมต้องมี Reverse Proxy?
ตัวอย่าง code:
WITHOUT Reverse Proxy
❌ No HTTPS
❌ No load balancing
❌ No static file caching
❌ Single point of failure
WITH Reverse Proxy
Nginx /
redirect)
✅ HTTPS (auto)
✅ Load balancing
✅ Static file serving
✅ Rate limiting
✅ Compression (gzip/brotli)
✅ WebSocket support
Nginx vs Caddy
ทั้งสองตัวดีมาก แต่ต่างกันที่จุดโฟกัส: Nginx ยืดหยุ่นสูงสำหรับ production ขนาดใหญ่, Caddy ชนะเรื่อง simplicity และ auto-HTTPS สำหรับทีมที่ต้องการความเร็วในการส่งงาน.
ไฟล์ configuration มีหน้าตาแบบนี้:
| Nginx | Caddy | |
|---|---|---|
| HTTPS | ต้อง setup certbot | AUTO! 🎉 |
| Config | ซับซ้อนแต่ยืดหยุ่น | ง่ายมาก |
| Performance | เร็วมาก | เร็วใกล้เคียง |
| Learning Curve | สูง | ต่ำ |
| Load Balancing | ✅ advanced | ✅ basic |
| เหมาะกับ | Production/high traffic | เริ่มต้น/auto-SSL |
Nginx Config — Full Production Setup
ตัวอย่าง code:
# /etc/nginx/sites-available/myapp
# Redirect HTTP → HTTPS
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$server_name$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
# SSL (managed by certbot)
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml text/javascript image/svg+xml;
# Static files (serve directly, bypass app)
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API / App (proxy to Node.js)
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Rate limiting for API
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Health check
location /health {
access_log off;
proxy_pass http://localhost:3000/health;
}
}
# Rate limit zone (ใส่ใน http block)
# limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
Caddy Config — ง่ายกว่าเยอะ!
ตัวอย่าง code:
# Caddyfile — แค่นี้จริงๆ!
myapp.com {
# HTTPS จัดการให้อัตโนมัติ — ไม่ต้องทำอะไร!
# Proxy to app
reverse_proxy localhost:3000
# Static files
handle_path /static/* {
root * /var/www/myapp/static
file_server {
precompressed gzip br
}
}
# Compression
encode gzip zstd
# Security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
# Rate limiting
rate_limit {
zone api {
key {remote_host}
events 10
window 1s
}
}
# Logs
log {
output file /var/log/caddy/myapp.log
format json
}
}
# Multiple sites? แค่เพิ่ม block!
api.myapp.com {
reverse_proxy localhost:4000
}
staging.myapp.com {
reverse_proxy localhost:5000
}
ไฟล์ configuration มีหน้าตาแบบนี้:
# Nginx: ต้องทำ 5 ขั้นตอน
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.com -d www.myapp.com
sudo nginx -t
sudo systemctl reload nginx
# + ตั้ง cron renew certificate
# Caddy: ขั้นตอนเดียว
sudo apt install caddy
sudo nano /etc/caddy/Caddyfile # ใส่ config ข้างบน
sudo systemctl reload caddy
# HTTPS จัดการให้อัตโนมัติ ✅
5️⃣ SSL/TLS — HTTPS ที่ทุกเว็บต้องมี
SSL/TLS คืออะไร?
ตัวอย่าง code:
HTTP (ไม่มี SSL):
🔓 ใครก็ดักข้อมูลได้ (password, credit card)
HTTPS (มี SSL/TLS):
🔐 เข้ารหัสทุกอย่าง ดักไม่ได้
TLS Handshake (simplified):
⚡ TLS 1.3: handshake เร็วขึ้น (1-RTT vs 2-RTT)
วิธีได้ SSL Certificate
หัวใจคือทำให้ HTTPS กลายเป็น default ตั้งแต่วันแรก เพราะมีผลทั้งความปลอดภัย, SEO, และความน่าเชื่อถือของระบบโดยตรง.
รันคำสั่งเหล่านี้เพื่อ deploy:
| วิธี | ราคา | ความยาก | ใช้เมื่อไร |
|---|---|---|---|
| Let's Encrypt | ฟรี | ง่าย | ทุกเว็บ (default) |
| Caddy auto-SSL | ฟรี | ง่ายที่สุด | ใช้ Caddy |
| Cloudflare SSL | ฟรี | ง่าย | ใช้ CF DNS |
| Vercel/Netlify | ฟรี | อัตโนมัติ | deploy ที่นั่น |
| Commercial | $10-500/yr | manual | enterprise/EV cert |
Let's Encrypt + Certbot
รันคำสั่งเหล่านี้เพื่อ deploy:
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Get certificate (ถ้าใช้ Nginx)
sudo certbot --nginx -d myapp.com -d www.myapp.com
# → ตอบคำถาม → ได้ cert อัตโนมัติ!
# Auto-renew (certbot ตั้งให้แล้ว)
sudo certbot renew --dry-run
# Check cert status
sudo certbot certificates
# Manual renew
sudo certbot renew
sudo systemctl reload nginx
6️⃣ Domain Management — DNS
DNS ทำงานยังไง?
ตัวอย่าง code:
Browser: "myapp.com อยู่ไหน?"
(ISP/Google (.com ?)
8.8.8.8)
┌────────▼───────┐
TLD Server
ns1.cf.com)
┌────────▼───────┐
◄─────────── Auth Server
IP: 1.2.3.4 (Cloudflare)
1.2.3.4
DNS Record Types
ตาราง DNS นี้ควร bookmark ไว้ใช้งานจริง เพราะปัญหา production จำนวนมากมักเริ่มจากการตั้งค่า A/CNAME/TXT ผิดเพียงจุดเดียว.
ตัวอย่าง code:
| Type | ใช้ทำอะไร | ตัวอย่าง |
|---|---|---|
| A | Domain → IPv4 | myapp.com → 1.2.3.4 |
| AAAA | Domain → IPv6 | myapp.com → 2001:db8::1 |
| CNAME | Alias | www → myapp.com |
| MX | Mail server | mail.google.com |
| TXT | SPF/DKIM verify | v=spf1 include:_spf... |
| NS | Nameserver | ns1.cloudflare.com |
| CAA | Allow CA issue cert | issue letsencrypt.org |
ตัวอย่าง DNS Setup — Cloudflare
ตัวอย่าง code:
Cloudflare DNS — myapp.com
Type Name Content TTL Proxy
A @ 1.2.3.4 Auto 🟠 On
A @ 5.6.7.8 Auto 🟠 On
AAAA @ 2001:db8::1 Auto 🟠 On
CNAME www myapp.com Auto 🟠 On
CNAME api api-prod.fly.dev Auto 🟠 On
CNAME blog myblog.vercel.app Auto ⚪ Off
MX @ mx1.google.com Auto N/A
MX @ mx2.google.com Auto N/A
TXT @ v=spf1 include:... Auto N/A
💡 Proxy 🟠 = Traffic ผ่าน Cloudflare (CDN + DDoS protection)
Proxy ⚪ = DNS only (ตรงไป server)
ซื้อ Domain ที่ไหน?
การเลือก registrar ไม่ใช่แค่เรื่องราคาเปิดโดเมนปีแรก แต่ต้องดูค่า renew, UX ตอนจัดการ DNS, และความโปร่งใสเรื่องค่าบริการระยะยาวด้วย.
ตัวอย่าง code:
| Registrar | .com ราคา | จุดเด่น |
|---|---|---|
| Cloudflare | ~$9.77 | at-cost + free DNS |
| Namecheap | ~$9.98 | UI ดี, WhoisGuard ฟรี |
| Porkbun | ~$9.73 | ถูกที่สุด, UI ดี |
| Google Domains | ~$12 | ย้ายไป Squarespace |
| GoDaddy | ~$12-20 | แพง/upsell เยอะ |
7️⃣ CDN — Content Delivery Network
CDN ทำงานยังไง?
ตัวอย่าง code:
WITHOUT CDN:
WITH CDN:
┌──── Edge Tokyo ◄── cached
User in Tokyo ──5ms─┤
- ── Origin US (if not cached)
┌──── Edge London ◄── cached
User in London ─5ms─┤
- ── Origin US (if not cached)
┌──── Edge Singapore ◄── cached
User in Bangkok ─10ms┤
- ── Origin US (if not cached)
⚡ Result: 200ms → 5-10ms (20-40x faster!)
CDN Providers
สำหรับเว็บทั่วไป Cloudflare free plan มักพอแล้ว แต่ถ้าอยู่ใน ecosystem AWS หนัก ๆ การใช้ CloudFront จะบริหารรวมกับระบบเดิมได้ง่ายกว่า.
ตัวอย่าง code:
| Provider | Free Tier | Edge Nodes | จุดเด่น |
|---|---|---|---|
| Cloudflare | ✅ ฟรี | 330+ cities | DDoS/WAF/Workers |
| AWS CloudFront | 1 TB/mo free | 600+ edges | AWS ecosystem |
| Bunny CDN | 14-day trial | 123 PoPs | ถูกมาก |
| Fastly | $50 trial | 90+ PoPs | real-time purge |
Cloudflare Setup (5 นาที)
ตัวอย่าง code:
1. สมัคร cloudflare.com (ฟรี)
3. เปลี่ยน nameserver ที่ registrar ไปเป็น:
- ns1.cloudflare.com
- ns2.cloudflare.com
4. รอ propagate (ไม่เกิน 24 ชม, ปกติ < 1 ชม)
5. เปิด features:
- SSL: Full (Strict)
- Always Use HTTPS: On
- Auto Minify: CSS/JS/HTML
- Brotli: On
- Cache: Standard
- Security Level: Medium
ได้ทันที:
✅ CDN (330+ edge nodes)
✅ DDoS protection
✅ Free SSL
✅ DNS management
✅ Page Rules
✅ Web Analytics
Cache Headers — ควบคุมสิ่งที่ cache
ตัวอย่าง code:
# Nginx — Cache Control headers
# Static assets (images, CSS, JS) — cache นาน
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# HTML — ไม่ cache (ให้ได้ content ใหม่เสมอ)
location ~* \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
}
# API — ไม่ cache เด็ดขาด
location /api/ {
add_header Cache-Control "no-store, no-cache";
}
ตัวอย่าง code:
Cache-Control Cheatsheet:
- public, max-age=31536000, immutable → Static assets (1 ปี)
- public, max-age=3600 → Semi-static (1 ชม)
- private, max-age=0, must-revalidate → User-specific data
- no-cache → ต้อง validate ก่อน serve
- no-store → ห้าม cache เลย (sensitive)
- stale-while-revalidate=60 → Serve stale + fetch new
8️⃣ Deployment Strategies — วิธี Deploy ที่ไม่พัง
Overview
รันคำสั่งเหล่านี้เพื่อ deploy:
❌ Bad Deploy:
1. FTP upload ไฟล์ใหม่ทับของเก่า
2. เว็บพัง 5 นาที
4. User โกรธ
✅ Good Deploy:
1. Deploy version ใหม่ข้างๆ version เก่า
2. ค่อยๆ ส่ง traffic ไป version ใหม่
4. User ไม่รู้เลยว่ามี deploy
Zero-Downtime Deployment = standard ในปี 2025
Rolling Deployment
รันคำสั่งเหล่านี้เพื่อ deploy:
เหมือนเปลี่ยนยางรถ... ทีละล้อ
เริ่มต้น: [v1] [v1] [v1] [v1] ← 4 instances, all v1
Step 1: [v2] [v1] [v1] [v1] ← update 1st instance
↑ ตรวจ health check → ✅ ผ่าน → ไปต่อ
Step 2: [v2] [v2] [v1] [v1] ← update 2nd instance
↑ ตรวจ health check → ✅ ผ่าน → ไปต่อ
Step 3: [v2] [v2] [v2] [v1] ← update 3rd instance
Step 4: [v2] [v2] [v2] [v2] ← done! 🎉
❌ ถ้า Step 2 fail?
Step 2: [v2] [v2] [v1] [v1] ← v2 #2 health check ❌
→ Stop! Rollback v2 #1 and #2 → [v1] [v1] [v1] [v1]
✅ Pros: ง่าย, ใช้ resource เท่าเดิม
❌ Cons: ระหว่าง deploy มี 2 versions วิ่งพร้อมกัน
Blue-Green Deployment
รันคำสั่งเหล่านี้เพื่อ deploy:
มี 2 environments: Blue (production) + Green (staging)
Phase 1: Blue = LIVE ✅
🔵 BLUE 🟢 GREEN
v1 (LIVE) ◄─── (idle)
- App x3 LB
- DB
Phase 2: Deploy v2 to Green
🔵 BLUE 🟢 GREEN
v1 (LIVE) ◄─── v2 (testing)
- App x3 LB ├── App x3
- DB └── DB
↑ ทดสอบ v2 ใน Green
Phase 3: Switch! (แค่เปลี่ยน LB)
🔵 BLUE 🟢 GREEN
v1 (standby) LB v2 (LIVE) ◄───
- App x3 ───► ├── App x3
- DB └── DB
✅ Pros: Zero-downtime, instant rollback
❌ Cons: ต้องมี 2x resources (แพงกว่า)
Canary Deployment
ตัวอย่าง code:
เหมือนนกขมิ้นในเหมือง — ส่งตัวเล็กไปทดสอบก่อน
Step 1: ส่ง 5% traffic ไป v2
Load Balancer
Monitor: error rate, latency, CPU
v1 instances terminated
❌ ถ้า Step 1 มีปัญหา (error spike)?
✅ Pros: ลด blast radius, data-driven decisions
❌ Cons: ซับซ้อน, ต้องมี monitoring ดี
เปรียบเทียบ Deployment Strategies
ตารางนี้ช่วยตัดสินใจเชิงความเสี่ยง: ถ้าต้องการปล่อยงานไวใช้ Rolling, ถ้าระบบ critical มากให้ขยับไป Blue-Green หรือ Canary.
รันคำสั่งเหล่านี้เพื่อ deploy:
| Rolling | Blue-Green | Canary | Recreate | |
|---|---|---|---|---|
| Downtime | Zero | Zero | Zero | มี ❌ |
| Rollback | ช้า | ทันที | เร็ว | ช้า |
| Resource | N+1 | 2N | N+1 | N |
| Risk | ปานกลาง | ต่ำ | ต่ำสุด | สูง |
| Complexity | ต่ำ | ปานกลาง | สูง | ต่ำสุด |
| Use case | ทั่วไป | ต้อง fast rollback | critical systems | dev/test |
Kubernetes Rolling Update Config
ไฟล์ configuration มีหน้าตาแบบนี้:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # สร้าง pod ใหม่เพิ่มได้ 1 ตัว
maxUnavailable: 0 # ห้ามมี pod ลดลง (zero-downtime)
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v2
ports:
- containerPort: 3000
# Health checks — สำคัญมาก!
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
ไฟล์ configuration มีหน้าตาแบบนี้:
# Deploy
kubectl apply -f deployment.yaml
# ดู rollout status
kubectl rollout status deployment/myapp
# Rollback ถ้ามีปัญหา
kubectl rollout undo deployment/myapp
# ดู history
kubectl rollout history deployment/myapp
9️⃣ Full Production Setup — จากศูนย์ถึง Production
Architecture Overview
รันคำสั่งเหล่านี้เพื่อ deploy:
Production Stack
Cloudflare CDN + DNS + DDoS + WAF
(Free Plan)
┌──────▼───────┐
Caddy Reverse Proxy + Auto-SSL
(or Nginx)
┌──────▼───────────────────────────────────┐
Docker Compose
App Postgres Redis
:3000 :5432 :6379
Monitoring: Uptime Kuma + Grafana
Backup: Automated daily to S3/B2
VPS: Hetzner CPX11 (2 vCPU, 2GB RAM) — €3.79/mo
Domain: Cloudflare Registrar — ~$10/yr
Total: ~$15/month for full production stack! 💰
GitHub Actions — Auto Deploy on Push
รันคำสั่งเหล่านี้เพื่อ deploy:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install & Test
run: |
npm ci
npm run lint
npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /home/deploy/myapp
git pull origin main
docker compose pull
docker compose up -d --build --no-deps app
docker image prune -f
- name: Health Check
run: |
sleep 10
curl -f https://myapp.com/health || exit 1
- name: Notify on Failure
if: failure()
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-type: application/json' \
-d '{"text":"❌ Deploy failed! Check GitHub Actions."}'
Automated Backups
รันคำสั่งเหล่านี้เพื่อ deploy:
#!/bin/bash
# backup.sh — run daily via cron
set -euo pipefail
BACKUP_DIR="/home/deploy/backups"
DATE=$(date +%Y-%m-%d_%H%M)
RETENTION_DAYS=7
# Backup PostgreSQL
docker exec postgres pg_dump -U user mydb | gzip > "$BACKUP_DIR/db_${DATE}.sql.gz"
# Backup app data
tar czf "$BACKUP_DIR/data_${DATE}.tar.gz" /home/deploy/myapp/data/
# Upload to Backblaze B2 (cheap!)
rclone copy "$BACKUP_DIR/db_${DATE}.sql.gz" b2:my-backups/db/
rclone copy "$BACKUP_DIR/data_${DATE}.tar.gz" b2:my-backups/data/
# Cleanup old backups
find "$BACKUP_DIR" -name "*.gz" -mtime +$RETENTION_DAYS -delete
echo "✅ Backup completed: $DATE"
รันคำสั่งเหล่านี้เพื่อ deploy:
# Cron job (daily at 3 AM)
crontab -e
0 3 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1
🔟 Monitoring — รู้ก่อนที่จะพัง
Uptime Monitoring
ตัวอย่าง code:
# Uptime Kuma — self-hosted uptime monitor
# (ฟรี, สวย, alert ได้)
docker run -d \
--name uptime-kuma \
--restart unless-stopped \
-p 3001:3001 \
-v uptime-kuma:/app/data \
louislam/uptime-kuma:1
# → เข้า http://your-server:3001
# → เพิ่ม monitors:
# - HTTPS: myapp.com (check every 60s)
# - TCP: db server port 5432
# - Docker container health
# → ตั้ง alert: Telegram, Slack, Discord, Email
Simple Health Check Endpoint
ไฟล์ configuration มีหน้าตาแบบนี้:
// Express.js health check
app.get('/health', async (req, res) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks: {}
};
// Check database
try {
await db.query('SELECT 1');
health.checks.database = 'ok';
} catch (err) {
health.status = 'degraded';
health.checks.database = 'error';
}
// Check Redis
try {
await redis.ping();
health.checks.redis = 'ok';
} catch (err) {
health.status = 'degraded';
health.checks.redis = 'error';
}
// Check disk space
const diskFree = getDiskFreePercent();
health.checks.disk = diskFree > 10 ? 'ok' : 'warning';
const statusCode = health.status === 'ok' ? 200 : 503;
res.status(statusCode).json(health);
});
🛡️ Security Checklist — ก่อนจะ Go Live
ไฟล์ configuration มีหน้าตาแบบนี้:
Production Security Checklist ✅
🔐 SSL/TLS
- [ ] HTTPS everywhere (redirect HTTP → HTTPS)
- [ ] TLS 1.2+ only (disable TLS 1.0/1.1)
- [ ] HSTS header enabled
- [ ] Certificate auto-renewal working
🔒 Server
- [ ] SSH key only (no password login)
- [ ] Non-root user for app
- [ ] Firewall enabled (only 22, 80, 443)
- [ ] Unattended security updates
- [ ] Fail2ban installed
🛡️ Application
- [ ] Environment variables for secrets
- [ ] CORS configured properly
- [ ] Rate limiting on API
- [ ] Input validation & sanitization
- [ ] Security headers (X-Frame, CSP, etc.)
- [ ] Dependencies updated (npm audit)
📊 Monitoring
- [ ] Uptime monitoring active
- [ ] Error tracking (Sentry)
- [ ] Log rotation configured
- [ ] Alerts configured (Telegram/Slack)
💾 Backup
- [ ] Daily automated backups
- [ ] Backups stored off-server
- [ ] Restore tested recently
- [ ] Backup monitoring (alert on failure)
🗺️ Decision Flowchart — เลือก Hosting ยังไง?
ตัวอย่าง code:
เริ่มต้น: มี project อะไร?
- Static site / SPA / Next.js?
- ใช้ Next.js → Vercel (free)
- Static HTML → Cloudflare Pages (free, fastest)
- อื่นๆ → Netlify (free, easiest)
- Full-stack app + DB?
- MVP / prototype?
└── Railway or Render (ง่ายที่สุด)
- ต้อง customize?
└── VPS + Docker Compose
- ต้อง scale globally?
└── Fly.io (multi-region)
- Enterprise?
- AWS/GCP/Azure + Kubernetes
- API only?
- Lightweight → Cloudflare Workers / Vercel Edge
- Node/Python → Railway or Fly.io
- Complex → VPS or Cloud
- ML / GPU workloads?
- Inference → Modal, Replicate, RunPod
- Training → Lambda Labs, Vast.ai
- Enterprise → AWS SageMaker, GCP Vertex
🔑 Key Takeaways
แนวคิดสำคัญของบทนี้คือ “เลือกให้เหมาะกับ stage ของโปรเจกต์” มากกว่าการไล่ใช้ของใหญ่ตั้งแต่วันแรก แล้วค่อยยกระดับ architecture ตามภาระงานจริง.
- เริ่มจากง่ายไปยาก — Vercel/Netlify → PaaS → VPS → Cloud
- HTTPS ฟรีทุกที่ — Let's Encrypt, Caddy auto-SSL, Cloudflare
- Cloudflare = must-have — CDN + DNS + DDoS ฟรี
- Caddy ง่ายกว่า Nginx — สำหรับ project ที่ไม่ต้อง fine-tune มาก
- Zero-downtime deploy เป็น standard — Rolling update เป็นค่า default
- Canary deploy สำหรับ critical systems — ลด blast radius
- Blue-Green สำหรับ instant rollback — แต่ต้องมี 2x resources
- VPS + Docker Compose + Caddy = sweet spot — $5-15/mo ได้ production-grade
- Automate everything — CI/CD, backups, monitoring, SSL renewal
- Monitor before you deploy — ถ้าไม่ monitor = ไม่รู้ว่าพัง
📌 This is #21 in our DevOps & Full-Stack series
📚 Series ทั้งหมด:
- Git Branching — แตก branch ยังไงให้เป็นระบบ
- CI/CD Pipeline — จาก commit ถึง production
- Docker vs VMs — Container คืออะไร ทำไมต้องใช้
- API Lifecycle — Request เดินทางยังไง
- Kubernetes — จัดการ container ระดับ production
- Networking — DNS, TCP, Load Balancer, Firewall
- Infrastructure as Code — Terraform & Ansible
- Monitoring & Observability — Logs, Metrics, Traces
- Linux & Shell Essentials — พื้นฐานที่ DevOps ต้องรู้
- DevSecOps & Security — Shift Left, OWASP, Secrets
- Testing Strategies — Pyramid, TDD, Load, Chaos
- GitOps & ArgoCD — Git-driven Deployments
- Cloud Architecture — AWS, GCP, Azure Fundamentals
- SRE — Site Reliability Engineering
- GitHub Actions Workflow — Advanced CI/CD
- Automated Testing — Unit, Integration, E2E
- Database Design & SQL — Normalization, Indexing, Query Optimization
- Authentication & Authorization — JWT, OAuth, RBAC
- Web Architecture & Design Patterns — Monolith vs Microservices
- Frontend Performance & Modern Frameworks
- Deployment & Hosting Strategies — Vercel to Bare Metal ← 📍 อยู่ตรงนี้
🐕 The Code Is Finished... So Where Does It Go?
Sample code:
The developer just starting out:
"The site is done! How do I let other people reach it?"
- 🤔 Buy my own server?
- 🤔 Use Vercel/Netlify?
- 🤔 Rent a VPS?
- 🤔 Go to AWS/GCP?
- 🤔 What about the domain? What about HTTPS?
❌ Choose wrong = expensive, slow, breaks often
✅ Choose right = cheap, fast, scales
This post walks through every option
from the free tier all the way to enterprise-grade
📊 Hosting Options — Every Kind Compared
Sample code:
| Kind | Starting price | Control | Scale | You maintain it | Best for |
|---|---|---|---|---|---|
| Static Host (Vercel/Netlify) | $0 | Low | Automatic | No | Static/SPA, JAMstack |
| PaaS (Railway/Render/Fly.io) | $0-7/mo | Low-medium | Automatic | A little | Web apps, API, MVP |
| VPS (DO/Linode/Hetzner) | $4-6/mo | High | manual | Moderate | custom setup |
| Cloud (AWS/GCP/Azure) | $0-????? | Very high | Automatic | A lot | Enterprise |
| Bare Metal | $50+/mo | Highest | manual | A great deal | High perf, ML, Gaming |
- Side project / portfolio → Vercel/Netlify (free)
- Startup MVP → Railway/Render (easy, cheap)
- Lots of customization → VPS (good control, good price)
- Enterprise / scale → Cloud (AWS/GCP/Azure)
- ML training / gaming → Bare Metal / GPU Cloud
1️⃣ Static Hosting — Vercel / Netlify / Cloudflare Pages
Why Is It Free?
Sample code:
Traditional Hosting:
💸 The server has to run all the time = you pay all the time
Static Hosting:
💰 Just storing files + serving them over a CDN = very cheap (free is possible)
Vercel vs Netlify vs Cloudflare Pages
This table makes it very quick to see that each platform is strong in a different direction: Vercel suits Next.js, Netlify is the easy one for small teams, and Cloudflare Pages leads on edge presence and bandwidth cost.
Run these commands to deploy:
| Vercel | Netlify | CF Pages | |
|---|---|---|---|
| Strength | Next.js best-in-class | Easiest | Fastest |
| Free BW | 100 GB/mo | 100 GB/mo | Unlimited |
| Free Builds | 6000 min/mo | 300 min/mo | 500 builds/mo |
| Serverless | ✅ Edge+Node | ✅ Functions | ✅ Workers |
| Edge Runtime | ✅ v8 | ✅ Deno | ✅ v8 |
| Git Integration | GitHub/GitLab/Bitbucket | GitHub/GitLab/Bitbucket | GitHub/GitLab |
| Analytics | ✅ (paid) | ✅ (paid) | ✅ (free basic) |
| Pro Price | $20/mo/seat | $19/mo/seat | $5/mo (paid) |
| Best for | Next.js projects | Beginners/Static sites | speed + low cost |
Deploying on Vercel — Just Three Steps
Run these commands to deploy:
# 1. Install CLI
npm i -g vercel
# 2. Deploy (first time)
cd my-next-app
vercel
# → answer the prompts → a preview URL straight away
# 3. Deploy production
vercel --prod
# → you get the URL: my-next-app.vercel.app
# Or... just push to GitHub
# Vercel auto-deploys on every push for you 🎉
Vercel Project Config (vercel.json)
Sample code:
{
"framework": "nextjs",
"buildCommand": "npm run build",
"outputDirectory": ".next",
"regions": ["sin1"],
"headers": [
{
"source": "/api/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-store" }
]
},
{
"source": "/(.*)",
"headers": [
{ "key": "X-Frame-Options", "value": "DENY" },
{ "key": "X-Content-Type-Options", "value": "nosniff" }
]
}
],
"rewrites": [
{ "source": "/api/:path*", "destination": "/api/:path*" }
],
"redirects": [
{ "source": "/old-page", "destination": "/new-page", "permanent": true }
]
}
2️⃣ PaaS — Railway / Render / Fly.io
When You Need a Backend + Database
Run these commands to deploy:
Static Host: HTML/CSS/JS only ❌ no backend
PaaS: Full app + DB + Background jobs ✅
PaaS handles all of this for you:
- 🖥️ Server provisioning
- 📦 Container orchestration
- 🗄️ Database management
- 🔐 SSL certificates
- 📊 Logs & monitoring
- 🔄 Auto-deploy from Git
- 📈 Auto-scaling (some)
The Popular PaaS Platforms Compared
If you are starting an MVP, this table is the main decision point: look at the free tier, the regions, and how convenient DB and cron are before you choose, so you do not have to change platforms too soon.
Sample code:
| Railway | Render | Fly.io | |
|---|---|---|---|
| Free Tier | $5 credit/month | 750 hrs/mo (spin down) | 3 shared VMs free |
| Starter | $5/mo | $7/mo | $0 + usage |
| DB included | ✅ Postgres/MySQL/Redis | ✅ Postgres/Redis | ✅ via Supabase |
| Regions | US/EU/AP | US/EU/SG | 35 regions |
| Cron Jobs | ✅ | ✅ (paid) | ✅ (machines) |
| Private Net | ✅ | ✅ | ✅ WireGuard |
| Best for | Startup/Prototype | Beginners/Simple app | Global low-latency |
Deploying with Railway
Run these commands to deploy:
# Install CLI
npm i -g @railway/cli
# Login
railway login
# Init project
railway init
# Add Postgres
railway add --database postgres
# Deploy
railway up
# You get the URL: my-app.up.railway.app 🎉
Fly.io — Deploy as Close to the User as Possible
Run these commands to deploy:
# Install
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Launch (creates fly.toml automatically)
fly launch
# Deploy
fly deploy
# Scale out to several regions
fly regions add sin nrt
fly scale count 2
# Check status
fly status
Sample code:
# fly.toml
app = "my-api"
primary_region = "sin"
[build]
dockerfile = "Dockerfile"
[env]
NODE_ENV = "production"
PORT = "3000"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 1
[[vm]]
cpu_kind = "shared"
cpus = 1
memory_mb = 256
3️⃣ VPS — Virtual Private Server
Why Do People Still Use a VPS?
Sample code:
PaaS is easy, but...
- 💸 Steadily more expensive as you scale
- 🔒 Limited customization
- 📦 Vendor lock-in
- 🐌 Cold starts (on some services)
A VPS gives you:
- 💰 A fixed price no matter how much traffic
- 🔧 Full root access — do whatever you like
- 📦 Run anything (Docker, custom stack)
- 📚 Learn Linux/DevOps for real
Trade-off: you maintain the server yourself
(security updates, backup, monitoring)
VPS Providers Compared on Price (2025)
This section is for anyone who wants to run the system themselves and optimize cost over the long run — especially once traffic has settled and predictable performance matters.
Sample code:
| Provider | RAM | vCPU | SSD | Price/month | Strength |
|---|---|---|---|---|---|
| Hetzner | 2 GB | 2 | 40 GB | €3.79 | Cheapest |
| DigitalOcean | 1 GB | 1 | 25 GB | $6 | Easy to start |
| Linode | 2 GB | 1 | 50 GB | $5 | community |
| Vultr | 1 GB | 1 | 25 GB | $5 | Many regions |
| Oracle Cloud | 1 GB | 1 | 50 GB | FREE! | Always Free |
| Contabo | 4 GB | 2 | 50 GB | €4.99 | specs/price |
VPS Setup — From Zero to Production
The configuration file looks like this:
# === Step 1: get into the server ===
ssh root@your-server-ip
# === Step 2: Security basics ===
# Create a new user (do not use root!)
adduser deploy
usermod -aG sudo deploy
# Setup SSH key
mkdir -p /home/deploy/.ssh
cp ~/.ssh/authorized_keys /home/deploy/.ssh/
chown -R deploy:deploy /home/deploy/.ssh
# Turn off password login + root login
sudo nano /etc/ssh/sshd_config
# PermitRootLogin no
# PasswordAuthentication no
sudo systemctl restart sshd
# === Step 3: Firewall ===
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status
# === Step 4: Install Docker ===
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker deploy
# === Step 5: Deploy with Docker Compose ===
Sample code:
# docker-compose.yml
version: "3.8"
services:
app:
image: my-app:latest
build: .
restart: unless-stopped
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://user:pass@db:5432/mydb
ports:
- "3000:3000"
depends_on:
- db
- redis
db:
image: postgres:16-alpine
restart: unless-stopped
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=securepassword
- POSTGRES_DB=mydb
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
pgdata:
Run these commands to deploy:
# Deploy!
docker compose up -d
# Update (zero-downtime)
docker compose pull
docker compose up -d --no-deps --build app
4️⃣ Reverse Proxy — Nginx vs Caddy
Why Do You Need a Reverse Proxy?
Sample code:
WITHOUT Reverse Proxy
❌ No HTTPS
❌ No load balancing
❌ No static file caching
❌ Single point of failure
WITH Reverse Proxy
Nginx /
redirect)
✅ HTTPS (auto)
✅ Load balancing
✅ Static file serving
✅ Rate limiting
✅ Compression (gzip/brotli)
✅ WebSocket support
Nginx vs Caddy
Both are excellent, but they focus differently: Nginx is highly flexible for large production estates, while Caddy wins on simplicity and auto-HTTPS for teams that need to ship quickly.
The configuration file looks like this:
| Nginx | Caddy | |
|---|---|---|
| HTTPS | You must set up certbot | AUTO! 🎉 |
| Config | Complex but flexible | Very easy |
| Performance | Very fast | Nearly as fast |
| Learning Curve | Steep | Gentle |
| Load Balancing | ✅ advanced | ✅ basic |
| Best for | Production/high traffic | Getting started/auto-SSL |
Nginx Config — Full Production Setup
Sample code:
# /etc/nginx/sites-available/myapp
# Redirect HTTP → HTTPS
server {
listen 80;
server_name myapp.com www.myapp.com;
return 301 https://$server_name$request_uri;
}
# Main HTTPS server
server {
listen 443 ssl http2;
server_name myapp.com www.myapp.com;
# SSL (managed by certbot)
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# Security headers
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# Gzip compression
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript
text/xml application/xml text/javascript image/svg+xml;
# Static files (serve directly, bypass app)
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# API / App (proxy to Node.js)
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# Rate limiting for API
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
# Health check
location /health {
access_log off;
proxy_pass http://localhost:3000/health;
}
}
# Rate limit zone (put this inside the http block)
# limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
Caddy Config — So Much Simpler!
Sample code:
# Caddyfile — this really is all of it!
myapp.com {
# HTTPS is handled automatically — nothing to do!
# Proxy to app
reverse_proxy localhost:3000
# Static files
handle_path /static/* {
root * /var/www/myapp/static
file_server {
precompressed gzip br
}
}
# Compression
encode gzip zstd
# Security headers
header {
X-Frame-Options "SAMEORIGIN"
X-Content-Type-Options "nosniff"
Strict-Transport-Security "max-age=31536000; includeSubDomains"
}
# Rate limiting
rate_limit {
zone api {
key {remote_host}
events 10
window 1s
}
}
# Logs
log {
output file /var/log/caddy/myapp.log
format json
}
}
# Multiple sites? Just add a block!
api.myapp.com {
reverse_proxy localhost:4000
}
staging.myapp.com {
reverse_proxy localhost:5000
}
The configuration file looks like this:
# Nginx: five steps to do
sudo apt install nginx certbot python3-certbot-nginx
sudo certbot --nginx -d myapp.com -d www.myapp.com
sudo nginx -t
sudo systemctl reload nginx
# + set up a cron job to renew the certificate
# Caddy: one step
sudo apt install caddy
sudo nano /etc/caddy/Caddyfile # paste the config above
sudo systemctl reload caddy
# HTTPS is handled automatically ✅
5️⃣ SSL/TLS — The HTTPS Every Site Must Have
What Are SSL and TLS?
Sample code:
HTTP (no SSL):
🔓 Anyone can intercept the data (password, credit card)
HTTPS (with SSL/TLS):
🔐 Everything is encrypted — interception gets nothing
TLS Handshake (simplified):
⚡ TLS 1.3: a faster handshake (1-RTT vs 2-RTT)
Ways to Get an SSL Certificate
The heart of it is making HTTPS the default from day one, because it bears directly on security, on SEO, and on how trustworthy the system looks.
Run these commands to deploy:
| Method | Price | Difficulty | When to use it |
|---|---|---|---|
| Let's Encrypt | Free | Easy | Every site (default) |
| Caddy auto-SSL | Free | Easiest | You use Caddy |
| Cloudflare SSL | Free | Easy | You use CF DNS |
| Vercel/Netlify | Free | Automatic | You deploy there |
| Commercial | $10-500/yr | manual | enterprise/EV cert |
Let's Encrypt + Certbot
Run these commands to deploy:
# Install certbot
sudo apt install certbot python3-certbot-nginx
# Get certificate (if you are on Nginx)
sudo certbot --nginx -d myapp.com -d www.myapp.com
# → answer the prompts → the cert arrives automatically!
# Auto-renew (certbot has already set this up)
sudo certbot renew --dry-run
# Check cert status
sudo certbot certificates
# Manual renew
sudo certbot renew
sudo systemctl reload nginx
6️⃣ Domain Management — DNS
How Does DNS Work?
Sample code:
Browser: "Where is myapp.com?"
(ISP/Google (.com ?)
8.8.8.8)
┌────────▼───────┐
TLD Server
ns1.cf.com)
┌────────▼───────┐
◄─────────── Auth Server
IP: 1.2.3.4 (Cloudflare)
1.2.3.4
DNS Record Types
Bookmark this DNS table for real work, because a great many production problems begin with a single wrong A, CNAME or TXT record.
Sample code:
| Type | What it does | Example |
|---|---|---|
| A | Domain → IPv4 | myapp.com → 1.2.3.4 |
| AAAA | Domain → IPv6 | myapp.com → 2001:db8::1 |
| CNAME | Alias | www → myapp.com |
| MX | Mail server | mail.google.com |
| TXT | SPF/DKIM verify | v=spf1 include:_spf... |
| NS | Nameserver | ns1.cloudflare.com |
| CAA | Allow CA issue cert | issue letsencrypt.org |
A Sample DNS Setup — Cloudflare
Sample code:
Cloudflare DNS — myapp.com
Type Name Content TTL Proxy
A @ 1.2.3.4 Auto 🟠 On
A @ 5.6.7.8 Auto 🟠 On
AAAA @ 2001:db8::1 Auto 🟠 On
CNAME www myapp.com Auto 🟠 On
CNAME api api-prod.fly.dev Auto 🟠 On
CNAME blog myblog.vercel.app Auto ⚪ Off
MX @ mx1.google.com Auto N/A
MX @ mx2.google.com Auto N/A
TXT @ v=spf1 include:... Auto N/A
💡 Proxy 🟠 = Traffic goes through Cloudflare (CDN + DDoS protection)
Proxy ⚪ = DNS only (straight to your server)
Where Should You Buy a Domain?
Choosing a registrar is not only about the first-year price of the domain: look at the renewal cost, at the UX of managing DNS, and at how transparent the fees are over the long run.
Sample code:
| Registrar | .com price | Strength |
|---|---|---|
| Cloudflare | ~$9.77 | at-cost + free DNS |
| Namecheap | ~$9.98 | Good UI, free WhoisGuard |
| Porkbun | ~$9.73 | Cheapest, good UI |
| Google Domains | ~$12 | Moved to Squarespace |
| GoDaddy | ~$12-20 | Expensive/heavy upsell |
7️⃣ CDN — Content Delivery Network
How Does a CDN Work?
Sample code:
WITHOUT CDN:
WITH CDN:
┌──── Edge Tokyo ◄── cached
User in Tokyo ──5ms─┤
- ── Origin US (if not cached)
┌──── Edge London ◄── cached
User in London ─5ms─┤
- ── Origin US (if not cached)
┌──── Edge Singapore ◄── cached
User in Bangkok ─10ms┤
- ── Origin US (if not cached)
⚡ Result: 200ms → 5-10ms (20-40x faster!)
CDN Providers
For an ordinary site the Cloudflare free plan is usually enough, but if you live deep inside the AWS ecosystem, CloudFront is easier to manage alongside what you already have.
Sample code:
| Provider | Free Tier | Edge Nodes | Strength |
|---|---|---|---|
| Cloudflare | ✅ Free | 330+ cities | DDoS/WAF/Workers |
| AWS CloudFront | 1 TB/mo free | 600+ edges | AWS ecosystem |
| Bunny CDN | 14-day trial | 123 PoPs | Very cheap |
| Fastly | $50 trial | 90+ PoPs | real-time purge |
Cloudflare Setup (5 Minutes)
Sample code:
1. Sign up at cloudflare.com (free)
3. Change the nameservers at your registrar to:
- ns1.cloudflare.com
- ns2.cloudflare.com
4. Wait for propagation (24 hrs at most, usually < 1 hr)
5. Turn on features:
- SSL: Full (Strict)
- Always Use HTTPS: On
- Auto Minify: CSS/JS/HTML
- Brotli: On
- Cache: Standard
- Security Level: Medium
What you get immediately:
✅ CDN (330+ edge nodes)
✅ DDoS protection
✅ Free SSL
✅ DNS management
✅ Page Rules
✅ Web Analytics
Cache Headers — Controlling What Gets Cached
Sample code:
# Nginx — Cache Control headers
# Static assets (images, CSS, JS) — cache for a long time
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
expires 365d;
add_header Cache-Control "public, immutable";
}
# HTML — do not cache (always serve fresh content)
location ~* \.html$ {
add_header Cache-Control "no-cache, must-revalidate";
}
# API — never cache, no exceptions
location /api/ {
add_header Cache-Control "no-store, no-cache";
}
Sample code:
Cache-Control Cheatsheet:
- public, max-age=31536000, immutable → Static assets (1 year)
- public, max-age=3600 → Semi-static (1 hr)
- private, max-age=0, must-revalidate → User-specific data
- no-cache → must validate before serving
- no-store → never cache at all (sensitive)
- stale-while-revalidate=60 → Serve stale + fetch new
8️⃣ Deployment Strategies — Shipping Without Breaking Things
Overview
Run these commands to deploy:
❌ Bad Deploy:
1. FTP the new files over the old ones
2. The site is down for 5 minutes
4. Users are furious
✅ Good Deploy:
1. Deploy the new version beside the old one
2. Move traffic across to the new version gradually
4. Users never notice a deploy happened
Zero-Downtime Deployment = the standard in 2025
Rolling Deployment
Run these commands to deploy:
Like changing a car's tyres... one wheel at a time
Start: [v1] [v1] [v1] [v1] ← 4 instances, all v1
Step 1: [v2] [v1] [v1] [v1] ← update 1st instance
↑ run the health check → ✅ pass → carry on
Step 2: [v2] [v2] [v1] [v1] ← update 2nd instance
↑ run the health check → ✅ pass → carry on
Step 3: [v2] [v2] [v2] [v1] ← update 3rd instance
Step 4: [v2] [v2] [v2] [v2] ← done! 🎉
❌ What if Step 2 fails?
Step 2: [v2] [v2] [v1] [v1] ← v2 #2 health check ❌
→ Stop! Rollback v2 #1 and #2 → [v1] [v1] [v1] [v1]
✅ Pros: simple, uses the same resources as before
❌ Cons: during the deploy two versions run side by side
Blue-Green Deployment
Run these commands to deploy:
Two environments: Blue (production) + Green (staging)
Phase 1: Blue = LIVE ✅
🔵 BLUE 🟢 GREEN
v1 (LIVE) ◄─── (idle)
- App x3 LB
- DB
Phase 2: Deploy v2 to Green
🔵 BLUE 🟢 GREEN
v1 (LIVE) ◄─── v2 (testing)
- App x3 LB ├── App x3
- DB └── DB
↑ test v2 inside Green
Phase 3: Switch! (just repoint the LB)
🔵 BLUE 🟢 GREEN
v1 (standby) LB v2 (LIVE) ◄───
- App x3 ───► ├── App x3
- DB └── DB
✅ Pros: Zero-downtime, instant rollback
❌ Cons: needs 2x the resources (more expensive)
Canary Deployment
Sample code:
Like the canary in the coal mine — send the small one in to test first
Step 1: send 5% of traffic to v2
Load Balancer
Monitor: error rate, latency, CPU
v1 instances terminated
❌ What if Step 1 goes wrong (error spike)?
✅ Pros: a smaller blast radius, data-driven decisions
❌ Cons: complex, and it needs good monitoring
Deployment Strategies Compared
This table helps you make the risk call: if you need to ship fast, use Rolling; if the system is highly critical, move up to Blue-Green or Canary.
Run these commands to deploy:
| Rolling | Blue-Green | Canary | Recreate | |
|---|---|---|---|---|
| Downtime | Zero | Zero | Zero | Yes ❌ |
| Rollback | Slow | Instant | Fast | Slow |
| Resource | N+1 | 2N | N+1 | N |
| Risk | Moderate | Low | Lowest | High |
| Complexity | Low | Moderate | High | Lowest |
| Use case | General | Need fast rollback | critical systems | dev/test |
Kubernetes Rolling Update Config
The configuration file looks like this:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1 # may add 1 new pod above the count
maxUnavailable: 0 # never let the pod count drop (zero-downtime)
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myapp:v2
ports:
- containerPort: 3000
# Health checks — this matters enormously!
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 15
periodSeconds: 10
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
The configuration file looks like this:
# Deploy
kubectl apply -f deployment.yaml
# Watch the rollout status
kubectl rollout status deployment/myapp
# Roll back if something goes wrong
kubectl rollout undo deployment/myapp
# See the history
kubectl rollout history deployment/myapp
9️⃣ Full Production Setup — From Zero to Production
Architecture Overview
Run these commands to deploy:
Production Stack
Cloudflare CDN + DNS + DDoS + WAF
(Free Plan)
┌──────▼───────┐
Caddy Reverse Proxy + Auto-SSL
(or Nginx)
┌──────▼───────────────────────────────────┐
Docker Compose
App Postgres Redis
:3000 :5432 :6379
Monitoring: Uptime Kuma + Grafana
Backup: Automated daily to S3/B2
VPS: Hetzner CPX11 (2 vCPU, 2GB RAM) — €3.79/mo
Domain: Cloudflare Registrar — ~$10/yr
Total: ~$15/month for full production stack! 💰
GitHub Actions — Auto Deploy on Push
Run these commands to deploy:
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install & Test
run: |
npm ci
npm run lint
npm test
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Deploy to VPS
uses: appleboy/ssh-action@v1
with:
host: ${{ secrets.SERVER_HOST }}
username: deploy
key: ${{ secrets.SSH_PRIVATE_KEY }}
script: |
cd /home/deploy/myapp
git pull origin main
docker compose pull
docker compose up -d --build --no-deps app
docker image prune -f
- name: Health Check
run: |
sleep 10
curl -f https://myapp.com/health || exit 1
- name: Notify on Failure
if: failure()
run: |
curl -X POST "${{ secrets.SLACK_WEBHOOK }}" \
-H 'Content-type: application/json' \
-d '{"text":"❌ Deploy failed! Check GitHub Actions."}'
Automated Backups
Run these commands to deploy:
#!/bin/bash
# backup.sh — run daily via cron
set -euo pipefail
BACKUP_DIR="/home/deploy/backups"
DATE=$(date +%Y-%m-%d_%H%M)
RETENTION_DAYS=7
# Backup PostgreSQL
docker exec postgres pg_dump -U user mydb | gzip > "$BACKUP_DIR/db_${DATE}.sql.gz"
# Backup app data
tar czf "$BACKUP_DIR/data_${DATE}.tar.gz" /home/deploy/myapp/data/
# Upload to Backblaze B2 (cheap!)
rclone copy "$BACKUP_DIR/db_${DATE}.sql.gz" b2:my-backups/db/
rclone copy "$BACKUP_DIR/data_${DATE}.tar.gz" b2:my-backups/data/
# Cleanup old backups
find "$BACKUP_DIR" -name "*.gz" -mtime +$RETENTION_DAYS -delete
echo "✅ Backup completed: $DATE"
Run these commands to deploy:
# Cron job (daily at 3 AM)
crontab -e
0 3 * * * /home/deploy/backup.sh >> /var/log/backup.log 2>&1
🔟 Monitoring — Know Before It Breaks
Uptime Monitoring
Sample code:
# Uptime Kuma — self-hosted uptime monitor
# (free, good-looking, and it can alert you)
docker run -d \
--name uptime-kuma \
--restart unless-stopped \
-p 3001:3001 \
-v uptime-kuma:/app/data \
louislam/uptime-kuma:1
# → open http://your-server:3001
# → add monitors:
# - HTTPS: myapp.com (check every 60s)
# - TCP: db server port 5432
# - Docker container health
# → set up alerts: Telegram, Slack, Discord, Email
Simple Health Check Endpoint
The configuration file looks like this:
// Express.js health check
app.get('/health', async (req, res) => {
const health = {
status: 'ok',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
checks: {}
};
// Check database
try {
await db.query('SELECT 1');
health.checks.database = 'ok';
} catch (err) {
health.status = 'degraded';
health.checks.database = 'error';
}
// Check Redis
try {
await redis.ping();
health.checks.redis = 'ok';
} catch (err) {
health.status = 'degraded';
health.checks.redis = 'error';
}
// Check disk space
const diskFree = getDiskFreePercent();
health.checks.disk = diskFree > 10 ? 'ok' : 'warning';
const statusCode = health.status === 'ok' ? 200 : 503;
res.status(statusCode).json(health);
});
🛡️ Security Checklist — Before You Go Live
The configuration file looks like this:
Production Security Checklist ✅
🔐 SSL/TLS
- [ ] HTTPS everywhere (redirect HTTP → HTTPS)
- [ ] TLS 1.2+ only (disable TLS 1.0/1.1)
- [ ] HSTS header enabled
- [ ] Certificate auto-renewal working
🔒 Server
- [ ] SSH key only (no password login)
- [ ] Non-root user for app
- [ ] Firewall enabled (only 22, 80, 443)
- [ ] Unattended security updates
- [ ] Fail2ban installed
🛡️ Application
- [ ] Environment variables for secrets
- [ ] CORS configured properly
- [ ] Rate limiting on API
- [ ] Input validation & sanitization
- [ ] Security headers (X-Frame, CSP, etc.)
- [ ] Dependencies updated (npm audit)
📊 Monitoring
- [ ] Uptime monitoring active
- [ ] Error tracking (Sentry)
- [ ] Log rotation configured
- [ ] Alerts configured (Telegram/Slack)
💾 Backup
- [ ] Daily automated backups
- [ ] Backups stored off-server
- [ ] Restore tested recently
- [ ] Backup monitoring (alert on failure)
🗺️ Decision Flowchart — How Do You Pick a Host?
Sample code:
Start here: what kind of project do you have?
- Static site / SPA / Next.js?
- Using Next.js → Vercel (free)
- Static HTML → Cloudflare Pages (free, fastest)
- Anything else → Netlify (free, easiest)
- Full-stack app + DB?
- MVP / prototype?
└── Railway or Render (the easiest)
- Need to customize?
└── VPS + Docker Compose
- Need to scale globally?
└── Fly.io (multi-region)
- Enterprise?
- AWS/GCP/Azure + Kubernetes
- API only?
- Lightweight → Cloudflare Workers / Vercel Edge
- Node/Python → Railway or Fly.io
- Complex → VPS or Cloud
- ML / GPU workloads?
- Inference → Modal, Replicate, RunPod
- Training → Lambda Labs, Vast.ai
- Enterprise → AWS SageMaker, GCP Vertex
🔑 Key Takeaways
The central idea of this post is to “match the choice to the project's stage” rather than reaching for the biggest thing on day one, then upgrading the architecture as the real workload demands.
- Start simple, then work up — Vercel/Netlify → PaaS → VPS → Cloud
- HTTPS is free everywhere — Let's Encrypt, Caddy auto-SSL, Cloudflare
- Cloudflare = must-have — CDN + DNS + DDoS, free
- Caddy is simpler than Nginx — for projects that need little fine-tuning
- Zero-downtime deploy is the standard — a rolling update is the default
- Canary deploy for critical systems — it shrinks the blast radius
- Blue-Green for instant rollback — but it costs 2x the resources
- VPS + Docker Compose + Caddy = sweet spot — $5-15/mo buys production-grade
- Automate everything — CI/CD, backups, monitoring, SSL renewal
- Monitor before you deploy — no monitoring = you never learn it broke
📌 This is #21 in our DevOps & Full-Stack series
📚 The full series:
- Git Branching — Branching That Stays Organized
- CI/CD Pipeline — From Commit to Production
- Docker vs VMs — What a Container Is and Why You Need One
- API Lifecycle — How a Request Travels
- Kubernetes — Managing Containers at Production Scale
- Networking — DNS, TCP, Load Balancer, Firewall
- Infrastructure as Code — Terraform & Ansible
- Monitoring & Observability — Logs, Metrics, Traces
- Linux & Shell Essentials — The Basics Every DevOps Engineer Needs
- DevSecOps & Security — Shift Left, OWASP, Secrets
- Testing Strategies — Pyramid, TDD, Load, Chaos
- GitOps & ArgoCD — Git-driven Deployments
- Cloud Architecture — AWS, GCP, Azure Fundamentals
- SRE — Site Reliability Engineering
- GitHub Actions Workflow — Advanced CI/CD
- Automated Testing — Unit, Integration, E2E
- Database Design & SQL — Normalization, Indexing, Query Optimization
- Authentication & Authorization — JWT, OAuth, RBAC
- Web Architecture & Design Patterns — Monolith vs Microservices
- Frontend Performance & Modern Frameworks
- Deployment & Hosting Strategies — Vercel to Bare Metal ← 📍 you are here