This commit is contained in:
@@ -1,214 +1,278 @@
|
||||
# Deployment Guide
|
||||
# KLZ Cables - Deployment Guide
|
||||
|
||||
This document describes the deployment setup for KLZ Cables website.
|
||||
This document explains the deployment process and environment variable management for the KLZ Cables application.
|
||||
|
||||
## Automatic Deployment (Gitea Actions)
|
||||
## Table of Contents
|
||||
|
||||
The project uses Gitea Actions for CI/CD. On every push to the `main` branch:
|
||||
- [Overview](#overview)
|
||||
- [Environment Variables](#environment-variables)
|
||||
- [Local Development](#local-development)
|
||||
- [Production Deployment](#production-deployment)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
|
||||
1. **Build**: Docker image is built with platform `linux/arm64`
|
||||
2. **Push**: Image is pushed to `registry.infra.mintel.me/mintel/klz-cables.com:latest`
|
||||
3. **Deploy**: SSH connection to production server pulls and restarts containers
|
||||
## Overview
|
||||
|
||||
### Workflow File
|
||||
The application uses a clean, robust, **fully automated** environment variable strategy:
|
||||
|
||||
Location: `.gitea/workflows/deploy.yml`
|
||||
- **Build-time variables**: Only `NEXT_PUBLIC_*` variables are baked into the client bundle
|
||||
- **Runtime variables**: All other variables are loaded from `.env` file at runtime
|
||||
- **Fully automated**: `.env` file is automatically generated from Gitea secrets on every deployment
|
||||
- **No manual steps**: No need to manually create or update `.env` files on the server
|
||||
- **No duplication**: Single source of truth for each environment
|
||||
|
||||
### Required Secrets
|
||||
### Architecture
|
||||
|
||||
Configure these in your Gitea repository settings:
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ CI/CD Workflow (.gitea/workflows/deploy.yml) │
|
||||
│ │
|
||||
│ 1. Build Docker image with NEXT_PUBLIC_* build args │
|
||||
│ 2. Push to registry │
|
||||
│ 3. SSH to server and run deploy.sh │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
↓
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Production Server (alpha.mintel.me) │
|
||||
│ │
|
||||
│ /home/deploy/sites/klz-cables.com/ │
|
||||
│ ├── .env ← Runtime environment vars │
|
||||
│ ├── docker-compose.yml ← Loads .env file │
|
||||
│ └── varnish/ │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
- `REGISTRY_USER` - Docker registry username
|
||||
- `REGISTRY_PASS` - Docker registry password
|
||||
- `ALPHA_SSH_KEY` - SSH private key for deployment user
|
||||
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Umami analytics website ID
|
||||
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Umami analytics script URL
|
||||
- `SENTRY_DSN` - Sentry/GlitchTip DSN for error tracking
|
||||
## Environment Variables
|
||||
|
||||
## Manual Deployment
|
||||
### Build-Time Variables (NEXT_PUBLIC_*)
|
||||
|
||||
If you need to deploy manually:
|
||||
These are embedded into the JavaScript bundle during build and are visible to the client:
|
||||
|
||||
### On the Production Server
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `NEXT_PUBLIC_BASE_URL` | ✅ Yes | Base URL of the application (e.g., `https://klz-cables.com`) |
|
||||
| `NEXT_PUBLIC_UMAMI_WEBSITE_ID` | ❌ No | Umami analytics website ID |
|
||||
| `NEXT_PUBLIC_UMAMI_SCRIPT_URL` | ❌ No | Umami analytics script URL (default: `https://analytics.infra.mintel.me/script.js`) |
|
||||
|
||||
**Important**: These must be provided as `--build-arg` when building the Docker image.
|
||||
|
||||
### Runtime Variables
|
||||
|
||||
These are loaded from the `.env` file at runtime and are only available on the server:
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `NODE_ENV` | ✅ Yes | Environment mode (`production`, `development`, `test`) |
|
||||
| `SENTRY_DSN` | ❌ No | GlitchTip/Sentry error tracking DSN |
|
||||
| `MAIL_HOST` | ❌ No | SMTP server hostname |
|
||||
| `MAIL_PORT` | ❌ No | SMTP server port (default: `587`) |
|
||||
| `MAIL_USERNAME` | ❌ No | SMTP authentication username |
|
||||
| `MAIL_PASSWORD` | ❌ No | SMTP authentication password |
|
||||
| `MAIL_FROM` | ❌ No | Email sender address |
|
||||
| `MAIL_RECIPIENTS` | ❌ No | Comma-separated list of recipient emails |
|
||||
| `REDIS_URL` | ❌ No | Redis connection URL (e.g., `redis://redis:6379/2`) |
|
||||
| `REDIS_KEY_PREFIX` | ❌ No | Redis key prefix (default: `klz:`) |
|
||||
| `VARNISH_CACHE_SIZE` | ❌ No | Varnish cache size (default: `256m`) |
|
||||
|
||||
## Local Development
|
||||
|
||||
### Setup
|
||||
|
||||
1. Copy the example environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Edit `.env` and fill in your local configuration:
|
||||
```bash
|
||||
NODE_ENV=development
|
||||
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||
# Add other variables as needed
|
||||
```
|
||||
|
||||
3. Install dependencies:
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
4. Run the development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Testing Docker Build Locally
|
||||
|
||||
```bash
|
||||
# SSH into the server
|
||||
ssh deploy@alpha.mintel.me
|
||||
# Build with build-time arguments
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_BASE_URL=http://localhost:3000 \
|
||||
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-id \
|
||||
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js \
|
||||
-t klz-cables:local .
|
||||
|
||||
# Run with runtime environment file
|
||||
docker run --env-file .env -p 3000:3000 klz-cables:local
|
||||
```
|
||||
|
||||
## Production Deployment
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. **Server Setup**: Ensure the production server has:
|
||||
- Docker and Docker Compose installed
|
||||
- Traefik reverse proxy running
|
||||
- Network `infra` created
|
||||
- Project directory exists: `/home/deploy/sites/klz-cables.com`
|
||||
|
||||
2. **Gitea Secrets**: Configure the following secrets in your Gitea repository:
|
||||
|
||||
**Registry Access:**
|
||||
- `REGISTRY_USER` - Docker registry username
|
||||
- `REGISTRY_PASS` - Docker registry password
|
||||
|
||||
**Build-Time Variables:**
|
||||
- `NEXT_PUBLIC_BASE_URL` - Production URL (e.g., `https://klz-cables.com`)
|
||||
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID` - Umami analytics ID
|
||||
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL` - Umami script URL
|
||||
|
||||
**Runtime Variables:**
|
||||
- `SENTRY_DSN` - Error tracking DSN
|
||||
- `MAIL_HOST` - SMTP server
|
||||
- `MAIL_PORT` - SMTP port
|
||||
- `MAIL_USERNAME` - SMTP username
|
||||
- `MAIL_PASSWORD` - SMTP password
|
||||
- `MAIL_FROM` - Sender email
|
||||
- `MAIL_RECIPIENTS` - Recipient emails (comma-separated)
|
||||
- `REDIS_URL` - Redis connection URL
|
||||
- `REDIS_KEY_PREFIX` - Redis key prefix
|
||||
|
||||
**Infrastructure:**
|
||||
- `REGISTRY_USER` - Docker registry username
|
||||
- `REGISTRY_PASS` - Docker registry password
|
||||
- `ALPHA_SSH_KEY` - SSH private key for deployment server
|
||||
|
||||
**Notifications:**
|
||||
- `GOTIFY_URL` - Gotify notification server URL
|
||||
- `GOTIFY_TOKEN` - Gotify application token
|
||||
|
||||
### Deployment Process
|
||||
|
||||
The deployment is **fully automated** via Gitea Actions:
|
||||
|
||||
1. **Trigger**: Push to `main` branch
|
||||
2. **Build**: Docker image is built with `NEXT_PUBLIC_*` build arguments
|
||||
3. **Push**: Image is pushed to private registry
|
||||
4. **Generate .env**: Workflow creates `.env` file from all Gitea secrets
|
||||
5. **Upload .env**: File is uploaded to server via SCP and secured (600 permissions)
|
||||
6. **Deploy**: SSH to server, pull image, and run docker-compose
|
||||
7. **Notify**: Send success/failure notification via Gotify
|
||||
|
||||
**No manual steps required!** The `.env` file is automatically created and deployed on every push.
|
||||
|
||||
### Manual Deployment
|
||||
|
||||
If you need to deploy manually (not recommended - use the automated workflow):
|
||||
|
||||
```bash
|
||||
# SSH to the server
|
||||
ssh root@alpha.mintel.me
|
||||
|
||||
# Navigate to the project directory
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
|
||||
# Ensure .env file exists (normally created by workflow)
|
||||
# If missing, you'll need to create it from Gitea secrets
|
||||
|
||||
# Pull the latest image
|
||||
docker compose pull
|
||||
docker pull registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||
|
||||
# Restart containers
|
||||
docker compose up -d --force-recreate --remove-orphans
|
||||
# Restart the services
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
|
||||
# Clean up old images
|
||||
docker image prune -f
|
||||
# Check logs
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
**Note**: Manual deployment requires the `.env` file to exist. The automated workflow creates this file from Gitea secrets. If deploying manually, you'll need to create it yourself using [`.env.production`](.env.production) as a template.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Workflow Not Triggering
|
||||
|
||||
1. Check Gitea Actions is enabled in repository settings
|
||||
2. Verify the workflow file syntax
|
||||
3. Check runner availability with label `docker`
|
||||
|
||||
### Build Failures
|
||||
|
||||
1. Check build logs in Gitea Actions tab
|
||||
2. Verify all secrets are configured correctly
|
||||
3. Ensure Dockerfile is valid
|
||||
|
||||
### Deployment Failures
|
||||
|
||||
1. Verify SSH key has correct permissions (600)
|
||||
2. Check deploy user has Docker permissions
|
||||
3. Verify registry credentials are correct
|
||||
4. Check server disk space: `df -h`
|
||||
|
||||
### Container Issues
|
||||
**Problem**: Build fails with "Environment validation failed"
|
||||
|
||||
**Solution**: Ensure all required `NEXT_PUBLIC_*` variables are provided as build arguments:
|
||||
```bash
|
||||
# Check container status
|
||||
docker compose ps
|
||||
|
||||
# View logs
|
||||
docker compose logs -f app
|
||||
docker compose logs -f varnish
|
||||
|
||||
# Check health
|
||||
docker compose exec app wget -O- http://localhost:3000/health
|
||||
docker build \
|
||||
--build-arg NEXT_PUBLIC_BASE_URL=https://klz-cables.com \
|
||||
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-id \
|
||||
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js \
|
||||
-t klz-cables .
|
||||
```
|
||||
|
||||
## Architecture
|
||||
### Runtime Failures
|
||||
|
||||
```
|
||||
Client
|
||||
↓
|
||||
Traefik (TLS termination, routing)
|
||||
↓
|
||||
Varnish (HTTP caching)
|
||||
↓
|
||||
Next.js App (port 3000)
|
||||
```
|
||||
|
||||
### Services
|
||||
|
||||
- **app**: Next.js application
|
||||
- **varnish**: HTTP cache layer
|
||||
- **traefik**: Reverse proxy (external network)
|
||||
|
||||
### Domains
|
||||
|
||||
- `klz-cables.com` - Production
|
||||
- `www.klz-cables.com` - Production (www)
|
||||
- `staging.klz-cables.com` - Staging
|
||||
|
||||
## Environment Variables
|
||||
|
||||
### Build-time (in Dockerfile/Workflow)
|
||||
|
||||
These variables are baked into the Docker image during the build process.
|
||||
|
||||
- `NEXT_PUBLIC_BASE_URL` (Required)
|
||||
- `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
|
||||
- `NEXT_PUBLIC_UMAMI_SCRIPT_URL`
|
||||
- `SENTRY_DSN`
|
||||
|
||||
### Runtime (in docker-compose.yml)
|
||||
|
||||
These variables are passed to the container at runtime.
|
||||
|
||||
- `NODE_ENV` (Defaults to `production`)
|
||||
- `NEXT_PUBLIC_BASE_URL` (Must be passed again at runtime for server-side use)
|
||||
- `SENTRY_DSN`
|
||||
- `MAIL_HOST`
|
||||
- `MAIL_PORT`
|
||||
- `MAIL_USERNAME`
|
||||
- `MAIL_PASSWORD`
|
||||
- `MAIL_FROM`
|
||||
- `MAIL_RECIPIENTS`
|
||||
- `REDIS_URL`
|
||||
- `REDIS_KEY_PREFIX`
|
||||
|
||||
### Local Development
|
||||
|
||||
For local development, use a `.env` file. The `docker-compose.override.yml` file automatically loads this file for the `app` service.
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Health Checks
|
||||
|
||||
- App: `https://klz-cables.com/health`
|
||||
- Varnish: Configured in docker-compose.yml
|
||||
|
||||
### Logs
|
||||
|
||||
```bash
|
||||
# Application logs
|
||||
docker compose logs -f app
|
||||
|
||||
# Varnish logs
|
||||
docker compose logs -f varnish
|
||||
|
||||
# All logs
|
||||
docker compose logs -f
|
||||
```
|
||||
|
||||
### Analytics
|
||||
|
||||
- Umami: Configured via environment variables
|
||||
- Sentry/GlitchTip: Error tracking
|
||||
|
||||
## Rollback
|
||||
|
||||
To rollback to a previous version:
|
||||
**Problem**: Container starts but application crashes
|
||||
|
||||
**Solution**: Check that the `.env` file exists and contains all required runtime variables:
|
||||
```bash
|
||||
# On the server
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
cat /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# Pull a specific version (if tagged)
|
||||
docker pull registry.infra.mintel.me/mintel/klz-cables.com:TAG
|
||||
|
||||
# Or rebuild from a specific commit
|
||||
# (requires access to the repository on the server)
|
||||
|
||||
# Restart with the older image
|
||||
docker compose up -d --force-recreate
|
||||
# Check container logs
|
||||
docker-compose logs app
|
||||
```
|
||||
|
||||
## Performance
|
||||
### Missing Environment Variables
|
||||
|
||||
### Cache Invalidation
|
||||
**Problem**: Features not working (email, analytics, etc.)
|
||||
|
||||
Varnish caches static assets. To clear cache:
|
||||
**Solution**:
|
||||
1. Check that the secret is configured in Gitea
|
||||
2. Verify the workflow includes it in the `.env` generation (see `.gitea/workflows/deploy.yml`)
|
||||
3. Redeploy to regenerate the `.env` file:
|
||||
```bash
|
||||
git commit --allow-empty -m "Trigger redeploy"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
4. Check the variable is loaded on the server:
|
||||
```bash
|
||||
ssh root@alpha.mintel.me
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
docker-compose exec app env | grep VARIABLE_NAME
|
||||
```
|
||||
|
||||
### Docker Compose Issues
|
||||
|
||||
**Problem**: `docker-compose up` fails with "env file not found"
|
||||
|
||||
**Solution**: The `.env` file should be automatically created by the workflow. If it's missing:
|
||||
1. Check the workflow logs for errors in the "📝 Preparing environment configuration" step
|
||||
2. Manually trigger a deployment by pushing to main
|
||||
3. If still missing, check server permissions and disk space
|
||||
|
||||
### Network Issues
|
||||
|
||||
**Problem**: Container can't connect to Traefik
|
||||
|
||||
**Solution**: Verify the `infra` network exists:
|
||||
```bash
|
||||
docker compose exec varnish varnishadm "ban req.url ~ ."
|
||||
docker network ls | grep infra
|
||||
docker network inspect infra
|
||||
```
|
||||
|
||||
### Cache Configuration
|
||||
## Security Best Practices
|
||||
|
||||
Edit `varnish/default.vcl` and restart:
|
||||
1. **Never commit `.env` files** with real credentials to git
|
||||
2. **Use Gitea/GitHub secrets** for CI/CD workflows
|
||||
3. **Restrict SSH key access** to deployment server
|
||||
4. **Rotate credentials regularly** (SMTP passwords, API keys, etc.)
|
||||
5. **Use HTTPS** for all production URLs
|
||||
6. **Monitor logs** for suspicious activity
|
||||
|
||||
```bash
|
||||
docker compose restart varnish
|
||||
```
|
||||
## Additional Resources
|
||||
|
||||
## Security
|
||||
|
||||
- All secrets are stored in Gitea repository settings
|
||||
- SSH key is injected at deployment time
|
||||
- Registry credentials are not stored in the repository
|
||||
- Deploy webhook requires secret token
|
||||
|
||||
## Support
|
||||
|
||||
For issues or questions:
|
||||
1. Check logs first
|
||||
2. Review this documentation
|
||||
3. Contact the development team
|
||||
- [Next.js Environment Variables](https://nextjs.org/docs/basic-features/environment-variables)
|
||||
- [Docker Compose Environment Variables](https://docs.docker.com/compose/environment-variables/)
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
|
||||
276
docs/ENV_MIGRATION.md
Normal file
276
docs/ENV_MIGRATION.md
Normal file
@@ -0,0 +1,276 @@
|
||||
# Environment Variables Migration Guide
|
||||
|
||||
This guide helps you migrate from the old fragile environment variable setup to the new clean, robust system.
|
||||
|
||||
## What Changed?
|
||||
|
||||
### Before (Fragile & Overkill)
|
||||
|
||||
❌ **Problems:**
|
||||
- Environment variables passed individually via SSH (12+ vars)
|
||||
- Duplicate definitions in Dockerfile, docker-compose.yml, and deploy.yml
|
||||
- Build args included runtime-only variables (SENTRY_DSN, MAIL_*, REDIS_*)
|
||||
- No single source of truth
|
||||
- Difficult to maintain and error-prone
|
||||
|
||||
```yaml
|
||||
# Old deploy.yml - FRAGILE!
|
||||
ssh root@alpha.mintel.me \
|
||||
"MAIL_FROM='${{ secrets.MAIL_FROM }}' \
|
||||
MAIL_HOST='${{ secrets.MAIL_HOST }}' \
|
||||
MAIL_PASSWORD='${{ secrets.MAIL_PASSWORD }}' \
|
||||
MAIL_PORT='${{ secrets.MAIL_PORT }}' \
|
||||
MAIL_RECIPIENTS='${{ secrets.MAIL_RECIPIENTS }}' \
|
||||
MAIL_USERNAME='${{ secrets.MAIL_USERNAME }}' \
|
||||
NEXT_PUBLIC_BASE_URL='${{ secrets.NEXT_PUBLIC_BASE_URL }}' \
|
||||
... (12+ variables) \
|
||||
/home/deploy/deploy.sh"
|
||||
```
|
||||
|
||||
### After (Clean & Robust)
|
||||
|
||||
✅ **Benefits:**
|
||||
- Single `.env` file on server contains all runtime variables
|
||||
- Only `NEXT_PUBLIC_*` variables passed as build args (3 vars)
|
||||
- Clear separation: build-time vs runtime
|
||||
- Easy to maintain and update
|
||||
- Single source of truth per environment
|
||||
|
||||
```yaml
|
||||
# New deploy.yml - CLEAN!
|
||||
ssh root@alpha.mintel.me "/home/deploy/deploy.sh"
|
||||
```
|
||||
|
||||
## Migration Steps
|
||||
|
||||
### Step 1: Update Gitea Secrets
|
||||
|
||||
**Remove these secrets** (no longer needed in CI/CD):
|
||||
- ❌ `MAIL_FROM`
|
||||
- ❌ `MAIL_HOST`
|
||||
- ❌ `MAIL_PASSWORD`
|
||||
- ❌ `MAIL_PORT`
|
||||
- ❌ `MAIL_RECIPIENTS`
|
||||
- ❌ `MAIL_USERNAME`
|
||||
- ❌ `NODE_ENV`
|
||||
- ❌ `REDIS_URL`
|
||||
- ❌ `REDIS_KEY_PREFIX`
|
||||
- ❌ `SENTRY_DSN` (from build args)
|
||||
|
||||
**Keep these secrets** (still needed for build):
|
||||
- ✅ `NEXT_PUBLIC_BASE_URL`
|
||||
- ✅ `NEXT_PUBLIC_UMAMI_WEBSITE_ID`
|
||||
- ✅ `NEXT_PUBLIC_UMAMI_SCRIPT_URL`
|
||||
- ✅ `REGISTRY_USER`
|
||||
- ✅ `REGISTRY_PASS`
|
||||
- ✅ `ALPHA_SSH_KEY`
|
||||
- ✅ `GOTIFY_URL`
|
||||
- ✅ `GOTIFY_TOKEN`
|
||||
|
||||
### Step 2: Create .env File on Server
|
||||
|
||||
SSH to the production server and create the `.env` file:
|
||||
|
||||
```bash
|
||||
ssh root@alpha.mintel.me
|
||||
|
||||
# Create .env file
|
||||
cat > /home/deploy/sites/klz-cables.com/.env << 'EOF'
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
NEXT_PUBLIC_BASE_URL=https://klz-cables.com
|
||||
|
||||
# Analytics
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-actual-id
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||
|
||||
# Error Tracking
|
||||
SENTRY_DSN=your-actual-dsn
|
||||
|
||||
# Email
|
||||
MAIL_HOST=smtp.eu.mailgun.org
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=your-actual-username
|
||||
MAIL_PASSWORD=your-actual-password
|
||||
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
|
||||
MAIL_RECIPIENTS=info@klz-cables.com
|
||||
|
||||
# Redis
|
||||
REDIS_URL=redis://redis:6379/2
|
||||
REDIS_KEY_PREFIX=klz:
|
||||
|
||||
# Varnish
|
||||
VARNISH_CACHE_SIZE=256m
|
||||
EOF
|
||||
|
||||
# Secure the file
|
||||
chmod 600 /home/deploy/sites/klz-cables.com/.env
|
||||
chown deploy:deploy /home/deploy/sites/klz-cables.com/.env
|
||||
```
|
||||
|
||||
**Important**: Replace all `your-actual-*` placeholders with real values from your old Gitea secrets.
|
||||
|
||||
### Step 3: Update Deployment Script
|
||||
|
||||
Update `/home/deploy/deploy.sh` to use the new approach:
|
||||
|
||||
```bash
|
||||
cat > /home/deploy/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="/home/deploy/sites/klz-cables.com"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ KLZ Cables - Deployment Script ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ ERROR: .env file not found at $PROJECT_DIR/.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔐 Logging into Docker registry..."
|
||||
echo "$REGISTRY_PASS" | docker login registry.infra.mintel.me -u "$REGISTRY_USER" --password-stdin
|
||||
|
||||
echo "🔄 Pulling latest image..."
|
||||
docker pull registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||
|
||||
echo "🔄 Stopping existing containers..."
|
||||
docker-compose down
|
||||
|
||||
echo "🚀 Starting new containers..."
|
||||
docker-compose up -d
|
||||
|
||||
echo "⏳ Waiting for services to be healthy..."
|
||||
sleep 10
|
||||
|
||||
echo "🔍 Checking service status..."
|
||||
docker-compose ps
|
||||
|
||||
echo ""
|
||||
echo "✅ Deployment complete!"
|
||||
EOF
|
||||
|
||||
chmod +x /home/deploy/deploy.sh
|
||||
```
|
||||
|
||||
### Step 4: Deploy Updated Code
|
||||
|
||||
The new code is already in the repository. Just push to trigger deployment:
|
||||
|
||||
```bash
|
||||
git push origin main
|
||||
```
|
||||
|
||||
The CI/CD workflow will:
|
||||
1. Build with only `NEXT_PUBLIC_*` build args
|
||||
2. Push to registry
|
||||
3. SSH to server and run deploy.sh
|
||||
4. Deploy.sh will use the `.env` file for runtime vars
|
||||
|
||||
### Step 5: Verify Migration
|
||||
|
||||
After deployment, verify everything works:
|
||||
|
||||
```bash
|
||||
# SSH to server
|
||||
ssh root@alpha.mintel.me
|
||||
|
||||
# Check containers are running
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
docker-compose ps
|
||||
|
||||
# Verify environment variables are loaded
|
||||
docker-compose exec app env | grep -E "NODE_ENV|NEXT_PUBLIC|MAIL|REDIS"
|
||||
|
||||
# Check application logs
|
||||
docker-compose logs -f app
|
||||
|
||||
# Test the website
|
||||
curl -I https://klz-cables.com
|
||||
```
|
||||
|
||||
## Comparison Table
|
||||
|
||||
| Aspect | Before | After |
|
||||
|--------|--------|-------|
|
||||
| **Gitea Secrets** | 15+ secrets | 8 secrets |
|
||||
| **Build Args** | 4 vars (including runtime-only) | 3 vars (NEXT_PUBLIC_* only) |
|
||||
| **Runtime Vars** | Passed via SSH command | Loaded from .env file |
|
||||
| **Maintenance** | Update in 3 places | Update in 1 place |
|
||||
| **Security** | Secrets in CI logs | Secrets only on server |
|
||||
| **Clarity** | Confusing duplication | Clear separation |
|
||||
| **Robustness** | Fragile SSH command | Robust file-based config |
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If you need to rollback to the old system:
|
||||
|
||||
1. Revert the changes in git:
|
||||
```bash
|
||||
git revert HEAD
|
||||
git push origin main
|
||||
```
|
||||
|
||||
2. Re-add the removed Gitea secrets
|
||||
|
||||
3. The old deployment will work again
|
||||
|
||||
## FAQ
|
||||
|
||||
**Q: Why keep `NEXT_PUBLIC_*` in both build args and .env file?**
|
||||
|
||||
A: `NEXT_PUBLIC_*` variables are special in Next.js - they're embedded into the JavaScript bundle at build time. They must be provided as build args. However, they're also needed at runtime for server-side rendering, so they're in the .env file too.
|
||||
|
||||
**Q: Can I update environment variables without rebuilding?**
|
||||
|
||||
A: Yes, for runtime-only variables (MAIL_*, REDIS_*, SENTRY_DSN, etc.). Just edit the `.env` file on the server and restart containers:
|
||||
```bash
|
||||
nano /home/deploy/sites/klz-cables.com/.env
|
||||
docker-compose down && docker-compose up -d
|
||||
```
|
||||
|
||||
For `NEXT_PUBLIC_*` variables, you need to rebuild the Docker image since they're baked into the client bundle.
|
||||
|
||||
**Q: Where should I store the .env file backup?**
|
||||
|
||||
A: Keep a secure backup outside the server:
|
||||
```bash
|
||||
# Download from server
|
||||
scp root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/.env \
|
||||
~/secure-backups/klz-cables.env.backup
|
||||
|
||||
# Store in password manager or encrypted storage
|
||||
```
|
||||
|
||||
**Q: What if I accidentally commit .env to git?**
|
||||
|
||||
A:
|
||||
1. Remove it immediately: `git rm .env && git commit -m "Remove .env"`
|
||||
2. Rotate all credentials in the file
|
||||
3. Update the `.gitignore` to ensure it doesn't happen again (already done)
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues during migration:
|
||||
|
||||
1. Check the logs: `docker-compose logs -f app`
|
||||
2. Verify .env file exists and has correct permissions
|
||||
3. Ensure all required variables are set
|
||||
4. Review [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed troubleshooting
|
||||
|
||||
## Summary
|
||||
|
||||
The new system is:
|
||||
- ✅ **Simpler**: One .env file instead of scattered variables
|
||||
- ✅ **Cleaner**: Clear separation of build vs runtime
|
||||
- ✅ **Robust**: File-based config instead of fragile SSH commands
|
||||
- ✅ **Secure**: Secrets stay on server, not in CI logs
|
||||
- ✅ **Maintainable**: Single source of truth per environment
|
||||
|
||||
You've successfully migrated from a fragile, overkill setup to a clean, production-ready configuration! 🎉
|
||||
349
docs/SERVER_SETUP.md
Normal file
349
docs/SERVER_SETUP.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# Server Setup Guide - KLZ Cables
|
||||
|
||||
This guide explains how to set up the production environment on the deployment server.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Server: `alpha.mintel.me`
|
||||
- User: `deploy` (with sudo access)
|
||||
- Docker and Docker Compose installed
|
||||
- Traefik reverse proxy running
|
||||
- External network `infra` created
|
||||
|
||||
## Initial Server Setup
|
||||
|
||||
### 1. Create Project Directory
|
||||
|
||||
```bash
|
||||
# SSH to the server as root or deploy user
|
||||
ssh root@alpha.mintel.me
|
||||
|
||||
# Create project directory
|
||||
mkdir -p /home/deploy/sites/klz-cables.com
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
```
|
||||
|
||||
### 2. Create Environment File
|
||||
|
||||
Create the `.env` file with production configuration:
|
||||
|
||||
```bash
|
||||
# Create .env file from template
|
||||
cat > /home/deploy/sites/klz-cables.com/.env << 'EOF'
|
||||
# ============================================================================
|
||||
# KLZ Cables - Production Environment Configuration
|
||||
# ============================================================================
|
||||
|
||||
# Application
|
||||
NODE_ENV=production
|
||||
NEXT_PUBLIC_BASE_URL=https://klz-cables.com
|
||||
|
||||
# Analytics (Umami)
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-umami-website-id
|
||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||
|
||||
# Error Tracking (GlitchTip/Sentry)
|
||||
SENTRY_DSN=https://your-sentry-dsn@errors.infra.mintel.me/project-id
|
||||
|
||||
# Email Configuration (Mailgun)
|
||||
MAIL_HOST=smtp.eu.mailgun.org
|
||||
MAIL_PORT=587
|
||||
MAIL_USERNAME=your-mailgun-username
|
||||
MAIL_PASSWORD=your-mailgun-password
|
||||
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
|
||||
MAIL_RECIPIENTS=info@klz-cables.com
|
||||
|
||||
# Redis Cache
|
||||
REDIS_URL=redis://redis:6379/2
|
||||
REDIS_KEY_PREFIX=klz:
|
||||
|
||||
# Varnish Cache Size
|
||||
VARNISH_CACHE_SIZE=256m
|
||||
EOF
|
||||
```
|
||||
|
||||
**Important**: Replace all placeholder values with actual production credentials.
|
||||
|
||||
### 3. Secure the Environment File
|
||||
|
||||
```bash
|
||||
# Set proper permissions (readable only by owner)
|
||||
chmod 600 /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# Verify ownership
|
||||
chown deploy:deploy /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# Verify permissions
|
||||
ls -la /home/deploy/sites/klz-cables.com/.env
|
||||
# Should show: -rw------- 1 deploy deploy
|
||||
```
|
||||
|
||||
### 4. Create Docker Compose File
|
||||
|
||||
```bash
|
||||
# Copy docker-compose.yml from repository
|
||||
# This should be done automatically by the deployment script
|
||||
# Or manually:
|
||||
cat > /home/deploy/sites/klz-cables.com/docker-compose.yml << 'EOF'
|
||||
services:
|
||||
varnish:
|
||||
image: varnish:7
|
||||
restart: always
|
||||
networks:
|
||||
- infra
|
||||
depends_on:
|
||||
- app
|
||||
command: >-
|
||||
varnishd
|
||||
-F
|
||||
-f /etc/varnish/default.vcl
|
||||
-s malloc,${VARNISH_CACHE_SIZE:-256m}
|
||||
volumes:
|
||||
- ./varnish/default.vcl:/etc/varnish/default.vcl:ro
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:80/health || wget --quiet --tries=1 --spider http://localhost:80/ || true"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.klz-cables-web.rule=(Host(\`klz-cables.com\`) || Host(\`www.klz-cables.com\`) || Host(\`staging.klz-cables.com\`)) && !PathPrefix(\`/.well-known/acme-challenge/\`)"
|
||||
- "traefik.http.routers.klz-cables-web.entrypoints=web"
|
||||
- "traefik.http.routers.klz-cables-web.middlewares=redirect-https"
|
||||
- "traefik.http.routers.klz-cables.rule=Host(\`klz-cables.com\`) || Host(\`www.klz-cables.com\`) || Host(\`staging.klz-cables.com\`)"
|
||||
- "traefik.http.routers.klz-cables.entrypoints=websecure"
|
||||
- "traefik.http.routers.klz-cables.tls.certresolver=le"
|
||||
- "traefik.http.routers.klz-cables.tls=true"
|
||||
- "traefik.http.routers.klz-cables.service=klz-cables"
|
||||
- "traefik.http.services.klz-cables.loadbalancer.server.port=80"
|
||||
- "traefik.http.services.klz-cables.loadbalancer.server.scheme=http"
|
||||
- "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Proto=https"
|
||||
- "traefik.http.middlewares.klz-forward.headers.customrequestheaders.X-Forwarded-Ssl=on"
|
||||
- "traefik.http.routers.klz-cables.middlewares=klz-forward,compress"
|
||||
|
||||
app:
|
||||
image: registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||
restart: always
|
||||
networks:
|
||||
- infra
|
||||
env_file:
|
||||
- .env
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || wget --quiet --tries=1 --spider http://localhost:3000/ || true"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 30s
|
||||
|
||||
networks:
|
||||
infra:
|
||||
external: true
|
||||
EOF
|
||||
```
|
||||
|
||||
### 5. Create Varnish Configuration
|
||||
|
||||
```bash
|
||||
# Create varnish directory
|
||||
mkdir -p /home/deploy/sites/klz-cables.com/varnish
|
||||
|
||||
# Copy varnish configuration from repository
|
||||
# This should be in the repository at varnish/default.vcl
|
||||
```
|
||||
|
||||
### 6. Create Deployment Script
|
||||
|
||||
```bash
|
||||
cat > /home/deploy/deploy.sh << 'EOF'
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
PROJECT_DIR="/home/deploy/sites/klz-cables.com"
|
||||
|
||||
echo "╔══════════════════════════════════════════════════════════════════════════════╗"
|
||||
echo "║ KLZ Cables - Deployment Script ║"
|
||||
echo "╚══════════════════════════════════════════════════════════════════════════════╝"
|
||||
echo ""
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
# Check if .env file exists
|
||||
if [ ! -f .env ]; then
|
||||
echo "❌ ERROR: .env file not found at $PROJECT_DIR/.env"
|
||||
echo "Please create the .env file before deploying."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "🔐 Logging into Docker registry..."
|
||||
echo "$REGISTRY_PASS" | docker login registry.infra.mintel.me -u "$REGISTRY_USER" --password-stdin
|
||||
|
||||
echo "🔄 Pulling latest image..."
|
||||
docker pull registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||
|
||||
echo "🔄 Stopping existing containers..."
|
||||
docker-compose down
|
||||
|
||||
echo "🚀 Starting new containers..."
|
||||
docker-compose up -d
|
||||
|
||||
echo "⏳ Waiting for services to be healthy..."
|
||||
sleep 10
|
||||
|
||||
echo "🔍 Checking service status..."
|
||||
docker-compose ps
|
||||
|
||||
echo ""
|
||||
echo "✅ Deployment complete!"
|
||||
echo ""
|
||||
echo "📊 Service URLs:"
|
||||
echo " • Production: https://klz-cables.com"
|
||||
echo " • Staging: https://staging.klz-cables.com"
|
||||
echo ""
|
||||
EOF
|
||||
|
||||
# Make script executable
|
||||
chmod +x /home/deploy/deploy.sh
|
||||
```
|
||||
|
||||
### 7. Configure Docker Registry Access
|
||||
|
||||
The deployment script needs registry credentials. These are passed as environment variables from the CI/CD workflow:
|
||||
|
||||
```bash
|
||||
# The workflow passes these variables:
|
||||
# REGISTRY_USER - from Gitea secrets
|
||||
# REGISTRY_PASS - from Gitea secrets
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
### Check Environment File
|
||||
|
||||
```bash
|
||||
# Verify .env file exists and has correct permissions
|
||||
ls -la /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# Check content (be careful not to expose secrets in logs)
|
||||
head -n 5 /home/deploy/sites/klz-cables.com/.env
|
||||
```
|
||||
|
||||
### Test Deployment Script
|
||||
|
||||
```bash
|
||||
# Run deployment script manually (requires registry credentials)
|
||||
REGISTRY_USER=your-user REGISTRY_PASS=your-pass /home/deploy/deploy.sh
|
||||
```
|
||||
|
||||
### Check Running Containers
|
||||
|
||||
```bash
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
docker-compose ps
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
### Verify Environment Variables in Container
|
||||
|
||||
```bash
|
||||
# Check that environment variables are loaded
|
||||
docker-compose exec app env | grep -E "NODE_ENV|NEXT_PUBLIC|MAIL|REDIS"
|
||||
```
|
||||
|
||||
## Updating Environment Variables
|
||||
|
||||
When you need to update environment variables:
|
||||
|
||||
```bash
|
||||
# 1. SSH to the server
|
||||
ssh root@alpha.mintel.me
|
||||
|
||||
# 2. Edit the .env file
|
||||
nano /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# 3. Restart the containers to pick up changes
|
||||
cd /home/deploy/sites/klz-cables.com
|
||||
docker-compose down
|
||||
docker-compose up -d
|
||||
|
||||
# 4. Verify the changes
|
||||
docker-compose logs -f app
|
||||
```
|
||||
|
||||
**Note**: Changes to `NEXT_PUBLIC_*` variables require rebuilding the Docker image, as they are baked into the client bundle at build time.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### .env File Not Found
|
||||
|
||||
```bash
|
||||
# Check if file exists
|
||||
ls -la /home/deploy/sites/klz-cables.com/.env
|
||||
|
||||
# If missing, create it from template
|
||||
cp .env.production /home/deploy/sites/klz-cables.com/.env
|
||||
# Then edit with actual values
|
||||
```
|
||||
|
||||
### Permission Denied
|
||||
|
||||
```bash
|
||||
# Fix ownership
|
||||
chown -R deploy:deploy /home/deploy/sites/klz-cables.com
|
||||
|
||||
# Fix .env permissions
|
||||
chmod 600 /home/deploy/sites/klz-cables.com/.env
|
||||
```
|
||||
|
||||
### Container Won't Start
|
||||
|
||||
```bash
|
||||
# Check logs
|
||||
docker-compose logs app
|
||||
|
||||
# Check if .env is loaded
|
||||
docker-compose config
|
||||
|
||||
# Verify environment variables
|
||||
docker-compose exec app env
|
||||
```
|
||||
|
||||
### Network Issues
|
||||
|
||||
```bash
|
||||
# Verify infra network exists
|
||||
docker network ls | grep infra
|
||||
|
||||
# If missing, create it
|
||||
docker network create infra
|
||||
```
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] `.env` file has `600` permissions (readable only by owner)
|
||||
- [ ] `.env` file is owned by `deploy:deploy`
|
||||
- [ ] `.env` file is NOT in git repository
|
||||
- [ ] All sensitive credentials are filled in
|
||||
- [ ] SSH keys are properly secured
|
||||
- [ ] Firewall rules are configured
|
||||
- [ ] HTTPS is enforced via Traefik
|
||||
- [ ] Regular backups of `.env` file are maintained
|
||||
|
||||
## Backup
|
||||
|
||||
Create a secure backup of the environment file:
|
||||
|
||||
```bash
|
||||
# Backup .env file
|
||||
cp /home/deploy/sites/klz-cables.com/.env \
|
||||
/home/deploy/backups/klz-cables.env.$(date +%Y%m%d)
|
||||
|
||||
# Set proper permissions on backup
|
||||
chmod 600 /home/deploy/backups/klz-cables.env.*
|
||||
```
|
||||
|
||||
## Additional Resources
|
||||
|
||||
- [Deployment Guide](./DEPLOYMENT.md)
|
||||
- [Docker Compose Documentation](https://docs.docker.com/compose/)
|
||||
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||
Reference in New Issue
Block a user