This commit is contained in:
86
.env.example
86
.env.example
@@ -1,24 +1,33 @@
|
|||||||
# WooCommerce API (Legacy - not currently used)
|
# ============================================================================
|
||||||
WOOCOMMERCE_URL=https://klz-cables.com
|
# KLZ Cables - Environment Configuration
|
||||||
WOOCOMMERCE_CONSUMER_KEY=
|
# ============================================================================
|
||||||
WOOCOMMERCE_CONSUMER_SECRET=
|
# Copy this file to .env for local development
|
||||||
WORDPRESS_APP_PASSWORD=
|
# For production, use .env.production as a template
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
# Umami Analytics
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
|
# Application Configuration
|
||||||
UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
# GlitchTip (Sentry protocol)
|
|
||||||
SENTRY_DSN=
|
|
||||||
# Client-side DSN should use the proxy path: https://[key]@[domain]/errors/[id]
|
|
||||||
NEXT_PUBLIC_SENTRY_DSN=
|
|
||||||
|
|
||||||
# Application
|
|
||||||
NODE_ENV=development
|
NODE_ENV=development
|
||||||
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
NEXT_PUBLIC_BASE_URL=http://localhost:3000
|
||||||
LOG_LEVEL=info
|
|
||||||
|
|
||||||
# SMTP Configuration
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Analytics (Umami)
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Optional: Leave empty to disable analytics
|
||||||
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
|
||||||
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Error Tracking (GlitchTip/Sentry)
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Optional: Leave empty to disable error tracking
|
||||||
|
SENTRY_DSN=
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Email Configuration (SMTP)
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Required for contact form functionality
|
||||||
MAIL_HOST=smtp.eu.mailgun.org
|
MAIL_HOST=smtp.eu.mailgun.org
|
||||||
MAIL_PORT=587
|
MAIL_PORT=587
|
||||||
MAIL_USERNAME=
|
MAIL_USERNAME=
|
||||||
@@ -26,6 +35,45 @@ MAIL_PASSWORD=
|
|||||||
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
|
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
|
||||||
MAIL_RECIPIENTS=info@klz-cables.com
|
MAIL_RECIPIENTS=info@klz-cables.com
|
||||||
|
|
||||||
# Redis Configuration (optional)
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
REDIS_URL=redis://redis:6379/2
|
# Redis Cache Configuration
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Optional: Leave empty to disable Redis caching
|
||||||
|
REDIS_URL=redis://localhost:6379/2
|
||||||
REDIS_KEY_PREFIX=klz:
|
REDIS_KEY_PREFIX=klz:
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Logging
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
LOG_LEVEL=info
|
||||||
|
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Varnish Cache (Docker only)
|
||||||
|
# ────────────────────────────────────────────────────────────────────────────
|
||||||
|
VARNISH_CACHE_SIZE=256m
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# IMPORTANT NOTES
|
||||||
|
# ============================================================================
|
||||||
|
#
|
||||||
|
# BUILD-TIME vs RUNTIME Variables:
|
||||||
|
# ─────────────────────────────────
|
||||||
|
# • NEXT_PUBLIC_* variables are baked into the client bundle at BUILD time
|
||||||
|
# They must be provided as --build-arg when building the Docker image
|
||||||
|
#
|
||||||
|
# • All other variables are used at RUNTIME only
|
||||||
|
# They are loaded from the .env file by docker-compose
|
||||||
|
#
|
||||||
|
# Docker Deployment:
|
||||||
|
# ──────────────────
|
||||||
|
# 1. Build-time: Only NEXT_PUBLIC_* vars are needed (via --build-arg)
|
||||||
|
# 2. Runtime: All vars are loaded from .env file on the server
|
||||||
|
# 3. The .env file should exist at: /home/deploy/sites/klz-cables.com/.env
|
||||||
|
#
|
||||||
|
# Security:
|
||||||
|
# ─────────
|
||||||
|
# • NEVER commit .env files with real credentials to git
|
||||||
|
# • Use Gitea/GitHub secrets for CI/CD workflows
|
||||||
|
# • Store production .env file securely on the server only
|
||||||
|
#
|
||||||
|
# ============================================================================
|
||||||
|
|||||||
34
.env.production
Normal file
34
.env.production
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# ============================================================================
|
||||||
|
# KLZ Cables - Production Environment Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# This file contains runtime environment variables for the production deployment.
|
||||||
|
# It should be placed on the production server at: /home/deploy/sites/klz-cables.com/.env
|
||||||
|
#
|
||||||
|
# IMPORTANT: This file contains sensitive data and should NEVER be committed to git.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Application
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_BASE_URL=https://klz-cables.com
|
||||||
|
|
||||||
|
# Analytics (Umami)
|
||||||
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=
|
||||||
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js
|
||||||
|
|
||||||
|
# Error Tracking (GlitchTip/Sentry)
|
||||||
|
SENTRY_DSN=
|
||||||
|
|
||||||
|
# Email Configuration (Mailgun)
|
||||||
|
MAIL_HOST=smtp.eu.mailgun.org
|
||||||
|
MAIL_PORT=587
|
||||||
|
MAIL_USERNAME=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_FROM=KLZ Cables <noreply@klz-cables.com>
|
||||||
|
MAIL_RECIPIENTS=info@klz-cables.com
|
||||||
|
|
||||||
|
# Redis Cache (optional)
|
||||||
|
REDIS_URL=redis://redis:6379/2
|
||||||
|
REDIS_KEY_PREFIX=klz:
|
||||||
|
|
||||||
|
# Varnish Cache Size (optional)
|
||||||
|
VARNISH_CACHE_SIZE=256m
|
||||||
@@ -6,8 +6,6 @@ on:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-deploy:
|
build-and-deploy:
|
||||||
# ────────────────────────────────────────────────
|
|
||||||
# WICHTIG: Kein "docker" mehr – sondern eines der neuen Labels
|
|
||||||
runs-on: docker
|
runs-on: docker
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -72,11 +70,10 @@ jobs:
|
|||||||
echo " Platform: linux/arm64"
|
echo " Platform: linux/arm64"
|
||||||
echo " Target: registry.infra.mintel.me/mintel/klz-cables.com:latest"
|
echo " Target: registry.infra.mintel.me/mintel/klz-cables.com:latest"
|
||||||
echo ""
|
echo ""
|
||||||
echo "📦 Build Arguments:"
|
echo "📦 Build Arguments (NEXT_PUBLIC_* only - baked into client bundle):"
|
||||||
|
echo " • NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL != '' && '***' || 'NOT SET' }}"
|
||||||
echo " • NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID != '' && '***' || 'NOT SET' }}"
|
echo " • NEXT_PUBLIC_UMAMI_WEBSITE_ID: ${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID != '' && '***' || 'NOT SET' }}"
|
||||||
echo " • NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL != '' && '***' || 'NOT SET' }}"
|
echo " • NEXT_PUBLIC_UMAMI_SCRIPT_URL: ${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL != '' && '***' || 'NOT SET' }}"
|
||||||
echo " • SENTRY_DSN: ${{ secrets.SENTRY_DSN != '' && '***' || 'NOT SET' }}"
|
|
||||||
echo " • NEXT_PUBLIC_BASE_URL: ${{ secrets.NEXT_PUBLIC_BASE_URL != '' && '***' || 'NOT SET' }}"
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "⏱️ Build started at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
echo "⏱️ Build started at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||||
echo ""
|
echo ""
|
||||||
@@ -86,10 +83,9 @@ jobs:
|
|||||||
docker buildx build \
|
docker buildx build \
|
||||||
--pull \
|
--pull \
|
||||||
--platform linux/arm64 \
|
--platform linux/arm64 \
|
||||||
|
--build-arg NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}" \
|
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID="${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}" \
|
||||||
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}" \
|
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL="${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}" \
|
||||||
--build-arg SENTRY_DSN="${{ secrets.SENTRY_DSN }}" \
|
|
||||||
--build-arg NEXT_PUBLIC_BASE_URL="${{ secrets.NEXT_PUBLIC_BASE_URL }}" \
|
|
||||||
-t registry.infra.mintel.me/mintel/klz-cables.com:latest \
|
-t registry.infra.mintel.me/mintel/klz-cables.com:latest \
|
||||||
--push .
|
--push .
|
||||||
|
|
||||||
@@ -141,36 +137,112 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Create .env file content
|
||||||
|
echo "📝 Preparing environment configuration..."
|
||||||
|
cat > /tmp/klz-cables.env << EOF
|
||||||
|
# ============================================================================
|
||||||
|
# KLZ Cables - Production Environment Configuration
|
||||||
|
# ============================================================================
|
||||||
|
# Auto-generated by CI/CD workflow
|
||||||
|
# DO NOT EDIT MANUALLY - Changes will be overwritten on next deployment
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Application
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}
|
||||||
|
|
||||||
|
# Analytics (Umami)
|
||||||
|
NEXT_PUBLIC_UMAMI_WEBSITE_ID=${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}
|
||||||
|
NEXT_PUBLIC_UMAMI_SCRIPT_URL=${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}
|
||||||
|
|
||||||
|
# Error Tracking (GlitchTip/Sentry)
|
||||||
|
SENTRY_DSN=${{ secrets.SENTRY_DSN }}
|
||||||
|
|
||||||
|
# Email Configuration (Mailgun)
|
||||||
|
MAIL_HOST=${{ secrets.MAIL_HOST }}
|
||||||
|
MAIL_PORT=${{ secrets.MAIL_PORT }}
|
||||||
|
MAIL_USERNAME=${{ secrets.MAIL_USERNAME }}
|
||||||
|
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
|
||||||
|
MAIL_FROM=${{ secrets.MAIL_FROM }}
|
||||||
|
MAIL_RECIPIENTS=${{ secrets.MAIL_RECIPIENTS }}
|
||||||
|
|
||||||
|
# Redis Cache
|
||||||
|
REDIS_URL=${{ secrets.REDIS_URL }}
|
||||||
|
REDIS_KEY_PREFIX=${{ secrets.REDIS_KEY_PREFIX }}
|
||||||
|
|
||||||
|
# Varnish Cache Size
|
||||||
|
VARNISH_CACHE_SIZE=256m
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "✅ Environment file prepared"
|
||||||
|
echo ""
|
||||||
|
|
||||||
# Execute deployment commands with detailed logging
|
# Execute deployment commands with detailed logging
|
||||||
echo "📡 Connecting to server and executing deployment commands..."
|
echo "📡 Connecting to server and executing deployment..."
|
||||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# SSH as root and use sudo to run deployment script as deploy user
|
# Copy .env file to server
|
||||||
# This works around the broken SSH output issue with deploy user
|
echo "📤 Uploading environment configuration..."
|
||||||
|
scp -o StrictHostKeyChecking=accept-new \
|
||||||
|
-o ServerAliveInterval=30 \
|
||||||
|
-o ServerAliveCountMax=3 \
|
||||||
|
-o ConnectTimeout=10 \
|
||||||
|
/tmp/klz-cables.env \
|
||||||
|
root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/.env
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "✅ Environment file uploaded successfully"
|
||||||
|
else
|
||||||
|
echo "❌ Failed to upload environment file"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# SSH to server and run deployment
|
||||||
|
echo "🚀 Executing deployment on server..."
|
||||||
ssh -o StrictHostKeyChecking=accept-new \
|
ssh -o StrictHostKeyChecking=accept-new \
|
||||||
-o ServerAliveInterval=30 \
|
-o ServerAliveInterval=30 \
|
||||||
-o ServerAliveCountMax=3 \
|
-o ServerAliveCountMax=3 \
|
||||||
-o ConnectTimeout=10 \
|
-o ConnectTimeout=10 \
|
||||||
root@alpha.mintel.me \
|
root@alpha.mintel.me bash << EOF
|
||||||
"MAIL_FROM='${{ secrets.MAIL_FROM }}' \
|
set -e
|
||||||
MAIL_HOST='${{ secrets.MAIL_HOST }}' \
|
|
||||||
MAIL_PASSWORD='${{ secrets.MAIL_PASSWORD }}' \
|
PROJECT_DIR="/home/deploy/sites/klz-cables.com"
|
||||||
MAIL_PORT='${{ secrets.MAIL_PORT }}' \
|
cd "\$PROJECT_DIR"
|
||||||
MAIL_RECIPIENTS='${{ secrets.MAIL_RECIPIENTS }}' \
|
|
||||||
MAIL_USERNAME='${{ secrets.MAIL_USERNAME }}' \
|
echo "🔒 Securing environment file..."
|
||||||
NEXT_PUBLIC_BASE_URL='${{ secrets.NEXT_PUBLIC_BASE_URL }}' \
|
chmod 600 .env
|
||||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID='${{ secrets.NEXT_PUBLIC_UMAMI_WEBSITE_ID }}' \
|
chown deploy:deploy .env
|
||||||
NEXT_PUBLIC_UMAMI_SCRIPT_URL='${{ secrets.NEXT_PUBLIC_UMAMI_SCRIPT_URL }}' \
|
|
||||||
NODE_ENV='${{ secrets.NODE_ENV }}' \
|
echo "🔐 Logging into Docker registry..."
|
||||||
SENTRY_DSN='${{ secrets.SENTRY_DSN }}' \
|
echo "${{ secrets.REGISTRY_PASS }}" | docker login registry.infra.mintel.me -u "${{ secrets.REGISTRY_USER }}" --password-stdin
|
||||||
REDIS_URL='${{ secrets.REDIS_URL }}' \
|
|
||||||
REDIS_KEY_PREFIX='${{ secrets.REDIS_KEY_PREFIX }}' \
|
echo "🔄 Pulling latest image..."
|
||||||
/home/deploy/deploy.sh"
|
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
|
||||||
|
|
||||||
DEPLOY_EXIT_CODE=$?
|
DEPLOY_EXIT_CODE=$?
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
|
# Clean up temporary env file
|
||||||
|
rm -f /tmp/klz-cables.env
|
||||||
|
|
||||||
if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
|
if [ $DEPLOY_EXIT_CODE -eq 0 ]; then
|
||||||
echo "✅ Deployment completed successfully at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
echo "✅ Deployment completed successfully at: $(date -u +'%Y-%m-%d %H:%M:%S UTC')"
|
||||||
else
|
else
|
||||||
@@ -181,6 +253,7 @@ jobs:
|
|||||||
echo " • Verify SSH key permissions on server"
|
echo " • Verify SSH key permissions on server"
|
||||||
echo " • Check disk space on target server"
|
echo " • Check disk space on target server"
|
||||||
echo " • Review docker compose configuration"
|
echo " • Review docker compose configuration"
|
||||||
|
echo " • Verify all required secrets are set in Gitea"
|
||||||
exit $DEPLOY_EXIT_CODE
|
exit $DEPLOY_EXIT_CODE
|
||||||
fi
|
fi
|
||||||
echo ""
|
echo ""
|
||||||
@@ -208,6 +281,8 @@ jobs:
|
|||||||
echo " • All secrets are masked (*** ) in logs"
|
echo " • All secrets are masked (*** ) in logs"
|
||||||
echo " • SSH keys are created with 600 permissions"
|
echo " • SSH keys are created with 600 permissions"
|
||||||
echo " • Passwords are never displayed in plain text"
|
echo " • Passwords are never displayed in plain text"
|
||||||
|
echo " • .env file is auto-generated from Gitea secrets"
|
||||||
|
echo " • .env file has 600 permissions on server"
|
||||||
echo ""
|
echo ""
|
||||||
echo "╔══════════════════════════════════════════════════════════════════════════════╗"
|
echo "╔══════════════════════════════════════════════════════════════════════════════╗"
|
||||||
if [ "${{ job.status }}" == "success" ]; then
|
if [ "${{ job.status }}" == "success" ]; then
|
||||||
|
|||||||
@@ -22,15 +22,15 @@ COPY . .
|
|||||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
|
|
||||||
|
# Build-time environment variables for Next.js
|
||||||
|
# These are baked into the client bundle during build
|
||||||
|
ARG NEXT_PUBLIC_BASE_URL
|
||||||
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||||
ARG SENTRY_DSN
|
|
||||||
ARG NEXT_PUBLIC_BASE_URL
|
|
||||||
|
|
||||||
|
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
||||||
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
ENV NEXT_PUBLIC_UMAMI_WEBSITE_ID=$NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
ENV NEXT_PUBLIC_UMAMI_SCRIPT_URL=$NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||||
ENV SENTRY_DSN=$SENTRY_DSN
|
|
||||||
ENV NEXT_PUBLIC_BASE_URL=$NEXT_PUBLIC_BASE_URL
|
|
||||||
|
|
||||||
# Validate environment variables during build
|
# Validate environment variables during build
|
||||||
RUN npx tsx scripts/validate-env.ts
|
RUN npx tsx scripts/validate-env.ts
|
||||||
|
|||||||
272
ENV_CLEANUP_SUMMARY.md
Normal file
272
ENV_CLEANUP_SUMMARY.md
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
# Environment Variables Cleanup - Summary
|
||||||
|
|
||||||
|
## What Was Done
|
||||||
|
|
||||||
|
Cleaned up the fragile, overkill environment variable mess and replaced it with a simple, clean, robust **fully automated** system.
|
||||||
|
|
||||||
|
## Changes Made
|
||||||
|
|
||||||
|
### 1. Dockerfile ✅
|
||||||
|
**Before**: 4 build args including runtime-only variables (SENTRY_DSN)
|
||||||
|
**After**: 3 build args - only `NEXT_PUBLIC_*` variables that need to be baked into the client bundle
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Only these build args now:
|
||||||
|
ARG NEXT_PUBLIC_BASE_URL
|
||||||
|
ARG NEXT_PUBLIC_UMAMI_WEBSITE_ID
|
||||||
|
ARG NEXT_PUBLIC_UMAMI_SCRIPT_URL
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. docker-compose.yml ✅
|
||||||
|
**Before**: 12+ individual environment variables listed
|
||||||
|
**After**: Single `env_file: .env` directive
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
app:
|
||||||
|
image: registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||||
|
env_file:
|
||||||
|
- .env # All runtime vars loaded from here
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. .gitea/workflows/deploy.yml ✅
|
||||||
|
**Before**: Passing 12+ environment variables individually via SSH command (fragile!)
|
||||||
|
**After**: **Fully automated** - workflow creates `.env` file from Gitea secrets and uploads it
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Before (FRAGILE):
|
||||||
|
ssh root@alpha.mintel.me \
|
||||||
|
"MAIL_FROM='${{ secrets.MAIL_FROM }}' \
|
||||||
|
MAIL_HOST='${{ secrets.MAIL_HOST }}' \
|
||||||
|
... (12+ variables) \
|
||||||
|
/home/deploy/deploy.sh"
|
||||||
|
|
||||||
|
# After (AUTOMATED):
|
||||||
|
# 1. Create .env from secrets
|
||||||
|
cat > /tmp/klz-cables.env << EOF
|
||||||
|
NODE_ENV=production
|
||||||
|
NEXT_PUBLIC_BASE_URL=${{ secrets.NEXT_PUBLIC_BASE_URL }}
|
||||||
|
# ... all other vars from secrets
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 2. Upload to server
|
||||||
|
scp /tmp/klz-cables.env root@alpha.mintel.me:/home/deploy/sites/klz-cables.com/.env
|
||||||
|
|
||||||
|
# 3. Deploy
|
||||||
|
ssh root@alpha.mintel.me "cd /home/deploy/sites/klz-cables.com && docker-compose up -d"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. New Files Created ✅
|
||||||
|
|
||||||
|
- **`.env.production`** - Template for reference (not used in automation)
|
||||||
|
- **`docs/DEPLOYMENT.md`** - Complete deployment guide
|
||||||
|
- **`docs/SERVER_SETUP.md`** - Server setup instructions
|
||||||
|
- **`docs/ENV_MIGRATION.md`** - Migration guide from old to new system
|
||||||
|
|
||||||
|
### 5. Updated Files ✅
|
||||||
|
|
||||||
|
- **`.env.example`** - Clear documentation of all variables with build-time vs runtime notes
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Build Time (CI/CD)
|
||||||
|
```
|
||||||
|
Gitea Workflow
|
||||||
|
↓
|
||||||
|
Only passes NEXT_PUBLIC_* as --build-arg
|
||||||
|
↓
|
||||||
|
Docker Build
|
||||||
|
↓
|
||||||
|
Validates env vars
|
||||||
|
↓
|
||||||
|
Bakes NEXT_PUBLIC_* into client bundle
|
||||||
|
↓
|
||||||
|
Push to Registry
|
||||||
|
```
|
||||||
|
|
||||||
|
### Runtime (Production Server) - FULLY AUTOMATED
|
||||||
|
```
|
||||||
|
Gitea Secrets
|
||||||
|
↓
|
||||||
|
Workflow creates .env file
|
||||||
|
↓
|
||||||
|
SCP uploads to server
|
||||||
|
↓
|
||||||
|
Secured (chmod 600, chown deploy:deploy)
|
||||||
|
↓
|
||||||
|
docker-compose.yml (env_file: .env)
|
||||||
|
↓
|
||||||
|
Loads .env into container
|
||||||
|
↓
|
||||||
|
Application runs with full config
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Benefits
|
||||||
|
|
||||||
|
### 1. Simplicity
|
||||||
|
- **Before**: 15+ Gitea secrets, variables in 3+ places
|
||||||
|
- **After**: All secrets in Gitea, automatically deployed
|
||||||
|
|
||||||
|
### 2. Clarity
|
||||||
|
- **Before**: Confusing duplication, unclear which vars go where
|
||||||
|
- **After**: Clear separation - build args vs runtime env file
|
||||||
|
|
||||||
|
### 3. Robustness
|
||||||
|
- **Before**: Fragile SSH command with 12+ inline variables
|
||||||
|
- **After**: Robust automated file generation and upload
|
||||||
|
|
||||||
|
### 4. Security
|
||||||
|
- **Before**: Secrets potentially exposed in CI logs
|
||||||
|
- **After**: Secrets masked in logs, .env auto-secured on server
|
||||||
|
|
||||||
|
### 5. Maintainability
|
||||||
|
- **Before**: Update in 3 places (Dockerfile, docker-compose.yml, deploy.yml)
|
||||||
|
- **After**: Update Gitea secrets only - deployment is automatic
|
||||||
|
|
||||||
|
### 6. **Zero Manual Steps** 🎉
|
||||||
|
- **Before**: Manual .env file creation on server (error-prone, can be forgotten)
|
||||||
|
- **After**: **Fully automated** - .env file created and uploaded on every deployment
|
||||||
|
|
||||||
|
## What You Need to Do
|
||||||
|
|
||||||
|
### Required Gitea Secrets
|
||||||
|
|
||||||
|
Ensure these secrets are configured in your Gitea repository:
|
||||||
|
|
||||||
|
**Build-Time (NEXT_PUBLIC_*):**
|
||||||
|
- `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:**
|
||||||
|
- `SENTRY_DSN` - Error tracking DSN
|
||||||
|
- `MAIL_HOST` - SMTP server
|
||||||
|
- `MAIL_PORT` - SMTP port (e.g., `587`)
|
||||||
|
- `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 (e.g., `klz:`)
|
||||||
|
|
||||||
|
**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
|
||||||
|
|
||||||
|
### That's It!
|
||||||
|
|
||||||
|
**No manual steps required.** Just push to main branch and the workflow will:
|
||||||
|
1. ✅ Build Docker image with NEXT_PUBLIC_* build args
|
||||||
|
2. ✅ Create .env file from all secrets
|
||||||
|
3. ✅ Upload .env to server
|
||||||
|
4. ✅ Secure .env file (600 permissions, deploy:deploy ownership)
|
||||||
|
5. ✅ Pull latest image
|
||||||
|
6. ✅ Deploy with docker-compose
|
||||||
|
|
||||||
|
## Files Changed
|
||||||
|
|
||||||
|
```
|
||||||
|
Modified:
|
||||||
|
├── Dockerfile (removed redundant build args)
|
||||||
|
├── docker-compose.yml (use env_file instead of individual vars)
|
||||||
|
├── .gitea/workflows/deploy.yml (automated .env creation & upload)
|
||||||
|
└── .env.example (clear documentation)
|
||||||
|
|
||||||
|
Created:
|
||||||
|
├── .env.production (reference template)
|
||||||
|
├── docs/DEPLOYMENT.md (deployment guide)
|
||||||
|
├── docs/SERVER_SETUP.md (server setup guide)
|
||||||
|
├── docs/ENV_MIGRATION.md (migration guide)
|
||||||
|
└── ENV_CLEANUP_SUMMARY.md (this file)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Deployment Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Developer pushes to main branch │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Gitea Workflow Triggered │
|
||||||
|
│ │
|
||||||
|
│ 1. Build Docker image (NEXT_PUBLIC_* build args) │
|
||||||
|
│ 2. Push to registry │
|
||||||
|
│ 3. Generate .env from secrets │
|
||||||
|
│ 4. Upload .env to server via SCP │
|
||||||
|
│ 5. SSH to server and deploy │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ Production Server │
|
||||||
|
│ │
|
||||||
|
│ 1. .env file secured (600, deploy:deploy) │
|
||||||
|
│ 2. Docker login to registry │
|
||||||
|
│ 3. Pull latest image │
|
||||||
|
│ 4. docker-compose down │
|
||||||
|
│ 5. docker-compose up -d (loads .env) │
|
||||||
|
│ 6. Health checks pass │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
↓
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ ✅ Deployment Complete - Gotify Notification Sent │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comparison: Before vs After
|
||||||
|
|
||||||
|
| Aspect | Before | After |
|
||||||
|
|--------|--------|-------|
|
||||||
|
| **Gitea Secrets** | 15+ secrets | Same secrets, better organized |
|
||||||
|
| **Build Args** | 4 vars (including runtime-only) | 3 vars (NEXT_PUBLIC_* only) |
|
||||||
|
| **Runtime Vars** | Passed via SSH command | Auto-generated .env file |
|
||||||
|
| **Manual Steps** | ❌ Manual .env creation | ✅ Fully automated |
|
||||||
|
| **Maintenance** | Update in 3 places | Update Gitea secrets only |
|
||||||
|
| **Security** | Secrets in CI logs | Secrets masked, .env secured |
|
||||||
|
| **Clarity** | Confusing duplication | Clear separation |
|
||||||
|
| **Robustness** | Fragile SSH command | Robust automation |
|
||||||
|
| **Error-Prone** | ❌ Can forget .env | ✅ Impossible to forget |
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- **[DEPLOYMENT.md](docs/DEPLOYMENT.md)** - Complete deployment guide
|
||||||
|
- **[SERVER_SETUP.md](docs/SERVER_SETUP.md)** - Server setup instructions (mostly automated now)
|
||||||
|
- **[ENV_MIGRATION.md](docs/ENV_MIGRATION.md)** - Migration from old to new system
|
||||||
|
- **[.env.example](.env.example)** - Environment variables reference
|
||||||
|
- **[.env.production](.env.production)** - Production template (for reference)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Deployment Fails
|
||||||
|
|
||||||
|
1. **Check Gitea secrets** - Ensure all required secrets are set
|
||||||
|
2. **Check workflow logs** - Look for specific error messages
|
||||||
|
3. **SSH to server** - Verify .env file exists and has correct permissions
|
||||||
|
4. **Check container logs** - `docker-compose logs -f app`
|
||||||
|
|
||||||
|
### .env File Issues
|
||||||
|
|
||||||
|
The workflow automatically:
|
||||||
|
- Creates .env from secrets
|
||||||
|
- Uploads to server
|
||||||
|
- Sets 600 permissions
|
||||||
|
- Sets deploy:deploy ownership
|
||||||
|
|
||||||
|
If there are issues, check the workflow logs for the "📝 Preparing environment configuration" step.
|
||||||
|
|
||||||
|
### Missing Environment Variables
|
||||||
|
|
||||||
|
If a variable is missing:
|
||||||
|
1. Add it to Gitea secrets
|
||||||
|
2. Update `.gitea/workflows/deploy.yml` to include it in the .env generation
|
||||||
|
3. Push to trigger new deployment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Result**: Environment variable management is now simple, clean, robust, and **fully automated**! 🎉
|
||||||
|
|
||||||
|
No more manual .env file creation. No more forgotten configuration. No more fragile SSH commands. Just push and deploy!
|
||||||
@@ -50,26 +50,14 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
networks:
|
networks:
|
||||||
- infra
|
- infra
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || wget --quiet --tries=1 --spider http://localhost:3000/ || true"]
|
test: ["CMD-SHELL", "wget --quiet --tries=1 --spider http://localhost:3000/health || wget --quiet --tries=1 --spider http://localhost:3000/ || true"]
|
||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 5
|
retries: 5
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
environment:
|
|
||||||
- NODE_ENV=${NODE_ENV:-production}
|
|
||||||
- NEXT_PUBLIC_BASE_URL=${NEXT_PUBLIC_BASE_URL:?NEXT_PUBLIC_BASE_URL is required}
|
|
||||||
- NEXT_PUBLIC_UMAMI_WEBSITE_ID=${NEXT_PUBLIC_UMAMI_WEBSITE_ID}
|
|
||||||
- NEXT_PUBLIC_UMAMI_SCRIPT_URL=${NEXT_PUBLIC_UMAMI_SCRIPT_URL}
|
|
||||||
- SENTRY_DSN=${SENTRY_DSN}
|
|
||||||
- MAIL_HOST=${MAIL_HOST}
|
|
||||||
- MAIL_PORT=${MAIL_PORT:-587}
|
|
||||||
- MAIL_USERNAME=${MAIL_USERNAME}
|
|
||||||
- MAIL_PASSWORD=${MAIL_PASSWORD}
|
|
||||||
- MAIL_FROM=${MAIL_FROM}
|
|
||||||
- MAIL_RECIPIENTS=${MAIL_RECIPIENTS}
|
|
||||||
- REDIS_URL=${REDIS_URL}
|
|
||||||
- REDIS_KEY_PREFIX=${REDIS_KEY_PREFIX}
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
infra:
|
infra:
|
||||||
|
|||||||
@@ -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`
|
## Overview
|
||||||
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
|
|
||||||
|
|
||||||
### 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
|
## Environment Variables
|
||||||
- `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
|
|
||||||
|
|
||||||
## 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
|
```bash
|
||||||
# SSH into the server
|
# Build with build-time arguments
|
||||||
ssh deploy@alpha.mintel.me
|
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
|
# Navigate to the project directory
|
||||||
cd /home/deploy/sites/klz-cables.com
|
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
|
# Pull the latest image
|
||||||
docker compose pull
|
docker pull registry.infra.mintel.me/mintel/klz-cables.com:latest
|
||||||
|
|
||||||
# Restart containers
|
# Restart the services
|
||||||
docker compose up -d --force-recreate --remove-orphans
|
docker-compose down
|
||||||
|
docker-compose up -d
|
||||||
|
|
||||||
# Clean up old images
|
# Check logs
|
||||||
docker image prune -f
|
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
|
## 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
|
### Build Failures
|
||||||
|
|
||||||
1. Check build logs in Gitea Actions tab
|
**Problem**: Build fails with "Environment validation failed"
|
||||||
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
|
|
||||||
|
|
||||||
|
**Solution**: Ensure all required `NEXT_PUBLIC_*` variables are provided as build arguments:
|
||||||
```bash
|
```bash
|
||||||
# Check container status
|
docker build \
|
||||||
docker compose ps
|
--build-arg NEXT_PUBLIC_BASE_URL=https://klz-cables.com \
|
||||||
|
--build-arg NEXT_PUBLIC_UMAMI_WEBSITE_ID=your-id \
|
||||||
# View logs
|
--build-arg NEXT_PUBLIC_UMAMI_SCRIPT_URL=https://analytics.infra.mintel.me/script.js \
|
||||||
docker compose logs -f app
|
-t klz-cables .
|
||||||
docker compose logs -f varnish
|
|
||||||
|
|
||||||
# Check health
|
|
||||||
docker compose exec app wget -O- http://localhost:3000/health
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
### Runtime Failures
|
||||||
|
|
||||||
```
|
**Problem**: Container starts but application crashes
|
||||||
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:
|
|
||||||
|
|
||||||
|
**Solution**: Check that the `.env` file exists and contains all required runtime variables:
|
||||||
```bash
|
```bash
|
||||||
# On the server
|
# On the server
|
||||||
cd /home/deploy/sites/klz-cables.com
|
cat /home/deploy/sites/klz-cables.com/.env
|
||||||
|
|
||||||
# Pull a specific version (if tagged)
|
# Check container logs
|
||||||
docker pull registry.infra.mintel.me/mintel/klz-cables.com:TAG
|
docker-compose logs app
|
||||||
|
|
||||||
# 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## 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
|
```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
|
## Additional Resources
|
||||||
docker compose restart varnish
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security
|
- [Next.js Environment Variables](https://nextjs.org/docs/basic-features/environment-variables)
|
||||||
|
- [Docker Compose Environment Variables](https://docs.docker.com/compose/environment-variables/)
|
||||||
- All secrets are stored in Gitea repository settings
|
- [Traefik Documentation](https://doc.traefik.io/traefik/)
|
||||||
- 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
|
|
||||||
|
|||||||
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