Some checks failed
🧪 CI (QA) / 🧪 Quality Assurance (push) Failing after 1m3s
- 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
269 lines
7.2 KiB
Markdown
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) |