Deployment Strategies¶
Navigation: Home → Deployment → Deployment Strategies
Overview¶
This guide compares various deployment strategies for the Simon Stijnen Portfolio website, from simple cloud hosting to self-hosted Kubernetes clusters. Each option is evaluated based on cost, complexity, scalability, and maintenance requirements.
Table of Contents¶
- Deployment Comparison
- Cloud Platform Deployment
- Container Orchestration
- Self-Hosted Deployment
- Serverless Deployment
- Edge Deployment
- Choosing a Strategy
Deployment Comparison¶
Quick Comparison Matrix¶
| Platform | Cost | Complexity | Scalability | Maintenance | Best For |
|---|---|---|---|---|---|
| Vercel | Free-$$$ | ⭐ Very Low | ⭐⭐⭐⭐⭐ Excellent | ⭐ Minimal | Next.js apps, quick start |
| Netlify | Free-$$ | ⭐ Very Low | ⭐⭐⭐⭐ Great | ⭐ Minimal | Static sites, JAMstack |
| Railway | $-$$ | ⭐⭐ Low | ⭐⭐⭐ Good | ⭐⭐ Low | Docker containers |
| Fly.io | $-$$ | ⭐⭐ Low | ⭐⭐⭐⭐ Great | ⭐⭐ Low | Global edge deployment |
| AWS | $$-$$$ | ⭐⭐⭐⭐ High | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐ High | Enterprise, complex apps |
| DigitalOcean | $-$$ | ⭐⭐⭐ Medium | ⭐⭐⭐ Good | ⭐⭐⭐ Medium | Cost-effective VPS |
| Kubernetes | $$-$$$ | ⭐⭐⭐⭐⭐ Very High | ⭐⭐⭐⭐⭐ Excellent | ⭐⭐⭐⭐⭐ Very High | Large-scale, multi-service |
| Self-Hosted | $-$$ | ⭐⭐⭐⭐ High | ⭐⭐ Limited | ⭐⭐⭐⭐ High | Learning, full control |
Cost Scale: Free / $ (<$20/mo) / $$ ($20-100/mo) / $$$ (>$100/mo)
Decision Tree¶
graph TD
Start{Choose Deployment} --> NextJS{Next.js<br/>optimized?}
NextJS -->|Yes| Vercel[Vercel<br/>Zero config, excellent DX]
NextJS -->|No preference| Docker{Want<br/>Docker?}
Docker -->|Yes| Container{Scale<br/>requirements?}
Docker -->|No| Static{Static or<br/>server-rendered?}
Container -->|Single region| Railway[Railway/Fly.io<br/>Simple Docker hosting]
Container -->|Multi-region| AWS[AWS/GCP<br/>Enterprise-grade]
Container -->|Complex orchestration| K8s[Kubernetes<br/>Full control]
Static -->|Static only| Netlify[Netlify/Vercel<br/>CDN + static hosting]
Static -->|Server-rendered| VPS[VPS DigitalOcean<br/>Traditional hosting]
Start --> Budget{Budget?}
Budget -->|Free| Free[Vercel/Netlify<br/>Free tier]
Budget -->|Low $| Railway[Railway/Fly.io<br/>$5-20/mo]
Budget -->|Medium $$| DO[DigitalOcean<br/>$20-50/mo]
Budget -->|High $$$| AWS[AWS/GCP<br/>Unlimited scale]
style Vercel fill:#27ae60,color:#fff
style Railway fill:#9b59b6,color:#fff
style AWS fill:#f39c12,color:#fff
style K8s fill:#e74c3c,color:#fff
Cloud Platform Deployment¶
Vercel (Recommended for Next.js)¶
Best for: Next.js applications, rapid deployment, zero configuration
Advantages¶
✅ Native Next.js Support
- Built by Next.js creators
- Zero configuration needed
- Automatic optimizations
✅ Excellent Developer Experience
- Git integration
- Automatic deployments
- Preview deployments for PRs
- Instant rollbacks
✅ Performance
- Global Edge Network
- Automatic image optimization
- Built-in CDN
- Edge middleware support
✅ Free Tier
- Generous limits
- Custom domains
- SSL certificates
- Analytics included
Deployment Process¶
# Install Vercel CLI
npm install -g vercel
# Deploy from local directory
cd /path/to/website
vercel
# Deploy production
vercel --prod
# Deploy from GitHub (preferred)
# 1. Push to GitHub
# 2. Import project at vercel.com
# 3. Configure (auto-detected for Next.js)
# 4. Deploy
Configuration¶
vercel.json (optional):
{
"buildCommand": "npm run build",
"outputDirectory": ".next",
"framework": "nextjs",
"regions": ["iad1"],
"env": {
"NEXT_PUBLIC_SITE_URL": "https://simonstijnen.com"
},
"headers": [
{
"source": "/(.*)",
"headers": [
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-Frame-Options",
"value": "DENY"
}
]
}
]
}
Pricing¶
| Tier | Price | Limits |
|---|---|---|
| Hobby | Free | 100GB bandwidth, 6,000 build minutes |
| Pro | $20/mo | 1TB bandwidth, unlimited builds |
| Enterprise | Custom | Dedicated infrastructure |
Netlify¶
Best for: Static sites, JAMstack applications
Advantages¶
✅ Static Site Excellence
- CDN distribution
- Atomic deploys
- Instant rollbacks
✅ Developer Tools
- Split testing
- Form handling
- Functions (serverless)
✅ Generous Free Tier
- 100GB bandwidth
- Unlimited sites
- Custom domains
Deployment Process¶
# Install Netlify CLI
npm install -g netlify-cli
# Login
netlify login
# Initialize
netlify init
# Deploy
netlify deploy --prod
# Or drag and drop build output at netlify.com
npm run build
# Upload .next folder
Configuration¶
netlify.toml:
[build]
command = "npm run build"
publish = ".next"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
Referrer-Policy = "strict-origin-when-cross-origin"
Pricing¶
| Tier | Price | Limits |
|---|---|---|
| Free | $0 | 100GB bandwidth, 300 build minutes |
| Pro | $19/mo | 1TB bandwidth, unlimited builds |
| Enterprise | Custom | SLA, priority support |
Railway¶
Best for: Docker containers, PostgreSQL databases, simple deployment
Advantages¶
✅ Docker Native
- Uses your Dockerfile
- No modifications needed
- Database included
✅ Developer Experience
- Simple CLI
- Git integration
- Environment variables
✅ Pricing
- Pay for what you use
- No minimum cost
- ~$5-10/mo typical
Deployment Process¶
# Install Railway CLI
npm install -g @railway/cli
# Login
railway login
# Initialize project
railway init
# Link to existing project
railway link
# Deploy
railway up
# Or connect GitHub repository
# 1. Go to railway.app
# 2. New Project → Deploy from GitHub
# 3. Select repository
# 4. Auto-detects Dockerfile
# 5. Deploy
Configuration¶
railway.json:
{
"build": {
"builder": "DOCKERFILE",
"dockerfilePath": "Dockerfile"
},
"deploy": {
"numReplicas": 1,
"sleepApplication": false,
"restartPolicyType": "ON_FAILURE",
"restartPolicyMaxRetries": 10
}
}
Pricing¶
- Free Trial: $5 credit
- Usage-Based: ~$0.000463/GB-hour (RAM), $0.000231/vCPU-hour
- Typical Cost: $5-20/mo for small app
Fly.io¶
Best for: Global edge deployment, Docker containers, low latency
Advantages¶
✅ Global Edge
- Deploy to multiple regions
- Automatic routing to nearest region
- Built-in load balancing
✅ Docker Support
- Uses your Dockerfile
- Multi-region deployments
- Persistent volumes
✅ Performance
- Anycast routing
- Fast cold starts
- Close to users
Deployment Process¶
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Login
fly auth login
# Launch app (interactive setup)
fly launch
# Deploy
fly deploy
# Scale to multiple regions
fly regions add lhr # London
fly regions add syd # Sydney
fly regions add iad # US East
# Scale instances
fly scale count 2
# View status
fly status
Configuration¶
fly.toml:
app = "personal-website"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[env]
PORT = "3000"
NODE_ENV = "production"
[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 = 512
Pricing¶
- Free Tier: 3 shared-cpu VMs, 3GB persistent storage
- Hobby: ~$5-10/mo for basic app
- Paid: $1.94/mo per 256MB RAM
Container Orchestration¶
Amazon ECS/Fargate¶
Best for: AWS ecosystem, serverless containers, enterprise
Architecture¶
graph TD
A[ALB<br/>Load Balancer] --> B[ECS Service]
B --> C[Fargate Task 1]
B --> D[Fargate Task 2]
B --> E[Fargate Task 3]
C --> F[ECR<br/>Docker Registry]
D --> F
E --> F
G[Auto Scaling] -.->|CPU/Memory| B
H[CloudWatch] -.->|Logs| C
H -.->|Logs| D
H -.->|Logs| E
style A fill:#f39c12,color:#fff
style B fill:#3498db,color:#fff
style F fill:#e74c3c,color:#fff
Deployment Process¶
# Install AWS CLI
aws configure
# Create ECR repository
aws ecr create-repository --repository-name personal-website
# Build and push Docker image
aws ecr get-login-password --region us-east-1 | \
docker login --username AWS --password-stdin <account-id>.dkr.ecr.us-east-1.amazonaws.com
docker build -t personal-website .
docker tag personal-website:latest <account-id>.dkr.ecr.us-east-1.amazonaws.com/personal-website:latest
docker push <account-id>.dkr.ecr.us-east-1.amazonaws.com/personal-website:latest
# Create ECS cluster
aws ecs create-cluster --cluster-name personal-website-cluster
# Register task definition
aws ecs register-task-definition --cli-input-json file://task-definition.json
# Create service
aws ecs create-service \
--cluster personal-website-cluster \
--service-name website \
--task-definition personal-website:1 \
--desired-count 2 \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[subnet-xxx],securityGroups=[sg-xxx],assignPublicIp=ENABLED}"
Task Definition¶
task-definition.json:
{
"family": "personal-website",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [
{
"name": "website",
"image": "<account-id>.dkr.ecr.us-east-1.amazonaws.com/personal-website:latest",
"portMappings": [
{
"containerPort": 3000,
"protocol": "tcp"
}
],
"environment": [
{
"name": "NODE_ENV",
"value": "production"
}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/personal-website",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}
]
}
Pricing (Fargate)¶
- vCPU: $0.04048/hour
- Memory: $0.004445/GB/hour
- Example: 0.25 vCPU + 0.5GB = ~$10/mo
Kubernetes¶
Best for: Large-scale applications, multi-service architectures, full control
Architecture¶
graph TD
A[Ingress<br/>nginx/traefik] --> B[Service<br/>LoadBalancer]
B --> C[Deployment]
C --> D[Pod 1<br/>website:latest]
C --> E[Pod 2<br/>website:latest]
C --> F[Pod 3<br/>website:latest]
G[HPA<br/>Horizontal Pod Autoscaler] -.->|Scale| C
H[ConfigMap] -.->|Config| C
I[Secret] -.->|Credentials| C
J[PVC<br/>Persistent Volume] -.->|Storage| C
style A fill:#f39c12,color:#fff
style B fill:#3498db,color:#fff
style C fill:#9b59b6,color:#fff
Kubernetes Manifests¶
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: personal-website
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: personal-website
template:
metadata:
labels:
app: personal-website
spec:
containers:
- name: website
image: ghcr.io/simonstnn/website:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: personal-website
namespace: production
spec:
selector:
app: personal-website
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: personal-website
namespace: production
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- simonstijnen.com
secretName: website-tls
rules:
- host: simonstijnen.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: personal-website
port:
number: 80
hpa.yaml:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: personal-website-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: personal-website
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Deployment Commands¶
# Apply all manifests
kubectl apply -f k8s/
# Apply specific manifest
kubectl apply -f deployment.yaml
# View status
kubectl get pods -n production
kubectl get svc -n production
kubectl get ingress -n production
# View logs
kubectl logs -f deployment/personal-website -n production
# Scale manually
kubectl scale deployment/personal-website --replicas=5 -n production
# Rolling update
kubectl set image deployment/personal-website \
website=ghcr.io/simonstnn/website:v2.0.0 \
-n production
# Rollback
kubectl rollout undo deployment/personal-website -n production
# View rollout status
kubectl rollout status deployment/personal-website -n production
Managed Kubernetes Options¶
| Provider | Service | Pricing |
|---|---|---|
| AWS | EKS | $0.10/hour + node costs |
| Google Cloud | GKE | $0.10/hour + node costs |
| Azure | AKS | Free control plane + node costs |
| DigitalOcean | DOKS | $12/mo + node costs |
| Linode | LKE | Free + node costs |
Self-Hosted Deployment¶
DigitalOcean Droplet¶
Best for: Cost-effective VPS, full control, learning
Setup Process¶
# 1. Create Droplet
# - OS: Ubuntu 24.04 LTS
# - Size: $6/mo (1GB RAM)
# - Region: Nearest to users
# 2. SSH into server
ssh root@your-server-ip
# 3. Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# 4. Install Docker Compose
apt-get install -y docker-compose-plugin
# 5. Clone repository
git clone https://github.com/simonstnn/website.git
cd website
# 6. Run with Docker Compose
docker compose up -d
# 7. Setup Nginx reverse proxy
apt-get install -y nginx certbot python3-certbot-nginx
# 8. Configure Nginx
cat > /etc/nginx/sites-available/website <<'EOF'
server {
listen 80;
server_name simonstijnen.com www.simonstijnen.com;
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_cache_bypass $http_upgrade;
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;
}
}
EOF
# 9. Enable site
ln -s /etc/nginx/sites-available/website /etc/nginx/sites-enabled/
nginx -t
systemctl restart nginx
# 10. Setup SSL with Let's Encrypt
certbot --nginx -d simonstijnen.com -d www.simonstijnen.com
# 11. Setup auto-deployment
cat > /root/deploy.sh <<'EOF'
#!/bin/bash
cd /root/website
git pull origin main-v2
docker compose down
docker compose build
docker compose up -d
EOF
chmod +x /root/deploy.sh
Automated Updates¶
Using webhook:
# Install webhook
apt-get install -y webhook
# Create webhook script
cat > /root/webhook-deploy.sh <<'EOF'
#!/bin/bash
cd /root/website
git pull origin main-v2
docker compose up -d --build
EOF
chmod +x /root/webhook-deploy.sh
# Configure webhook
cat > /etc/webhook.conf <<'EOF'
[
{
"id": "deploy-website",
"execute-command": "/root/webhook-deploy.sh",
"command-working-directory": "/root/website",
"pass-arguments-to-command": [],
"trigger-rule": {
"match": {
"type": "payload-hash-sha256",
"secret": "YOUR_SECRET_HERE",
"parameter": {
"source": "header",
"name": "X-Hub-Signature-256"
}
}
}
}
]
EOF
# Start webhook
webhook -hooks /etc/webhook.conf -verbose -port 9000
GitHub webhook:
- Repository Settings → Webhooks → Add webhook
- Payload URL:
http://your-server:9000/hooks/deploy-website - Content type:
application/json - Secret: Same as in webhook.conf
- Events:
pushevents
Pricing¶
| Provider | Size | Price | Specs |
|---|---|---|---|
| DigitalOcean | Basic | $6/mo | 1GB RAM, 1 vCPU, 25GB SSD |
| DigitalOcean | Regular | $12/mo | 2GB RAM, 1 vCPU, 50GB SSD |
| Linode | Nanode | $5/mo | 1GB RAM, 1 vCPU, 25GB SSD |
| Vultr | Regular | $6/mo | 1GB RAM, 1 vCPU, 25GB SSD |
| Hetzner | CX11 | €4/mo | 2GB RAM, 1 vCPU, 20GB SSD |
Serverless Deployment¶
AWS Lambda + API Gateway¶
Best for: Sporadic traffic, cost optimization, AWS ecosystem
Architecture¶
graph LR
A[CloudFront CDN] --> B[API Gateway]
B --> C[Lambda Function]
C --> D[Next.js Server]
E[S3 Bucket] --> A
E -.->|Static Assets| A
style A fill:#f39c12,color:#fff
style B fill:#3498db,color:#fff
style C fill:#e74c3c,color:#fff
Note: Next.js 15 with App Router is not fully compatible with AWS Lambda. Consider using OpenNext or AWS Amplify for better Next.js support.
Edge Deployment¶
Cloudflare Pages/Workers¶
Best for: Global edge deployment, static sites with dynamic elements
Deployment¶
# Install Wrangler CLI
npm install -g wrangler
# Login
wrangler login
# Deploy
wrangler pages deploy .next
# Or connect GitHub repository
# 1. Go to pages.cloudflare.com
# 2. Connect repository
# 3. Configure build:
# Build command: npm run build
# Output directory: .next
# 4. Deploy
Pricing¶
- Free: Unlimited requests, 100k reads/day
- Paid: $5/mo for 10M requests
Choosing a Strategy¶
Use Case Recommendations¶
Personal Portfolio (Current Project)¶
Recommended: Vercel or Railway
- Vercel if you prioritize developer experience and Next.js optimization
- Railway if you want Docker deployment and database included
Small Business Website¶
Recommended: Vercel, Netlify, or DigitalOcean
- Simple deployment
- Reasonable costs
- Minimal maintenance
High-Traffic Application¶
Recommended: AWS ECS/Fargate or Kubernetes
- Auto-scaling
- High availability
- Enterprise support
Multi-Service Application¶
Recommended: Kubernetes or Docker Compose on VPS
- Multiple containers
- Service orchestration
- Database, cache, queue integration
Learning/Experimentation¶
Recommended: DigitalOcean or Self-hosted
- Full control
- Learn infrastructure
- Low cost
See Also¶
- Docker Guide - Docker fundamentals for any deployment
- CI/CD Pipeline - Automated deployment workflows
- Production Configuration - Production best practices
- Docker Compose - Local and VPS deployment
Next Steps¶
- Choose Platform: Select deployment strategy based on requirements
- Configure CI/CD: Set up automated deployments
- Production Setup: Review production configuration
- Deploy: Follow platform-specific guide above
Last Updated: February 2026
Platforms Tested: Vercel, Railway, Fly.io, DigitalOcean, AWS ECS
Next.js Version: 15.1.6
Docker Compatibility: All platforms