wip
This commit is contained in:
269
DEPLOYMENT.md
Normal file
269
DEPLOYMENT.md
Normal file
@@ -0,0 +1,269 @@
|
||||
# Hetzner Deployment Guide
|
||||
|
||||
Complete deployment plan for your Astro blog on Hetzner using Docker, with your existing Gitea + Woodpecker setup.
|
||||
|
||||
## 🏗️ Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
||||
│ Hetzner VM │ │ Caddy Reverse │ │ Plausible │
|
||||
│ (Ubuntu) │◄──►│ Proxy │◄──►│ Analytics │
|
||||
│ │ │ (SSL/HTTP2) │ │ (Existing) │
|
||||
└────────┬────────┘ └──────────────────┘ └─────────────────┘
|
||||
│
|
||||
│ Docker Network
|
||||
│
|
||||
┌────▼─────┐ ┌──────────────┐ ┌──────────────┐
|
||||
│ Nginx │ │ Redis │ │ Website │
|
||||
│ Static │ │ Cache │ │ (Astro) │
|
||||
│ Server │ │ │ │ │
|
||||
└──────────┘ └──────────────┘ └──────────────┘
|
||||
```
|
||||
|
||||
## 📁 Project Structure
|
||||
|
||||
```
|
||||
mintel.me/
|
||||
├── docker/
|
||||
│ ├── Dockerfile # Multi-stage build
|
||||
│ ├── nginx.conf # Nginx config with caching
|
||||
│ └── Caddyfile # SSL reverse proxy
|
||||
├── docker-compose.yml # Local & Hetzner deployment
|
||||
├── .woodpecker.yml # CI/CD pipeline
|
||||
├── deploy.sh # Manual deployment script
|
||||
├── src/
|
||||
│ ├── utils/
|
||||
│ │ └── cache/ # Cache abstraction layer
|
||||
│ │ ├── interfaces.ts
|
||||
│ │ ├── memory-adapter.ts
|
||||
│ │ ├── redis-adapter.ts
|
||||
│ │ ├── composite-adapter.ts
|
||||
│ │ └── index.ts
|
||||
│ ├── components/
|
||||
│ │ └── Analytics.astro # Plausible integration
|
||||
│ └── layouts/
|
||||
│ └── BaseLayout.astro # Includes analytics
|
||||
└── package.json # Added ioredis dependency
|
||||
```
|
||||
|
||||
## 🚀 Deployment Options
|
||||
|
||||
### Option 1: Manual Deployment (Quick Start)
|
||||
|
||||
```bash
|
||||
# 1. Provision Hetzner VM (Ubuntu 22.04)
|
||||
# 2. Install Docker + Docker Compose
|
||||
ssh root@YOUR_HETZNER_IP "apt update && apt install -y docker.io docker-compose"
|
||||
|
||||
# 3. Copy files to server
|
||||
scp -r . root@YOUR_HETZNER_IP:/opt/mintel
|
||||
|
||||
# 4. Set environment variables
|
||||
ssh root@YOUR_HETZNER_IP "cat > /opt/mintel/.env << EOF
|
||||
DOMAIN=mintel.me
|
||||
ADMIN_EMAIL=admin@mintel.me
|
||||
REDIS_URL=redis://redis:6379
|
||||
EOF"
|
||||
|
||||
# 5. Deploy
|
||||
ssh root@YOUR_HETZNER_IP "cd /opt/mintel && ./deploy.sh"
|
||||
```
|
||||
|
||||
### Option 2: CI/CD with Woodpecker + Gitea (Recommended)
|
||||
|
||||
Your existing Woodpecker + Gitea setup will handle automatic deployments:
|
||||
|
||||
1. **Configure Woodpecker secrets** in your Gitea repo:
|
||||
- `SSH_PRIVATE_KEY` - Private key for Hetzner access
|
||||
- `DEPLOY_HOST` - Hetzner server IP
|
||||
- `DEPLOY_USER` - Usually `root`
|
||||
- `DOMAIN` - Your domain
|
||||
- `ADMIN_EMAIL` - Contact email
|
||||
|
||||
2. **Push to main branch** triggers automatic deployment
|
||||
|
||||
## 🔧 Environment Variables
|
||||
|
||||
See [ENV_SETUP.md](./ENV_SETUP.md) for detailed configuration guide.
|
||||
|
||||
**Quick setup:**
|
||||
```bash
|
||||
cp .env.example .env
|
||||
# Edit .env with your values
|
||||
```
|
||||
|
||||
**Required variables:**
|
||||
- `DOMAIN` - Your domain (e.g., mintel.me)
|
||||
- `ADMIN_EMAIL` - SSL certificate contact
|
||||
|
||||
**Optional variables:**
|
||||
- `REDIS_URL` - Redis connection (default: `redis://redis:6379`)
|
||||
- `PLAUSIBLE_DOMAIN` - Analytics domain
|
||||
- `PLAUSIBLE_SCRIPT_URL` - Plausible script URL
|
||||
|
||||
## 🎯 Performance Optimization
|
||||
|
||||
### Redis Caching Strategy
|
||||
|
||||
The cache abstraction layer provides:
|
||||
|
||||
```typescript
|
||||
// Usage in your Astro pages
|
||||
import { withCache } from '../utils/cache';
|
||||
|
||||
// Cache expensive operations
|
||||
const blogPosts = await withCache(
|
||||
'blog:posts',
|
||||
() => fetchBlogPosts(),
|
||||
3600 // 1 hour TTL
|
||||
);
|
||||
```
|
||||
|
||||
**Cache Layers:**
|
||||
1. **Memory Cache** - Fast, single instance
|
||||
2. **Redis Cache** - Distributed, multi-instance
|
||||
3. **Composite Cache** - Auto-fallback Redis → Memory
|
||||
|
||||
### Nginx Caching
|
||||
|
||||
- **Static assets**: 1 year cache
|
||||
- **HTML**: No cache (always fresh)
|
||||
- **Gzip/Brotli**: Enabled
|
||||
|
||||
### Caddy Benefits
|
||||
|
||||
- **Automatic SSL** with Let's Encrypt
|
||||
- **HTTP/2** out of the box
|
||||
- **Automatic redirects** HTTP → HTTPS
|
||||
- **Security headers** configured
|
||||
|
||||
## 📊 Analytics Integration
|
||||
|
||||
### Plausible Setup
|
||||
|
||||
Your existing Plausible instance is already self-hosted. Integration:
|
||||
|
||||
1. **Update Analytics.astro** with your Plausible domain:
|
||||
```astro
|
||||
src="https://plausible.yourdomain.com/js/script.js"
|
||||
```
|
||||
|
||||
2. **Add subdomain** to Caddyfile:
|
||||
```caddy
|
||||
analytics.mintel.me {
|
||||
reverse_proxy http://YOUR_PLAUSIBLE_SERVER:8000
|
||||
}
|
||||
```
|
||||
|
||||
3. **Track custom events**:
|
||||
```javascript
|
||||
window.plausible('Page Load', { props: { loadTime: 123 }});
|
||||
```
|
||||
|
||||
## 🔐 Security
|
||||
|
||||
### Caddy Security Headers
|
||||
```caddy
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
|
||||
X-Frame-Options "SAMEORIGIN"
|
||||
X-Content-Type-Options "nosniff"
|
||||
X-XSS-Protection "1; mode=block"
|
||||
Server "mintel"
|
||||
}
|
||||
```
|
||||
|
||||
### Docker Security
|
||||
- Non-root user in containers
|
||||
- Read-only filesystem where possible
|
||||
- Limited capabilities
|
||||
|
||||
## 📈 Monitoring & Health
|
||||
|
||||
### Health Check Endpoint
|
||||
```
|
||||
https://mintel.me/health
|
||||
```
|
||||
Returns: `healthy`
|
||||
|
||||
### Docker Commands
|
||||
```bash
|
||||
# View logs
|
||||
docker logs -f mintel-website
|
||||
|
||||
# Check Redis
|
||||
docker exec -it mintel-redis redis-cli ping
|
||||
|
||||
# Restart services
|
||||
docker-compose restart
|
||||
|
||||
# Update deployment
|
||||
docker-compose pull && docker-compose up -d
|
||||
```
|
||||
|
||||
## 🔄 CI/CD Pipeline Flow
|
||||
|
||||
1. **Push to Gitea** → Woodpecker triggers
|
||||
2. **Test** → Run smoke tests
|
||||
3. **Build** → Create Docker image
|
||||
4. **Push** → Registry (optional)
|
||||
5. **Deploy** → SSH to Hetzner, pull & restart
|
||||
|
||||
## 🚨 Troubleshooting
|
||||
|
||||
### SSL Issues
|
||||
```bash
|
||||
# Check Caddy logs
|
||||
docker logs mintel-caddy
|
||||
|
||||
# Force certificate renewal
|
||||
docker exec mintel-caddy caddy renew
|
||||
```
|
||||
|
||||
### Redis Connection
|
||||
```bash
|
||||
# Test Redis
|
||||
docker exec mintel-redis redis-cli ping
|
||||
|
||||
# Check Redis logs
|
||||
docker logs mintel-redis
|
||||
```
|
||||
|
||||
### Cache Issues
|
||||
```bash
|
||||
# Clear all cache
|
||||
docker exec mintel-redis redis-cli FLUSHALL
|
||||
```
|
||||
|
||||
## 📦 Dependencies
|
||||
|
||||
### Required
|
||||
- `ioredis` - Redis client
|
||||
- Already have: Astro, React, Tailwind
|
||||
|
||||
### Docker Images
|
||||
- `nginx:alpine` - Web server
|
||||
- `redis:7-alpine` - Cache
|
||||
- `caddy:2-alpine` - Reverse proxy
|
||||
|
||||
## 🎯 Next Steps
|
||||
|
||||
1. ✅ **Configure** environment variables
|
||||
2. ✅ **Test** locally with `docker-compose up`
|
||||
3. ✅ **Setup** Woodpecker secrets
|
||||
4. ✅ **Deploy** to Hetzner
|
||||
5. ✅ **Monitor** with Plausible analytics
|
||||
|
||||
## 💡 Tips
|
||||
|
||||
- **First deployment**: Use manual `deploy.sh` to verify everything works
|
||||
- **Updates**: CI/CD handles everything on git push
|
||||
- **Rollback**: `docker-compose down && docker-compose up -d` with previous image
|
||||
- **Scaling**: Add more web containers, Redis handles distributed cache
|
||||
|
||||
---
|
||||
|
||||
**Total setup time**: ~30 minutes
|
||||
**Maintenance**: Near zero (automated)
|
||||
**Performance**: Excellent (Redis + Nginx caching)
|
||||
**Analytics**: Privacy-focused (Plausible)
|
||||
Reference in New Issue
Block a user