10 KiB
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
# 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
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
# 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 guidedocs/SERVER_SETUP.md- Server setup instructionsdocs/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 IDNEXT_PUBLIC_UMAMI_SCRIPT_URL- Umami script URL
Runtime:
SENTRY_DSN- Error tracking DSNMAIL_HOST- SMTP serverMAIL_PORT- SMTP port (e.g.,587)MAIL_USERNAME- SMTP usernameMAIL_PASSWORD- SMTP passwordMAIL_FROM- Sender emailMAIL_RECIPIENTS- Recipient emails (comma-separated)
Infrastructure:
REGISTRY_USER- Docker registry usernameREGISTRY_PASS- Docker registry passwordALPHA_SSH_KEY- SSH private key for deployment server
Notifications:
GOTIFY_URL- Gotify notification server URLGOTIFY_TOKEN- Gotify application token
That's It!
No manual steps required. Just push to main branch and the workflow will:
- ✅ Build Docker image with NEXT_PUBLIC_* build args
- ✅ Create .env file from all secrets
- ✅ Upload .env to server
- ✅ Secure .env file (600 permissions, deploy:deploy ownership)
- ✅ Pull latest image
- ✅ 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)
├── lib/services/create-services.ts (removed redundant dotenv usage)
└── scripts/migrate-*.ts (removed redundant dotenv usage)
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 - Complete deployment guide
- SERVER_SETUP.md - Server setup instructions (mostly automated now)
- ENV_MIGRATION.md - Migration from old to new system
- .env.example - Environment variables reference
- .env.production - Production template (for reference)
Troubleshooting
Deployment Fails
- Check Gitea secrets - Ensure all required secrets are set
- Check workflow logs - Look for specific error messages
- SSH to server - Verify .env file exists and has correct permissions
- 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:
- Add it to Gitea secrets
- Update
.gitea/workflows/deploy.ymlto include it in the .env generation - 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!