deploy
Some checks failed
Build & Deploy KLZ Cables / build-and-deploy (push) Failing after 1m51s

This commit is contained in:
2026-01-28 19:05:20 +01:00
parent 6115e0e0d4
commit 21b16a5e6c
9 changed files with 1335 additions and 229 deletions

View File

@@ -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
View 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
View 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/)