Files
mintel.me/apps/web/DEPLOYMENT.md
Marc Mintel 103d71851c
Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
chore: overhaul infrastructure and integrate @mintel packages
- Restructure to pnpm monorepo (site moved to apps/web)
- Integrate @mintel/tsconfig, @mintel/eslint-config, @mintel/husky-config
- Implement Docker service architecture (Varnish, Directus, Gatekeeper)
- Setup environment-aware Gitea Actions deployment
2026-02-05 14:18:51 +01:00

269 lines
7.2 KiB
Markdown

# 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)